From 4793ef12e68f2884dcbff7b598716d9a6ecac4d2 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:38:08 -0700 Subject: [PATCH] refactor stats (#195) * logs contains only canister and install level key-value pairs * fix * mergeTags * babystep for stats endpoint * fix * fix * fix * track packages * Update src/Stats.tsx Co-authored-by: Ryan Vandersmith * validate origin * checkpoint * refactor * remove origin slice from grafana, since we have stats page --------- Co-authored-by: Ryan Vandersmith --- package-lock.json | 288 ++++++++++++---------- package.json | 1 + service/pool/Logs.mo | 86 +++---- service/pool/Main.mo | 47 ++-- service/pool/Metrics.mo | 3 +- src/App.tsx | 7 +- src/Stats.tsx | 168 +++++++++++++ src/build.ts | 14 +- src/components/CanisterModal.tsx | 2 +- src/components/Chart.tsx | 45 ++++ src/components/DeployModal.tsx | 48 +++- src/components/ImportGithub.tsx | 2 +- src/contexts/WorkplaceState.ts | 31 ++- src/declarations/backend/backend.did | 6 +- src/declarations/backend/backend.did.d.ts | 8 +- src/declarations/backend/backend.did.js | 2 +- src/index.js | 26 +- 17 files changed, 559 insertions(+), 225 deletions(-) create mode 100644 src/Stats.tsx create mode 100644 src/components/Chart.tsx diff --git a/package-lock.json b/package-lock.json index 66798e51..475c5e81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "prettier-plugin-motoko": "^0.8.1", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-google-charts": "^4.0.1", "react-markdown": "^6.0.2", "react-modal": "^3.14.3", "styled-components": "^5.3.0", @@ -59,11 +60,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.22.10", + "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" }, "engines": { @@ -123,21 +124,21 @@ } }, "node_modules/@babel/core": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz", - "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.17.tgz", + "integrity": "sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-compilation-targets": "^7.22.10", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.11", - "@babel/parser": "^7.22.11", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.11", - "@babel/types": "^7.22.11", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.22.17", + "@babel/helpers": "^7.22.15", + "@babel/parser": "^7.22.16", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.17", + "@babel/types": "^7.22.17", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -174,11 +175,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", + "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", "dependencies": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.22.15", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -222,13 +223,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", - "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", "browserslist": "^4.21.9", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -375,27 +376,27 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz", + "integrity": "sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -493,17 +494,17 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz", + "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -524,23 +525,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", - "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", + "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.11", - "@babel/types": "^7.22.11" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", + "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", "dependencies": { "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.4.2", @@ -590,9 +591,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz", - "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g==", + "version": "7.22.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", + "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1725,31 +1726,31 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", - "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.17.tgz", + "integrity": "sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==", "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.11", - "@babel/types": "^7.22.11", + "@babel/parser": "^7.22.16", + "@babel/types": "^7.22.17", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1758,12 +1759,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", - "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz", + "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.15", "to-fast-properties": "^2.0.0" }, "engines": { @@ -16444,6 +16445,15 @@ "dev": true, "license": "MIT" }, + "node_modules/react-google-charts": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-google-charts/-/react-google-charts-4.0.1.tgz", + "integrity": "sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ==", + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "license": "MIT" @@ -21727,11 +21737,11 @@ } }, "@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "requires": { - "@babel/highlight": "^7.22.10", + "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" }, "dependencies": { @@ -21775,21 +21785,21 @@ "dev": true }, "@babel/core": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz", - "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.17.tgz", + "integrity": "sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-compilation-targets": "^7.22.10", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.11", - "@babel/parser": "^7.22.11", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.11", - "@babel/types": "^7.22.11", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.22.17", + "@babel/helpers": "^7.22.15", + "@babel/parser": "^7.22.16", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.17", + "@babel/types": "^7.22.17", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -21812,11 +21822,11 @@ } }, "@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", + "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", "requires": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.22.15", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -21847,13 +21857,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", - "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "requires": { "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", "browserslist": "^4.21.9", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -21959,24 +21969,24 @@ } }, "@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" } }, "@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz", + "integrity": "sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.15" } }, "@babel/helper-optimise-call-expression": { @@ -22039,14 +22049,14 @@ "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz", + "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==" }, "@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true }, "@babel/helper-wrap-function": { @@ -22060,20 +22070,20 @@ } }, "@babel/helpers": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", - "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", + "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.11", - "@babel/types": "^7.22.11" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", + "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", "requires": { "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.4.2", @@ -22106,9 +22116,9 @@ } }, "@babel/parser": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz", - "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g==" + "version": "7.22.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", + "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==" }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.14.5", @@ -22764,39 +22774,39 @@ } }, "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", - "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.17.tgz", + "integrity": "sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==", "requires": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.22.15", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.11", - "@babel/types": "^7.22.11", + "@babel/parser": "^7.22.16", + "@babel/types": "^7.22.17", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", - "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", + "version": "7.22.17", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz", + "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==", "requires": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.15", "to-fast-properties": "^2.0.0" } }, @@ -32977,6 +32987,12 @@ "version": "6.0.9", "dev": true }, + "react-google-charts": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-google-charts/-/react-google-charts-4.0.1.tgz", + "integrity": "sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ==", + "requires": {} + }, "react-is": { "version": "17.0.2" }, diff --git a/package.json b/package.json index 5ddaaf03..c99a63c9 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "prettier-plugin-motoko": "^0.8.1", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-google-charts": "^4.0.1", "react-markdown": "^6.0.2", "react-modal": "^3.14.3", "styled-components": "^5.3.0", diff --git a/service/pool/Logs.mo b/service/pool/Logs.mo index 284c3f7a..7e28c0af 100644 --- a/service/pool/Logs.mo +++ b/service/pool/Logs.mo @@ -7,71 +7,73 @@ import {get} "mo:base/Option"; module { public type Origin = { origin: Text; tags: [Text] }; - public type SharedStatsByOrigin = (Map.Tree, Map.Tree, Map.Tree); + public type SharedStatsByOrigin = (Map.Tree, Map.Tree); public class StatsByOrigin() { var canisters = Map.RBTree(compare); var installs = Map.RBTree(compare); - var tags = Map.RBTree(compare); - public func share() : SharedStatsByOrigin = (canisters.share(), installs.share(), tags.share()); + public func share() : SharedStatsByOrigin = (canisters.share(), installs.share()); public func unshare(x : SharedStatsByOrigin) { canisters.unshare(x.0); installs.unshare(x.1); - tags.unshare(x.2); }; - func addTags(list: [Text]) { + func addTags(map: Map.RBTree, list: [Text]) { for (tag in list.vals()) { - switch (tags.get(tag)) { - case null { tags.put(tag, 1) }; - case (?n) { tags.put(tag, n + 1) }; + switch (map.get(tag)) { + case null { map.put(tag, 1) }; + case (?n) { map.put(tag, n + 1) }; }; }; }; - public func addCanister(origin: Origin) { - switch (canisters.get(origin.origin)) { - case null { canisters.put(origin.origin, 1) }; - case (?n) { canisters.put(origin.origin, n + 1) }; + // if to is null, delete the from tag + func merge_tag_(map: Map.RBTree, from: Text, opt_to: ?Text) { + ignore do ? { + let n1 = map.remove(from)!; + let to = opt_to!; + switch (map.get(to)) { + case null { map.put(to, n1) }; + case (?n2) { map.put(to, n1 + n2) }; + }; }; - // Not storing tags for create canister to avoid duplicate counting of tags - // addTags(origin.tags); + }; + public func merge_tag(from: Text, to: ?Text) { + merge_tag_(canisters, from, to); + merge_tag_(installs, from, to); + }; + public func addCanister(origin: Origin) { + addTags(canisters, ["origin:" # origin.origin]); + addTags(canisters, origin.tags); }; public func addInstall(origin: Origin) { - switch (installs.get(origin.origin)) { - case null { installs.put(origin.origin, 1) }; - case (?n) { installs.put(origin.origin, n + 1) }; - }; - // Only record tags for canister install - addTags(origin.tags); + addTags(installs, ["origin:" # origin.origin]); + addTags(installs, origin.tags); }; - public func dump() : ([(Text, Nat)], [(Text, Nat)], [(Text, Nat)]) { + public func dump() : ([(Text, Nat)], [(Text, Nat)]) { (toArray<(Text, Nat)>(canisters.entries()), toArray<(Text, Nat)>(installs.entries()), - toArray<(Text, Nat)>(tags.entries()) ) }; public func metrics() : Text { var result = ""; let now = timeNow() / 1_000_000; - for ((origin, count) in canisters.entries()) { - let name = "canisters_" # origin; - let desc = "Number of canisters requested from " # origin; - result := result # encode_single_value("counter", name, count, desc, now); - }; - for ((origin, count) in installs.entries()) { - let name = "installs_" # origin; - let desc = "Number of Wasm installed from " # origin; - result := result # encode_single_value("counter", name, count, desc, now); - }; - let profiling = get(tags.get("profiling"), 0); - let asset = get(tags.get("asset"), 0); - let install = get(tags.get("install"), 0); - let reinstall = get(tags.get("reinstall"), 0); - let upgrade = get(tags.get("upgrade"), 0); + let canister_playground = get(canisters.get("origin:playground"), 0); + let canister_dfx = get(canisters.get("origin:dfx"), 0); + let install_playground = get(installs.get("origin:playground"), 0); + let install_dfx = get(installs.get("origin:dfx"), 0); + let profiling = get(installs.get("wasm:profiling"), 0); + let asset = get(installs.get("wasm:asset"), 0); + let install = get(installs.get("mode:install"), 0); + let reinstall = get(installs.get("mode:reinstall"), 0); + let upgrade = get(installs.get("mode:upgrade"), 0); result := result - # encode_single_value("counter", "profiling", profiling, "Number of Wasm profiled", now) - # encode_single_value("counter", "asset", asset, "Number of asset Wasm canister installed", now) - # encode_single_value("counter", "install", install, "Number of Wasm with install mode", now) - # encode_single_value("counter", "reinstall", reinstall, "Number of Wasm with reinstall mode", now) - # encode_single_value("counter", "upgrade", upgrade, "Number of Wasm with upgrad mode", now); + # encode_single_value("counter", "create_from_playground", canister_playground, "Number of canisters created from playground", now) + # encode_single_value("counter", "install_from_playground", install_playground, "Number of Wasms installed from playground", now) + # encode_single_value("counter", "create_from_dfx", canister_dfx, "Number of canisters created from dfx", now) + # encode_single_value("counter", "install_from_dfx", install_dfx, "Number of Wasms installed from dfx", now) + # encode_single_value("counter", "profiling", profiling, "Number of Wasms profiled", now) + # encode_single_value("counter", "asset", asset, "Number of asset Wasms canister installed", now) + # encode_single_value("counter", "install", install, "Number of Wasms with install mode", now) + # encode_single_value("counter", "reinstall", reinstall, "Number of Wasms with reinstall mode", now) + # encode_single_value("counter", "upgrade", upgrade, "Number of Wasms with upgrad mode", now); result; }; }; diff --git a/service/pool/Main.mo b/service/pool/Main.mo index f3cf0a56..ef519739 100644 --- a/service/pool/Main.mo +++ b/service/pool/Main.mo @@ -33,7 +33,7 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { stable var stableChildren : [(Principal, [Principal])] = []; stable var stableTimers : [Types.CanisterInfo] = []; stable var previousParam : ?Types.InitParams = null; - stable var stableStatsByOrigin : Logs.SharedStatsByOrigin = (#leaf, #leaf, #leaf); + stable var stableStatsByOrigin : Logs.SharedStatsByOrigin = (#leaf, #leaf); system func preupgrade() { let (tree, metadata, children, timers) = pool.share(); @@ -62,9 +62,9 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { params; }; - public query func getStats() : async (Logs.Stats, [(Text, Nat)], [(Text, Nat)], [(Text, Nat)]) { - let (canister, install, tags) = statsByOrigin.dump(); - (stats, canister, install, tags); + public query func getStats() : async (Logs.Stats, [(Text, Nat)], [(Text, Nat)]) { + let (canister, install) = statsByOrigin.dump(); + (stats, canister, install); }; public query func balance() : async Nat { @@ -118,10 +118,22 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { }; }; }; + func validateOrigin(origin: Logs.Origin) : Bool { + if (origin.origin == "") { + return false; + }; + for (tag in origin.tags.vals()) { + // reject server side tags + if (tag == "mode:install" or tag == "mode:reinstall" or tag == "mode:upgrade" or tag == "wasm:profiling" or tag == "wasm:asset") { + return false; + } + }; + return true; + }; public shared ({ caller }) func getCanisterId(nonce : PoW.Nonce, origin : Logs.Origin) : async Types.CanisterInfo { - if (origin.origin == "") { - throw Error.reject "Please specify an origin"; + if (not validateOrigin(origin)) { + throw Error.reject "Please specify a valid origin"; }; if (caller != controller and not nonceCache.checkProofOfWork(nonce)) { stats := Logs.updateStats(stats, #mismatch); @@ -138,8 +150,8 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { type InstallConfig = { profiling: Bool; is_whitelisted: Bool; origin: Logs.Origin }; public shared ({ caller }) func installCode(info : Types.CanisterInfo, args : Types.InstallArgs, install_config : InstallConfig) : async Types.CanisterInfo { - if (install_config.origin.origin == "") { - throw Error.reject "Please specify an origin"; + if (not validateOrigin(install_config.origin)) { + throw Error.reject "Please specify a valid origin"; }; if (info.timestamp == 0) { stats := Logs.updateStats(stats, #mismatch); @@ -174,15 +186,15 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { // Build tags from install arguments let tags = Buffer.fromArray(install_config.origin.tags); if (install_config.profiling) { - tags.add("profiling"); + tags.add("wasm:profiling"); }; if (install_config.is_whitelisted) { - tags.add("asset"); + tags.add("wasm:asset"); }; switch (args.mode) { - case (#install) { tags.add("install") }; - case (#upgrade) { tags.add("upgrade") }; - case (#reinstall) { tags.add("reinstall") }; + case (#install) { tags.add("mode:install") }; + case (#upgrade) { tags.add("mode:upgrade") }; + case (#reinstall) { tags.add("mode:reinstall") }; }; let origin = { origin = install_config.origin.origin; tags = Buffer.toArray(tags) }; statsByOrigin.addInstall(origin); @@ -274,11 +286,17 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { stats := Logs.defaultStats; statsByOrigin := Logs.StatsByOrigin(); }; + public shared ({ caller }) func mergeTags(from: Text, to: ?Text) : async () { + if (caller != controller) { + throw Error.reject "Only called by controller"; + }; + statsByOrigin.merge_tag(from, to); + }; // Metrics public query func http_request(req : Metrics.HttpRequest) : async Metrics.HttpResponse { if (req.url == "/metrics") { - let body = Metrics.metrics(stats, statsByOrigin); + let body = Metrics.metrics(stats); { status_code = 200; headers = [("Content-Type", "text/plain; version=0.0.4"), ("Content-Length", Nat.toText(body.size()))]; @@ -427,6 +445,7 @@ shared (creator) actor class Self(opt_params : ?Types.InitParams) = this { #installCode : Any; #removeCode : Any; #resetStats : Any; + #mergeTags : Any; #wallet_receive : Any; #create_canister : Any; diff --git a/service/pool/Metrics.mo b/service/pool/Metrics.mo index 1642982f..6f29605d 100644 --- a/service/pool/Metrics.mo +++ b/service/pool/Metrics.mo @@ -15,7 +15,7 @@ module { body: Blob; }; let encode_single_value = Logs.encode_single_value; - public func metrics(stats: Logs.Stats, origin: Logs.StatsByOrigin) : Blob { + public func metrics(stats: Logs.Stats) : Blob { let now = Time.now() / 1_000_000; var result = ""; result := result # encode_single_value("counter", "canister_count", stats.num_of_canisters, "Number of canisters deployed", now); @@ -24,7 +24,6 @@ module { result := result # encode_single_value("counter", "out_of_capacity", stats.error_out_of_capacity, "Number of out of capacity requests", now); result := result # encode_single_value("counter", "total_wait_time", stats.error_total_wait_time, "Number of seconds waiting for out of capacity requests", now); result := result # encode_single_value("counter", "mismatch", stats.error_mismatch, "Number of mismatch requests including wrong nounce and timestamp", now); - result := result # origin.metrics(); Text.encodeUtf8(result) }; } diff --git a/src/App.tsx b/src/App.tsx index b51061ce..c50602c6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -82,7 +82,7 @@ async function fetchFromUrlParams( const { origin, files } = result; await dispatch({ type: "setOrigin", - payload: { origin: `playground:post:${origin}`, tags: [] }, + payload: { origin: "playground", tags: [`post:${origin}`] }, }); return files; } @@ -95,7 +95,7 @@ async function fetchFromUrlParams( }; await dispatch({ type: "setOrigin", - payload: { origin: "playground:git", tags: [`git:${git}`] }, + payload: { origin: "playground", tags: [`git:${git}`] }, }); return await worker.fetchGithub(repo); } @@ -147,7 +147,7 @@ async function fetchFromUrlParams( } await dispatch({ type: "setOrigin", - payload: { origin: "playground:tag", tags: [`tag:${tag}`] }, + payload: { origin: "playground", tags: [`tag:${tag}`] }, }); return files; } @@ -296,6 +296,7 @@ export function App() { isFirstOpen={isFirstVisit} /> setShowDeployModal(false)} onDeploy={deployWorkplace} diff --git a/src/Stats.tsx b/src/Stats.tsx new file mode 100644 index 00000000..26f106fd --- /dev/null +++ b/src/Stats.tsx @@ -0,0 +1,168 @@ +import { useState, useEffect } from "react"; +import { backend } from "./config/actor"; +import { Chart, Pie } from "./components/Chart"; + +/* +Taxonomy of tags +(*) indicates server side tags + +origin: + origin:playground + code source + example:{name} + file:new + git:{repo} + tag:{id} + post:{url} + upload:wasm + origin:dfx + wasm:asset (*) + origin:vscode + origin:spawned + +install mode: + mode:install (*) + mode:reinstall (*) + mode:upgrde (*) + +playground: + wasm property + wasm:profiling (*) + wasm:init_args + project imports + import:package:{repo} + import:canister:{id} + moc flags + moc:gc:force + moc:gc:{method} + ref:{url} +*/ + +function extract_slice(raw, cond) { + const res = raw + .filter(([name, _]) => cond(name)) + .map(([name, n]) => [name, Number(n)]); + res.sort((a, b) => b[1] - a[1]); + return res; +} +function join_slice(left, right) { + const l = Object.fromEntries(left); + const r = Object.fromEntries(right); + const full_key = [ + ...new Set( + left.map(([name, _]) => name).concat(right.map(([name, _]) => name)) + ), + ]; + const res = full_key.map((name) => [name, l[name] || 0, r[name] || 0]); + res.sort((a, b) => b[2] - a[2]); + return res; +} +function two_metric(canisters, installs, title, cond) { + const new_canister = extract_slice(canisters, cond); + const wasm = extract_slice(installs, cond); + return [[title, "Canister", "Wasm"]].concat(join_slice(new_canister, wasm)); +} +function one_metric(map, title, cond) { + const slice = extract_slice(map, cond); + return [[title, "Wasm"]].concat(slice); +} +function aggregate_slice(raw, prefix) { + const res = raw + .filter(([name, _]) => name.startsWith(prefix)) + .reduce((acc, [_, n]) => acc + Number(n), 0); + return res; +} +function generateCodeSourceData(map) { + const res = [ + ["Code source", "Wasm"], + ["file:new", aggregate_slice(map, "file:new")], + ["example", aggregate_slice(map, "example:")], + ["tag", aggregate_slice(map, "tag:")], + ["git", aggregate_slice(map, "git:")], + ["post", aggregate_slice(map, "post:")], + ["wasm", aggregate_slice(map, "upload:wasm")], + ]; + return res; +} + +export function Stats() { + const [canisters, setCanisters] = useState([]); + const [installs, setInstalls] = useState([]); + + useEffect(() => { + async function doit() { + // eslint-disable-next-line + const [_, canisters, installs] = await backend.getStats(); + setCanisters(canisters); + setInstalls(installs); + } + doit(); + }, []); + + return ( + <> +
+

Motoko Playground

+

Usage Statistics

+
+ + name.startsWith("mode:") + )} + /> + + + name.startsWith("origin:") + )} + /> + name.startsWith("example:") || name.startsWith("file:new") + )} + /> + + name.startsWith("post:") || + name.startsWith("git:") || + name.startsWith("tag:") || + name.startsWith("upload:wasm") + )} + /> + + name.startsWith("ref:") + )} + /> + + name.startsWith("import:") + )} + /> + name.startsWith("moc:"))} + /> + name.startsWith("wasm:"))} + /> + + ); +} diff --git a/src/build.ts b/src/build.ts index fcc0dd26..6e41514d 100644 --- a/src/build.ts +++ b/src/build.ts @@ -154,6 +154,14 @@ export async function deploy( } } +function mkOrigin(origin: Origin, is_install: boolean) { + const tags = + origin.session_tags && is_install + ? origin.tags.concat(origin.session_tags) + : origin.tags; + return { origin: origin.origin, tags: [...new Set(tags)] }; +} + async function createCanister( worker, logger: ILoggingStore, @@ -161,9 +169,7 @@ async function createCanister( ): Promise { const timestamp = BigInt(Date.now()) * BigInt(1_000_000); const nonce = await worker.pow(timestamp); - // remove tags for create canister to avoid duplicate counting - const no_tags = { origin: origin.origin, tags: [] }; - const info = await backend.getCanisterId(nonce, no_tags); + const info = await backend.getCanisterId(nonce, mkOrigin(origin, false)); logger.log(`Got canister id ${info.id}`); return { id: info.id, @@ -198,7 +204,7 @@ async function install( const installConfig = { profiling, is_whitelisted: false, - origin, + origin: mkOrigin(origin, true), }; const new_info = await backend.installCode( canisterInfo, diff --git a/src/components/CanisterModal.tsx b/src/components/CanisterModal.tsx index c5d0e85b..2cc76df2 100644 --- a/src/components/CanisterModal.tsx +++ b/src/components/CanisterModal.tsx @@ -110,7 +110,7 @@ export function CanisterModal({ isOpen, close, deploySetter }) { await deploySetter.setShowDeployModal(true); await dispatch({ type: "setOrigin", - payload: { origin: "playground:wasm", tags: [] }, + payload: { origin: "playground", tags: [`upload:wasm`] }, }); } async function addCanister() { diff --git a/src/components/Chart.tsx b/src/components/Chart.tsx new file mode 100644 index 00000000..f6dfa40c --- /dev/null +++ b/src/components/Chart.tsx @@ -0,0 +1,45 @@ +import { Chart as GChart } from "react-google-charts"; + +export function Chart({ title, data }) { + const options = { + title, + chartArea: { width: "50%" }, + hAxis: { minValue: 0 }, + bars: "horizontal", + }; + return ( + <> + {data.length > 1 ? ( + + ) : null} + + ); +} + +export function Pie({ title, data }) { + const options = { + title, + pieHole: 0.3, + is3D: false, + }; + return ( + <> + {data.length > 1 ? ( + + ) : null} + + ); +} diff --git a/src/components/DeployModal.tsx b/src/components/DeployModal.tsx index 8ba8ea5b..16a1b89e 100644 --- a/src/components/DeployModal.tsx +++ b/src/components/DeployModal.tsx @@ -6,7 +6,11 @@ import { Modal } from "./shared/Modal"; import { CanisterInfo, getCanisterName, deploy, compileWasm } from "../build"; import { ILoggingStore } from "./Logger"; import { Button } from "./shared/Button"; -import { WorkerContext } from "../contexts/WorkplaceState"; +import { + WorkerContext, + WorkplaceDispatchContext, + WorkplaceState, +} from "../contexts/WorkplaceState"; import { didjs } from "../config/actor"; import { Field } from "./shared/Field"; import { Confirm } from "./shared/Confirm"; @@ -89,6 +93,7 @@ export interface DeploySetter { } interface DeployModalProps { + state: WorkplaceState; isOpen: boolean; close: () => void; onDeploy: (string) => void; @@ -106,6 +111,7 @@ interface DeployModalProps { const MAX_CANISTERS = 3; export function DeployModal({ + state, isOpen, close, onDeploy, @@ -131,6 +137,7 @@ export function DeployModal({ const [deployMode, setDeployMode] = useState(""); const [startDeploy, setStartDeploy] = useState(false); const worker = useContext(WorkerContext); + const dispatch = useContext(WorkplaceDispatchContext); const exceedsLimit = Object.keys(canisters).length >= MAX_CANISTERS; const isMotoko = wasm ? false : true; @@ -234,8 +241,45 @@ export function DeployModal({ } } + async function addTags() { + if (initTypes.length > 0) { + await dispatch({ type: "addSessionTag", payload: "wasm:init_args" }); + } + if (forceGC) { + await dispatch({ type: "addSessionTag", payload: "moc:gc:force" }); + } + if (gcMethod !== "incremental") { + await dispatch({ type: "addSessionTag", payload: `moc:gc:${gcMethod}` }); + } + for (const pack of Object.values(state.packages)) { + if (pack.name !== "base") { + let repo = pack.repo; + if ( + pack.repo.startsWith("https://github.com/") && + pack.repo.endsWith(".git") + ) { + repo = pack.repo.slice(19, -4); + } + await dispatch({ + type: "addSessionTag", + payload: `import:package:${repo}`, + }); + } + } + for (const canister of Object.values(state.canisters)) { + if (canister.isExternal) { + await dispatch({ + type: "addSessionTag", + payload: `import:canister:${canister.id}`, + }); + } + } + } + async function handleDeploy(mode: string) { const args = parse(); + await addTags(); + console.log(origin); try { await isDeploy(true); const info = await deploy( @@ -261,8 +305,10 @@ export function DeployModal({ onDeploy(info); } setCompileResult({ wasm: undefined }); + await dispatch({ type: "clearSessionTags" }); } catch (err) { isDeploy(false); + await dispatch({ type: "clearSessionTags" }); throw err; } } diff --git a/src/components/ImportGithub.tsx b/src/components/ImportGithub.tsx index c0f5e4fe..d8c87a8d 100644 --- a/src/components/ImportGithub.tsx +++ b/src/components/ImportGithub.tsx @@ -45,7 +45,7 @@ export function ImportGitHub({ importCode, close, isPackageModal = false }) { close(); await dispatch({ type: "setOrigin", - payload: { origin: "playground:git", tags: [`git:${repo}`] }, + payload: { origin: "playground", tags: [`git:${repo}`] }, }); } else { setError(`Cannot find repo or the directory contains no ".mo" files.`); diff --git a/src/contexts/WorkplaceState.ts b/src/contexts/WorkplaceState.ts index d669f15f..19800ad4 100644 --- a/src/contexts/WorkplaceState.ts +++ b/src/contexts/WorkplaceState.ts @@ -5,6 +5,8 @@ import { PackageInfo } from "../workers/file"; export interface Origin { origin: string; tags: Array; + // only added for install_code, and will be clear after each deploy + session_tags?: Array; } export interface WorkplaceState { files: Record; @@ -12,7 +14,7 @@ export interface WorkplaceState { canisters: Record; selectedCanister: string | null; packages: Record; - origin: Origin | undefined; + origin: Origin; } export function getActorAliases( canisters: Record @@ -133,6 +135,13 @@ export type WorkplaceReducerAction = | { type: "setOrigin"; payload: Origin; + } + | { + type: "addSessionTag"; + payload: string; + } + | { + type: "clearSessionTags"; }; function selectFirstFile(files: Record): string | null { @@ -166,7 +175,7 @@ export const workplaceReducer = { canisters, selectedCanister: null, packages: {}, - origin: undefined, + origin: { origin: "playground", tags: [], session_tags: [] }, }; }, /** Return updated state based on an action */ @@ -242,6 +251,24 @@ export const workplaceReducer = { origin: action.payload, }; } + case "addSessionTag": { + if (state.origin.session_tags) { + state.origin.session_tags.push(action.payload); + } else { + state.origin.session_tags = [action.payload]; + } + return state; + } + case "clearSessionTags": { + return { + ...state, + origin: { + origin: state.origin.origin, + tags: state.origin.tags, + session_tags: [], + }, + }; + } default: // this should never be reached. If there is a type error here, add a 'case' // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/declarations/backend/backend.did b/src/declarations/backend/backend.did index 1ed04c4c..205dff95 100644 --- a/src/declarations/backend/backend.did +++ b/src/declarations/backend/backend.did @@ -52,10 +52,7 @@ type Self = }, vec record { text; nat; - }, vec record { - text; - nat; - }) query; + }) query; getSubtree: (CanisterInfo) -> (vec record { principal; @@ -74,6 +71,7 @@ type Self = }; wasm_module: wasm_module; }) -> (); + mergeTags: (text, opt text) -> (); removeCode: (CanisterInfo) -> (); resetStats: () -> (); start_canister: (record {canister_id: canister_id;}) -> (); diff --git a/src/declarations/backend/backend.did.d.ts b/src/declarations/backend/backend.did.d.ts index 5ed64dce..b33884a4 100644 --- a/src/declarations/backend/backend.did.d.ts +++ b/src/declarations/backend/backend.did.d.ts @@ -69,12 +69,7 @@ export interface Self { getInitParams: ActorMethod<[], InitParams>; getStats: ActorMethod< [], - [ - Stats, - Array<[string, bigint]>, - Array<[string, bigint]>, - Array<[string, bigint]> - ] + [Stats, Array<[string, bigint]>, Array<[string, bigint]>] >; getSubtree: ActorMethod< [CanisterInfo], @@ -96,6 +91,7 @@ export interface Self { ], undefined >; + mergeTags: ActorMethod<[string, [] | [string]], undefined>; removeCode: ActorMethod<[CanisterInfo], undefined>; resetStats: ActorMethod<[], undefined>; start_canister: ActorMethod<[{ canister_id: canister_id }], undefined>; diff --git a/src/declarations/backend/backend.did.js b/src/declarations/backend/backend.did.js index 667cbf24..c1d57a36 100644 --- a/src/declarations/backend/backend.did.js +++ b/src/declarations/backend/backend.did.js @@ -107,7 +107,6 @@ export const idlFactory = ({ IDL }) => { Stats, IDL.Vec(IDL.Tuple(IDL.Text, IDL.Nat)), IDL.Vec(IDL.Tuple(IDL.Text, IDL.Nat)), - IDL.Vec(IDL.Tuple(IDL.Text, IDL.Nat)), ], ["query"] ), @@ -138,6 +137,7 @@ export const idlFactory = ({ IDL }) => { [], [] ), + mergeTags: IDL.Func([IDL.Text, IDL.Opt(IDL.Text)], [], []), removeCode: IDL.Func([CanisterInfo], [], []), resetStats: IDL.Func([], [], []), start_canister: IDL.Func( diff --git a/src/index.js b/src/index.js index a988766e..955d83b4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,20 +1,30 @@ import React from "react"; import ReactDOM from "react-dom"; import { App } from "./App"; +import { Stats } from "./Stats"; import reportWebVitals from "./reportWebVitals"; import { ProvideLogging } from "./components/Logger"; import "./assets/styles/reboot.css"; import "./assets/styles/variables.css"; import "./assets/fonts/CircularXX.css"; -ReactDOM.render( - - - - - , - document.getElementById("root") -); +if (window.location.pathname === "/stats") { + ReactDOM.render( + + + , + document.getElementById("root") + ); +} else { + ReactDOM.render( + + + + + , + document.getElementById("root") + ); +} // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log))