feat: Implement the eBPF profile widget on dashboard (#72)

This commit is contained in:
Fine0830 2022-04-24 20:24:23 +08:00 committed by GitHub
parent 393885324b
commit 8a07b1d804
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1894 additions and 113 deletions

314
package-lock.json generated
View File

@ -10,11 +10,13 @@
"dependencies": {
"axios": "^0.24.0",
"d3": "^7.3.0",
"d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1",
"echarts": "^5.2.2",
"element-plus": "^2.0.2",
"lodash": "^4.17.21",
"pinia": "^2.0.5",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9",
@ -1810,6 +1812,18 @@
"ms": "^2.1.1"
}
},
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"peer": true,
"dependencies": {
"@types/hammerjs": "^2.0.36"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@element-plus/icons-vue": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.7.tgz",
@ -3299,6 +3313,12 @@
"@types/node": "*"
}
},
"node_modules/@types/hammerjs": {
"version": "2.0.41",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==",
"peer": true
},
"node_modules/@types/http-proxy": {
"version": "1.17.8",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
@ -7881,8 +7901,7 @@
"node_modules/component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"node_modules/compressible": {
"version": "2.0.18",
@ -8694,6 +8713,12 @@
"node": ">=4"
}
},
"node_modules/cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=",
"peer": true
},
"node_modules/cssnano": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
@ -9104,6 +9129,16 @@
"node": ">=0.8"
}
},
"node_modules/cypress/node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"dev": true,
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/d3": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.3.0.tgz",
@ -9306,6 +9341,21 @@
"node": ">=12"
}
},
"node_modules/d3-flame-graph": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/d3-flame-graph/-/d3-flame-graph-4.1.3.tgz",
"integrity": "sha512-NijuhJZhaTMwobVgwGQ67x9PovqMMHXBbs0FMHEGJvsWZGuL4M7OsB03v8mHdyVyHhnQYGsYnb5w021e9+R+RQ==",
"dependencies": {
"d3-array": "^3.1.1",
"d3-dispatch": "^3.0.1",
"d3-ease": "^3.0.1",
"d3-format": "^3.0.1",
"d3-hierarchy": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-transition": "^3.0.1"
}
},
"node_modules/d3-force": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
@ -16412,6 +16462,12 @@
"node": ">=0.6.0"
}
},
"node_modules/keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"peer": true
},
"node_modules/killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@ -18203,7 +18259,6 @@
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
"dev": true,
"engines": {
"node": "*"
}
@ -21672,6 +21727,15 @@
"node": ">= 6"
}
},
"node_modules/propagating-hammerjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-2.0.1.tgz",
"integrity": "sha512-PH3zG5whbSxMocphXJzVtvKr+vWAgfkqVvtuwjSJ/apmEACUoiw6auBAT5HYXpZOR0eGcTAfYG5Yl8h91O5Elg==",
"peer": true,
"peerDependencies": {
"@egjs/hammerjs": "^2.0.17"
}
},
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -22411,6 +22475,16 @@
"request": "^2.34"
}
},
"node_modules/request/node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"dev": true,
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -23746,15 +23820,6 @@
"ms": "^2.1.1"
}
},
"node_modules/sockjs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/sort-keys": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
@ -26807,13 +26872,11 @@
}
},
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"dev": true,
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "bin/uuid"
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache": {
@ -26871,6 +26934,59 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"node_modules/vis-data": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz",
"integrity": "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==",
"hasInstallScript": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"uuid": "^7.0.0 || ^8.0.0",
"vis-util": "^4.0.0 || ^5.0.0"
}
},
"node_modules/vis-timeline": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/vis-timeline/-/vis-timeline-7.5.1.tgz",
"integrity": "sha512-XZMHHbA8xm9/Y/iu3mE9MT7J5tfWgbdsW+PmqrgINU2QRX24AiqifNHZHV4YYzeJstiTSOg9Gs5qRkxQ0BvZJw==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0",
"keycharm": "^0.3.0 || ^0.4.0",
"moment": "^2.24.0",
"propagating-hammerjs": "^1.4.0 || ^2.0.0",
"uuid": "^3.4.0 || ^7.0.0 || ^8.0.0",
"vis-data": "^6.3.0 || ^7.0.0",
"vis-util": "^3.0.0 || ^4.0.0 || ^5.0.0",
"xss": "^1.0.0"
}
},
"node_modules/vis-util": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz",
"integrity": "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==",
"peer": true,
"engines": {
"node": ">=8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0"
}
},
"node_modules/vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@ -28266,6 +28382,16 @@
"node": ">= 6"
}
},
"node_modules/webpack-log/node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"dev": true,
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/webpack-merge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
@ -28709,6 +28835,28 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"node_modules/xss": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.11.tgz",
"integrity": "sha512-EimjrjThZeK2MO7WKR9mN5ZC1CSqivSl55wvUK5EtU6acf0rzEE1pN+9ZDrFXJ82BRp3JL38pPE6S4o/rpp1zQ==",
"peer": true,
"dependencies": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"bin": {
"xss": "bin/xss"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/xss/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"peer": true
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -30163,6 +30311,15 @@
}
}
},
"@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"peer": true,
"requires": {
"@types/hammerjs": "^2.0.36"
}
},
"@element-plus/icons-vue": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.7.tgz",
@ -31426,6 +31583,12 @@
"@types/node": "*"
}
},
"@types/hammerjs": {
"version": "2.0.41",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==",
"peer": true
},
"@types/http-proxy": {
"version": "1.17.8",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
@ -35117,8 +35280,7 @@
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"compressible": {
"version": "2.0.18",
@ -35759,6 +35921,12 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=",
"peer": true
},
"cssnano": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
@ -36098,6 +36266,12 @@
"psl": "^1.1.24",
"punycode": "^1.4.1"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
@ -36249,6 +36423,21 @@
"d3-dsv": "1 - 3"
}
},
"d3-flame-graph": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/d3-flame-graph/-/d3-flame-graph-4.1.3.tgz",
"integrity": "sha512-NijuhJZhaTMwobVgwGQ67x9PovqMMHXBbs0FMHEGJvsWZGuL4M7OsB03v8mHdyVyHhnQYGsYnb5w021e9+R+RQ==",
"requires": {
"d3-array": "^3.1.1",
"d3-dispatch": "^3.0.1",
"d3-ease": "^3.0.1",
"d3-format": "^3.0.1",
"d3-hierarchy": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-transition": "^3.0.1"
}
},
"d3-force": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
@ -41825,6 +42014,12 @@
"verror": "1.10.0"
}
},
"keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"peer": true
},
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@ -43211,8 +43406,7 @@
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
"dev": true
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"move-concurrently": {
"version": "1.0.1",
@ -45877,6 +46071,13 @@
"sisteransi": "^1.0.5"
}
},
"propagating-hammerjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-2.0.1.tgz",
"integrity": "sha512-PH3zG5whbSxMocphXJzVtvKr+vWAgfkqVvtuwjSJ/apmEACUoiw6auBAT5HYXpZOR0eGcTAfYG5Yl8h91O5Elg==",
"peer": true,
"requires": {}
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -46435,6 +46636,14 @@
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"request-progress": {
@ -47531,14 +47740,6 @@
"faye-websocket": "^0.11.3",
"uuid": "^8.3.2",
"websocket-driver": "^0.7.4"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
}
}
},
"sockjs-client": {
@ -49976,10 +50177,9 @@
"dev": true
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"v8-compile-cache": {
"version": "2.3.0",
@ -50028,6 +50228,26 @@
}
}
},
"vis-data": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz",
"integrity": "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==",
"peer": true,
"requires": {}
},
"vis-timeline": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/vis-timeline/-/vis-timeline-7.5.1.tgz",
"integrity": "sha512-XZMHHbA8xm9/Y/iu3mE9MT7J5tfWgbdsW+PmqrgINU2QRX24AiqifNHZHV4YYzeJstiTSOg9Gs5qRkxQ0BvZJw==",
"requires": {}
},
"vis-util": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz",
"integrity": "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==",
"peer": true,
"requires": {}
},
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@ -51307,6 +51527,14 @@
"requires": {
"ansi-colors": "^3.0.0",
"uuid": "^3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"webpack-merge": {
@ -51531,6 +51759,24 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"xss": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.11.tgz",
"integrity": "sha512-EimjrjThZeK2MO7WKR9mN5ZC1CSqivSl55wvUK5EtU6acf0rzEE1pN+9ZDrFXJ82BRp3JL38pPE6S4o/rpp1zQ==",
"peer": true,
"requires": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"peer": true
}
}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -12,11 +12,13 @@
"dependencies": {
"axios": "^0.24.0",
"d3": "^7.3.0",
"d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1",
"echarts": "^5.2.2",
"element-plus": "^2.0.2",
"lodash": "^4.17.21",
"pinia": "^2.0.5",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9",

View File

@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M17.016 17.016v-4.031h-2.016v4.031h2.016zM12.984 17.016v-10.031h-1.969v10.031h1.969zM9 17.016v-7.031h-2.016v7.031h2.016zM18.984 3q0.797 0 1.406 0.609t0.609 1.406v13.969q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-13.969q0-0.797 0.609-1.406t1.406-0.609h13.969z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

15
src/assets/icons/view.svg Normal file
View File

@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1650287922642" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3055" width="48" height="48"><path d="M277.333333 325.333333m5.333334 0l325.333333 0q5.333333 0 5.333333 5.333334l0 64q0 5.333333-5.333333 5.333333l-325.333333 0q-5.333333 0-5.333334-5.333333l0-64q0-5.333333 5.333334-5.333334Z" p-id="3056" fill="#707070"></path><path d="M277.333333 474.666667m5.333334 0l325.333333 0q5.333333 0 5.333333 5.333333l0 64q0 5.333333-5.333333 5.333333l-325.333333 0q-5.333333 0-5.333334-5.333333l0-64q0-5.333333 5.333334-5.333333Z" p-id="3057" fill="#707070"></path><path d="M277.333333 624m5.333334 0l247.36 0q5.333333 0 5.333333 5.333333l0 64q0 5.333333-5.333333 5.333334l-247.36 0q-5.333333 0-5.333334-5.333334l0-64q0-5.333333 5.333334-5.333333Z" p-id="3058" fill="#707070"></path><path d="M565.333333 842.666667H186.666667a5.333333 5.333333 0 0 1-5.333334-5.333334V186.666667a5.333333 5.333333 0 0 1 5.333334-5.333334h522.666666v346.24a5.333333 5.333333 0 0 0 5.333334 5.333334h64a5.333333 5.333333 0 0 0 5.333333-5.333334V106.666667H112a5.333333 5.333333 0 0 0-5.333333 5.333333v800a5.333333 5.333333 0 0 0 5.333333 5.333333h453.333333a5.333333 5.333333 0 0 0 5.333334-5.333333v-64a5.333333 5.333333 0 0 0-5.333334-5.333333z" p-id="3059" fill="#707070"></path><path d="M868.426667 723.786667a144.64 144.64 0 1 0-144.64 144.64 144.64 144.64 0 0 0 144.64-144.64z m-144.64 69.973333a69.973333 69.973333 0 1 1 69.973333-69.973333 70.026667 70.026667 0 0 1-69.973333 69.973333z" p-id="3060" fill="#707070"></path><path d="M811.758007 864.533065m3.771237-3.771236l45.254834-45.254834q3.771236-3.771236 7.542472 0l45.254834 45.254834q3.771236 3.771236 0 7.542472l-45.254834 45.254834q-3.771236 3.771236-7.542472 0l-45.254834-45.254834q-3.771236-3.771236 0-7.542472Z" p-id="3061" fill="#707070"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -32,7 +32,7 @@ interface Option {
const emit = defineEmits(["change"]);
const props = defineProps({
options: {
type: Array as PropType<(Option & { disabled: boolean })[]>,
type: Array as PropType<Option[]>,
default: () => [],
},
value: {
@ -44,7 +44,7 @@ const props = defineProps({
const selected = ref<string>(props.value);
function checked(opt: string) {
function checked(opt: unknown) {
emit("change", opt);
}
</script>

View File

@ -58,7 +58,7 @@ const props = defineProps({
},
size: { type: null, default: "default" },
placeholder: {
type: [String, Number] as PropType<string | number>,
type: [String, undefined] as PropType<string>,
default: "Select a option",
},
borderRadius: { type: Number, default: 3 },

View File

@ -14,13 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { App } from "vue";
import Icon from "./Icon.vue";
import TimePicker from "./TimePicker.vue";
import Selector from "./Selector.vue";
import Graph from "./Graph.vue";
import Radio from "./Radio.vue";
import SelectSingle from "./SelectSingle.vue";
import type { App } from "vue";
import VueGridLayout from "vue-grid-layout";
const components: { [key: string]: any } = {

View File

@ -0,0 +1,93 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const queryCreateTaskData = {
variable: "$serviceId: ID!",
query: `
createTaskData: queryPrepareCreateEBPFProfilingTaskData(serviceId: $serviceId) {
couldProfiling
processLabels
}`,
};
export const createEBPFTask = {
variable: "$request: EBPFProfilingTaskFixedTimeCreationRequest!",
query: `
createTaskData: createEBPFProfilingFixedTimeTask(request: $request) {
status
errorReason
id
}`,
};
export const queryEBPFTasks = {
variable: "$serviceId: ID!",
query: `
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId) {
taskId
serviceName
serviceId
processLabels
taskStartTime
triggerType
fixedTriggerDuration
targetType
createTime
}`,
};
export const queryEBPFSchedules = {
variable: "$taskId: ID!, $duration: Duration!",
query: `
eBPFSchedules: queryEBPFProfilingSchedules(taskId: $taskId, duration: $duration) {
scheduleId
taskId
process {
id
name
serviceId
serviceName
instanceId
instanceName
layer
agentId
detectType
attributes {
name
value
}
labels
}
startTime
endTime
}`,
};
export const analysisEBPFResult = {
variable:
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!",
query: `
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges) {
tip
trees {
elements {
id
parentId
symbol
stackType
dumpCount
}
}
}`,
};

View File

@ -25,6 +25,7 @@ import * as log from "./query/log";
import * as profile from "./query/profile";
import * as alarm from "./query/alarm";
import * as event from "./query/event";
import * as ebpf from "./query/ebpf";
const query: { [key: string]: string } = {
...app,
@ -36,6 +37,7 @@ const query: { [key: string]: string } = {
...profile,
...alarm,
...event,
...ebpf,
};
class Graphql {
private queryData = "";

34
src/graphql/query/ebpf.ts Normal file
View File

@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
queryCreateTaskData,
createEBPFTask,
queryEBPFTasks,
queryEBPFSchedules,
analysisEBPFResult,
} from "../fragments/ebpf";
export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`;
export const saveEBPFTask = `mutation createEBPFTask(${createEBPFTask.variable}) {${createEBPFTask.query}}`;
export const getEBPFTasks = `query queryEBPFTasks(${queryEBPFTasks.variable}) {${queryEBPFTasks.query}}`;
export const getEBPFSchedules = `query queryEBPFSchedules(${queryEBPFSchedules.variable}) {${queryEBPFSchedules.query}}`;
export const getEBPFResult = `query analysisEBPFResult(${analysisEBPFResult.variable}) {${analysisEBPFResult.query}}`;

View File

@ -131,6 +131,11 @@ const msg = {
metricLabel: "Metric Label",
showUnit: "Show Unit",
noGraph: "No Graph",
taskId: "Task ID",
triggerType: "Trigger Type",
targetType: "Target Type",
ebpfTip: "Don't have process could profiling",
processSelect: "Click to select processes",
hourTip: "Select Hour",
minuteTip: "Select Minute",
secondTip: "Select Second",

View File

@ -129,6 +129,11 @@ const msg = {
metricLabel: "指标标签",
showUnit: "显示单位",
noGraph: "无图表",
taskId: "任务ID",
triggerType: "触发类型",
targetType: "目标类型",
processSelect: "点击选择进程",
ebpfTip: "没有进程可以分析",
hourTip: "选择小时",
minuteTip: "选择分钟",
secondTip: "选择秒数",

View File

@ -20,7 +20,7 @@ import router from "./router";
import { store } from "./store";
import components from "@/components";
import i18n from "./locales";
import "./styles/index.scss";
import "./styles/index.ts";
const app = createApp(App);

View File

@ -114,7 +114,12 @@ export const dashboardStore = defineStore({
: 3,
};
}
if (type === "Trace" || type === "Profile" || type === "Log") {
if (
type === "Trace" ||
type === "Profile" ||
type === "Log" ||
type === "Ebpf"
) {
newItem.h = 36;
}
if (type === "Text") {

153
src/store/modules/ebpf.ts Normal file
View File

@ -0,0 +1,153 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Duration, Option } from "@/types/app";
import {
EBPFTaskCreationRequest,
EBPFProfilingSchedule,
EBPFTaskList,
AnalyzationTrees,
} from "@/types/ebpf";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface EbpfStore {
durationTime: Duration;
taskList: EBPFTaskList[];
eBPFSchedules: EBPFProfilingSchedule[];
currentSchedule: EBPFProfilingSchedule | Record<string, never>;
analyzeTrees: AnalyzationTrees[];
labels: Option[];
couldProfiling: boolean;
tip: string;
}
export const ebpfStore = defineStore({
id: "eBPF",
state: (): EbpfStore => ({
durationTime: useAppStoreWithOut().durationTime,
taskList: [],
eBPFSchedules: [],
currentSchedule: {},
analyzeTrees: [],
labels: [{ value: "", label: "" }],
couldProfiling: false,
tip: "",
}),
actions: {
setCurrentSpan(span: Span) {
this.currentSpan = span;
},
setCurrentSchedule(s: Trace) {
this.currentSchedule = s;
},
async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql
.query("getCreateTaskData")
.params({ serviceId });
if (res.data.errors) {
return res.data;
}
const json = res.data.data.createTaskData;
this.couldProfiling = json.couldProfiling || false;
this.labels = json.processLabels.map((d: string) => {
return { label: d, value: d };
});
return res.data;
},
async createTask(param: EBPFTaskCreationRequest) {
const res: AxiosResponse = await graphql
.query("saveEBPFTask")
.params({ request: param });
if (res.data.errors) {
return res.data;
}
this.getTaskList(param.serviceId);
return res.data;
},
async getTaskList(serviceId: string) {
const res: AxiosResponse = await graphql
.query("getEBPFTasks")
.params({ serviceId });
this.tip = "";
if (res.data.errors) {
return res.data;
}
this.taskList = res.data.data.queryEBPFTasks.reverse() || [];
if (!this.taskList.length) {
return res.data;
}
this.getEBPFSchedules({ taskId: this.taskList[0].taskId });
return res.data;
},
async getEBPFSchedules(params: { taskId: string; duration?: Duration }) {
const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql
.query("getEBPFSchedules")
.params({ ...params, duration });
if (res.data.errors) {
this.eBPFSchedules = [];
return res.data;
}
this.tip = "";
const { eBPFSchedules } = res.data.data;
this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) {
this.eBPFSchedules = [];
}
this.analyzeTrees = [];
return res.data;
},
async getEBPFAnalyze(params: {
scheduleIdList: string[];
timeRanges: Array<{ start: number; end: number }>;
}) {
const res: AxiosResponse = await graphql
.query("getEBPFResult")
.params(params);
if (res.data.errors) {
this.analyzeTrees = [];
return res.data;
}
const { analysisEBPFResult } = res.data.data;
this.tip = analysisEBPFResult.tip;
if (!analysisEBPFResult) {
this.analyzeTrees = [];
return res.data;
}
if (analysisEBPFResult.tip) {
this.analyzeTrees = [];
return res.data;
}
this.analyzeTrees = analysisEBPFResult.trees[0].elements;
return res.data;
},
},
});
export function useEbpfStore(): any {
return ebpfStore(store);
}

View File

@ -16,7 +16,7 @@
*/
import { defineStore } from "pinia";
import { Duration } from "@/types/app";
import { Service } from "@/types/selector";
import { Endpoint } from "@/types/selector";
import {
TaskListItem,
SegmentSpan,
@ -31,7 +31,8 @@ import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface ProfileState {
services: Service[];
endpoints: Endpoint[];
taskEndpoints: Endpoint[];
durationTime: Duration;
condition: { serviceId: string; endpointName: string };
taskList: TaskListItem[];
@ -47,7 +48,8 @@ interface ProfileState {
export const profileStore = defineStore({
id: "profile",
state: (): ProfileState => ({
services: [{ value: "0", label: "All" }],
endpoints: [{ value: "", label: "All" }],
taskEndpoints: [{ value: "", label: "All" }],
durationTime: useAppStoreWithOut().durationTime,
condition: { serviceId: "", endpointName: "" },
taskList: [],
@ -75,14 +77,28 @@ export const profileStore = defineStore({
setHighlightTop() {
this.highlightTop = !this.highlightTop;
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
async getEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
return res.data;
}
this.services = res.data.data.services;
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
return res.data;
}
this.taskEndpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getTaskList() {

View File

@ -95,10 +95,7 @@ export const traceStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.instances = [
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: " 0", label: "All" }];
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getEndpoints(id: string, keyword?: string) {
@ -113,10 +110,7 @@ export const traceStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.endpoints = [
{ value: "0", label: "All" },
...res.data.data.pods,
] || [{ value: "0", label: "All" }];
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getTraces() {

View File

@ -14,6 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "./grid.scss";
@import "./lib.scss";
@import "./reset.scss";
import "element-plus/es/components/message/style/css";
import "element-plus/es/components/message-box/style/css";
import "element-plus/es/components/notification/style/css";
import "./grid.scss";
import "./lib.scss";
import "./reset.scss";

76
src/types/ebpf.d.ts vendored Normal file
View File

@ -0,0 +1,76 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface EBPFTaskCreationRequest {
serviceId: string;
processLabels: string[];
startTime: number;
duration: number;
targetType: string;
}
export interface EBPFTaskList {
taskId: string;
serviceName: string;
serviceId: string;
processLabels: string[];
taskStartTime: number;
fixedTriggerDuration: number;
targetType: string;
createTime: number;
triggerType: string;
}
export interface EBPFProfilingSchedule {
scheduleId: string;
taskId: string;
process: Process;
endTime: number;
startTime: number;
}
export type Process = {
id: string;
name: string;
serviceId: string;
serviceName: string;
instanceId: string;
instanceName: string;
layer: string;
agentId: string;
detectType: string;
attributes: { name: string; value: string };
labels: string[];
};
export type StackElement = {
id: string;
originId: string;
name: string;
parentId: string;
symbol: string;
dumpCount: number;
stackType: string;
value: number;
children?: StackElement[];
};
export type AnalyzationTrees = {
id: string;
parentId: string;
symbol: string;
dumpCount: number;
stackType: string;
};

View File

@ -44,7 +44,7 @@ limitations under the License. -->
:style="{ fontSize: '13px', width: '100%' }"
v-loading="loading"
ref="multipleTableRef"
:default-sort="{ prop: 'name' }"
:default-sort="{ prop: 'name', order: 'ascending' }"
@selection-change="handleSelectionChange"
height="637px"
size="small"

View File

@ -0,0 +1,95 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="profile-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Header />
<Content />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/ebpf/Header.vue";
import Content from "../related/ebpf/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.trace {
width: 100%;
overflow: auto;
}
</style>

View File

@ -97,7 +97,7 @@ limitations under the License. -->
:key="item.i"
@click="clickTabGrid($event, item)"
:class="{ active: activeTabWidget === item.i }"
drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart"
:drag-ignore-from="dragIgnoreFrom"
>
<component
:is="item.type"
@ -122,6 +122,8 @@ import Trace from "./Trace.vue";
import Profile from "./Profile.vue";
import Log from "./Log.vue";
import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
import { dragIgnoreFrom } from "../data";
const props = {
data: {
@ -132,7 +134,7 @@ const props = {
};
export default defineComponent({
name: "Tab",
components: { Topology, Widget, Trace, Profile, Log, Text },
components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf },
props,
setup(props) {
const { t } = useI18n();
@ -248,6 +250,7 @@ export default defineComponent({
canEditTabName,
showTools,
t,
dragIgnoreFrom,
};
},
});

View File

@ -21,5 +21,6 @@ import Trace from "./Trace.vue";
import Profile from "./Profile.vue";
import Log from "./Log.vue";
import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
export default { Tab, Widget, Trace, Topology, Profile, Log, Text };
export default { Tab, Widget, Trace, Topology, Profile, Log, Text, Ebpf };

View File

@ -14,6 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const dragIgnoreFrom =
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules";
export const PodsChartTypes = ["EndpointList", "InstanceList"];
@ -181,7 +183,8 @@ export const ServiceTools = [
{ name: "library_books", content: "Text", id: "addText" },
{ name: "device_hub", content: "Topology", id: "addTopology" },
{ name: "merge", content: "Trace", id: "addTrace" },
{ name: "timeline", content: "Profile", id: "addProfile" },
{ name: "timeline", content: "Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Log", id: "addLog" },
];
export const InstanceTools = [

View File

@ -31,7 +31,7 @@ limitations under the License. -->
:key="item.i"
@click="clickGrid(item)"
:class="{ active: dashboardStore.activedGridItem === item.i }"
drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart"
:drag-ignore-from="dragIgnoreFrom"
>
<component :is="item.type" :data="item" />
</grid-item>
@ -45,6 +45,7 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { LayoutConfig } from "@/types/dashboard";
import controls from "../controls/index";
import { dragIgnoreFrom } from "../data";
export default defineComponent({
name: "Layout",
@ -72,6 +73,7 @@ export default defineComponent({
dashboardStore,
clickGrid,
t,
dragIgnoreFrom,
};
},
});

View File

@ -258,7 +258,7 @@ async function setSourceSelector() {
let currentPod;
if (states.currentPod) {
currentPod = selectorStore.pods.find(
(d: { id: string }) => d.label === states.currentPod
(d: { label: string }) => d.label === states.currentPod
);
} else {
currentPod = selectorStore.pods.find((d: { id: string }) => d.id === pod);
@ -283,10 +283,10 @@ async function setDestSelector() {
return;
}
const destPod = params.destPodId || selectorStore.destPods[0].id;
let currentDestPod = "";
let currentDestPod = { label: "" };
if (states.currentDestPod) {
currentDestPod = selectorStore.pods.find(
(d: { id: string }) => d.label === states.currentDestPod
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
currentDestPod = selectorStore.destPods.find(
@ -317,19 +317,23 @@ async function getServices() {
let s;
if (states.currentService) {
s = (selectorStore.services || []).find(
(d) => d.label === states.currentService
(d: { label: string }) => d.label === states.currentService
);
} else {
s = (selectorStore.services || []).find((d, index) => index === 0);
s = (selectorStore.services || []).find(
(d: unknown, index: number) => index === 0
);
}
selectorStore.setCurrentService(s || null);
let d;
if (states.currentService) {
d = (selectorStore.services || []).find(
(d) => d.label === states.currentDestService
(d: { label: string }) => d.label === states.currentDestService
);
} else {
d = (selectorStore.services || []).find((d, index) => index === 1);
d = (selectorStore.services || []).find(
(d: unknown, index: number) => index === 1
);
}
selectorStore.setCurrentDestService(d || null);
if (!selectorStore.currentService) {
@ -431,6 +435,9 @@ function setTabControls(id: string) {
case "addProfile":
dashboardStore.addTabControls("Profile");
break;
case "addEbpf":
dashboardStore.addTabControls("Ebpf");
break;
case "addTopology":
dashboardStore.addTabControls("Topology");
break;
@ -457,6 +464,9 @@ function setControls(id: string) {
case "addProfile":
dashboardStore.addControl("Profile");
break;
case "addEbpf":
dashboardStore.addControl("Ebpf");
break;
case "addLog":
dashboardStore.addControl("Log");
break;
@ -484,9 +494,13 @@ async function fetchPods(
if (setPod) {
let p;
if (states.currentPod) {
p = selectorStore.pods.find((d) => d.label === states.currentPod);
p = selectorStore.pods.find(
(d: { label: unknown }) => d.label === states.currentPod
);
} else {
p = selectorStore.pods.find((d, index) => index === 0);
p = selectorStore.pods.find(
(d: unknown, index: number) => index === 0
);
}
selectorStore.setCurrentPod(p || null);
states.currentPod = selectorStore.currentPod.label;
@ -497,9 +511,13 @@ async function fetchPods(
if (setPod) {
let p;
if (states.currentPod) {
p = selectorStore.pods.find((d) => d.label === states.currentPod);
p = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentPod
);
} else {
p = selectorStore.pods.find((d, index) => index === 0);
p = selectorStore.pods.find(
(d: { label: string }, index: number) => index === 0
);
}
selectorStore.setCurrentPod(p || null);
states.currentPod = selectorStore.currentPod.label;
@ -514,9 +532,13 @@ async function fetchPods(
if (setPod) {
let p;
if (states.currentDestPod) {
p = selectorStore.pods.find((d) => d.label === states.currentDestPod);
p = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
p = selectorStore.pods.find((d, index) => index === 0);
p = selectorStore.pods.find(
(d: { label: string }, index: number) => index === 0
);
}
selectorStore.setCurrentDestPod(p || null);
states.currentDestPod = selectorStore.currentDestPod.label;
@ -530,9 +552,13 @@ async function fetchPods(
if (setPod) {
let p;
if (states.currentDestPod) {
p = selectorStore.pods.find((d) => d.label === states.currentDestPod);
p = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
p = selectorStore.pods.find((d, index) => index === 0);
p = selectorStore.pods.find(
(d: { label: string }, index: number) => index === 0
);
}
selectorStore.setCurrentDestPod(p || null);
states.currentDestPod = selectorStore.currentDestPod.label;

View File

@ -0,0 +1,50 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="flex-h content">
<TaskList />
<div class="vis-graph ml-5">
<div class="item">
<EBPFSchedules />
</div>
<div class="item">
<EBPFStack />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import TaskList from "./components/TaskList.vue";
import EBPFSchedules from "./components/EBPFSchedules.vue";
import EBPFStack from "./components/EBPFStack.vue";
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 30px);
width: 100%;
}
.vis-graph {
height: 100%;
width: calc(100% - 300px);
}
.item {
width: 100%;
overflow: auto;
height: calc(50% - 10px);
padding-bottom: 10px;
}
</style>

View File

@ -0,0 +1,104 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="flex-h header">
<div class="title">eBPF Profiling</div>
<el-button type="primary" size="small" @click="createTask">
{{ t("newTask") }}
</el-button>
</div>
<el-dialog
v-model="newTask"
:destroy-on-close="true"
fullscreen
@closed="newTask = false"
>
<NewTask @close="newTask = false" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import NewTask from "./components/NewTask.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
const ebpfStore = useEbpfStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
const newTask = ref<boolean>(false);
searchTasks();
async function searchTasks() {
const serviceId =
(selectorStore.currentService && selectorStore.currentService.id) || "";
const res = await ebpfStore.getTaskList(serviceId);
if (res.errors) {
ElMessage.error(res.errors);
}
}
async function createTask() {
if (!selectorStore.currentService) {
return;
}
newTask.value = true;
ebpfStore.getCreateTaskData(selectorStore.currentService.id);
}
watch(
() => selectorStore.currentService,
() => {
searchTasks();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
}
}
);
</script>
<style lang="scss" scoped>
.header {
padding: 5px 20px 5px 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
justify-content: space-between;
}
.name {
width: 270px;
}
.new-btn {
float: right;
}
.title {
font-weight: bold;
line-height: 24px;
}
</style>

View File

@ -0,0 +1,244 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="filters">
<Selector
:value="selectedLabels"
:options="labels"
size="small"
placeholder="Please select labels"
@change="changeLabels"
class="inputs mr-10"
:multiple="true"
/>
<el-popover placement="bottom" :width="680" trigger="click">
<template #reference>
<el-button type="primary" size="small">
{{ t("processSelect") }}
</el-button>
</template>
<el-input
v-model="searchText"
placeholder="Please input name"
class="input-with-search"
size="small"
@change="searchProcesses"
>
<template #append>
<el-button size="small">
<Icon size="sm" iconName="search" />
</el-button>
</template>
</el-input>
<el-table
:data="currentProcesses"
ref="multipleTableRef"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column
v-for="(h, index) of TableHeader"
:property="h.property"
:label="h.label"
:key="index"
width="150"
/>
<el-table-column width="300" label="Attributes">
<template #default="scope">
{{ scope.row.attributes.map((d: {name: string, value: string}) => `${d.name}=${d.value}`).join("; ") }}
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
background
small
layout="prev, pager, next"
:page-size="pageSize"
:total="processes.length"
@current-change="changePage"
@prev-click="changePage"
@next-click="changePage"
/>
</el-popover>
<el-button type="primary" size="small" @click="analyzeEBPF">
{{ t("analyze") }}
</el-button>
</div>
<div ref="timeline" class="schedules"></div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { TableHeader } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import { ElMessage, ElTable } from "element-plus";
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const pageSize = 5;
/*global Nullable */
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]);
const timeline = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const labels = ref<Option[]>([{ label: "All", value: "0" }]);
const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>("");
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
new Date(dayjs(date).format(pattern));
function changeLabels(opt: any[]) {
const arr = opt.map((d) => d.value);
selectedLabels.value = arr;
}
const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id);
};
async function analyzeEBPF() {
let arr: string[] = selectedLabels.value;
if (selectedLabels.value.includes("0")) {
arr = labels.value.map((d: Option) => d.value);
}
const ranges: { start: number; end: number }[] = [];
const scheduleIdList = ebpfStore.eBPFSchedules.flatMap(
(d: EBPFProfilingSchedule) => {
const l = d.process.labels.find((d: string) => arr.includes(d));
const i = selectedProcesses.value.includes(d.process.id);
if (l || i) {
ranges.push({
start: d.startTime,
end: d.endTime,
});
return d.scheduleId;
}
}
);
let timeRanges: { start: number; end: number }[] = [];
for (const r of ranges) {
if (timeRanges.length) {
for (const t of timeRanges) {
if (r.start > t.start && r.start < t.end) {
if (r.end > t.end) {
t.end = r.end;
}
}
}
} else {
timeRanges.push(r);
}
}
const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList,
timeRanges,
});
if (res.data.errors) {
ElMessage.error(res.data.errors);
return;
}
}
function visTimeline() {
if (visGraph.value) {
visGraph.value.destroy();
}
labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"];
processes.value = [];
const schedules = ebpfStore.eBPFSchedules.map(
(d: EBPFProfilingSchedule, index: number) => {
for (const l of d.process.labels) {
labels.value.push({ label: l, value: l });
}
processes.value.push(d.process);
return {
id: index + 1,
content: d.process.name,
start: dateFormat(d.startTime),
end: dateFormat(d.endTime),
};
}
);
searchProcesses();
if (!timeline.value) {
return;
}
const h = timeline.value.getBoundingClientRect().height;
const items: any = new DataSet(schedules);
const options = {
height: h,
width: "100%",
locale: "en",
};
visGraph.value = new Timeline(timeline.value, items, options);
}
function changePage(pageIndex: number) {
searchProcesses(pageIndex);
}
function searchProcesses(pageIndex?: any) {
const arr = processes.value.filter(
(d: { name: string; instanceName: string }) =>
d.name.includes(searchText.value) ||
d.instanceName.includes(searchText.value)
);
currentProcesses.value = arr.splice(
(pageIndex - 1 || 0) * pageSize,
pageSize * (pageIndex || 1)
);
}
watch(
() => ebpfStore.eBPFSchedules,
() => {
visTimeline();
}
);
</script>
<style lang="scss" scoped>
.filters {
margin: 5px 0;
}
.schedules {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: calc(100% - 60px);
min-height: 150px;
}
.inputs {
width: 300px;
}
.input-with-search {
width: 650px;
margin-bottom: 5px;
}
.pagination {
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,148 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div id="graph-stack" ref="graph">
<span class="tip" v-show="ebpfStore.tip">{{ ebpfStore.tip }}</span>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import * as d3 from "d3";
import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf";
import { StackElement } from "@/types/ebpf";
import "d3-flame-graph/dist/d3-flamegraph.css";
/*global Nullable*/
const ebpfStore = useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null);
const flameChart = ref<any>(null);
function drawGraph() {
if (flameChart.value) {
flameChart.value.destroy();
}
if (!ebpfStore.analyzeTrees.length) {
return (stackTree.value = null);
}
stackTree.value = processTree(ebpfStore.analyzeTrees);
const w = (graph.value && graph.value.getBoundingClientRect().width) || 10;
flameChart.value = flamegraph()
.width(w - 15)
.cellHeight(18)
.transitionDuration(750)
.minFrameSize(5)
.transitionEase(d3.easeCubic as any)
.sort(true)
.title("")
.selfValue(false)
.setColorMapper((d, originalColor) =>
d.highlight ? "#6aff8f" : originalColor
);
const tip = (d3tip as any)()
.attr("class", "d3-tip")
.direction("w")
.html(
(d: { data: StackElement }) =>
`<div class="mb-5">Symbol: ${d.data.name}</div><div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
);
flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
}
function processTree(arr: StackElement[]) {
const copyArr = JSON.parse(JSON.stringify(arr));
const obj: any = {};
let res = null;
let min = 1;
let max = 1;
for (const item of copyArr) {
item.originId = item.id;
item.name = item.symbol;
delete item.id;
obj[item.originId] = item;
if (item.dumpCount > max) {
max = item.dumpCount;
}
if (item.dumpCount < min) {
min = item.dumpCount;
}
}
const scale = d3.scaleLinear().domain([min, max]).range([1, 200]);
for (const item of copyArr) {
if (item.parentId === "0") {
const val = Number(scale(item.dumpCount).toFixed(4));
res = item;
res.value = val;
}
for (const key in obj) {
if (item.originId === obj[key].parentId) {
const val = Number(scale(obj[key].dumpCount).toFixed(4));
obj[key].value = val;
if (item.children) {
item.children.push(obj[key]);
} else {
item.children = [obj[key]];
}
}
}
}
treeForeach([res], (node: StackElement) => {
if (node.children) {
let val = 0;
for (const child of node.children) {
val = child.value + val;
}
node.value = node.value < val ? val : node.value;
}
});
return res;
}
function treeForeach(tree: StackElement[], func: (node: StackElement) => void) {
for (const data of tree) {
data.children && treeForeach(data.children, func);
func(data);
}
return tree;
}
watch(
() => ebpfStore.analyzeTrees,
() => {
drawGraph();
}
);
</script>
<style>
#graph-stack {
width: 100%;
height: 100%;
cursor: pointer;
}
.tip {
display: inline-block;
width: 100%;
text-align: center;
color: red;
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,165 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="ebpf-task" v-if="eBPFStore.couldProfiling">
<div>
<div class="label">{{ t("labels") }}</div>
<Selector
class="profile-input"
size="small"
:value="labels"
:options="eBPFStore.labels"
placeholder="Select labels"
:multiple="true"
@change="changeLabel"
/>
</div>
<div>
<div class="label">{{ t("targetType") }}</div>
<Selector
class="profile-input"
size="small"
:value="type"
:options="TargetTypes"
placeholder="Select a type"
:isRemote="true"
@change="changeType"
/>
</div>
<div>
<div class="label">{{ t("monitorTime") }}</div>
<div>
<Radio
:value="monitorTime"
:options="InitTaskField.monitorTimeEn"
@change="changeMonitorTime"
/>
<span class="date">
<TimePicker
:value="time"
position="bottom"
format="YYYY-MM-DD HH:mm:ss"
@input="changeTimeRange"
/>
</span>
</div>
</div>
<div>
<div class="label">{{ t("monitorDuration") }}</div>
<el-input
class="profile-input"
v-model="monitorDuration"
size="small"
placeholder="none"
type="number"
:min="1"
:max="60"
/>
Min
</div>
<div>
<el-button @click="createTask" type="primary" class="create-task-btn">
{{ t("createTask") }}
</el-button>
</div>
</div>
<div v-else>{{ t("ebpfTip") }}</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import { InitTaskField, TargetTypes } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const eBPFStore = useEbpfStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const labels = ref<string[]>([]);
const type = ref<string>(TargetTypes[0].value);
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<number>(10);
const time = ref<Date>(appStore.durationRow.start);
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
function changeLabel(opt: any[]) {
labels.value = opt.map((d) => d.value);
}
function changeType(opt: any[]) {
type.value = opt[0].value;
}
async function createTask() {
if (!labels.value.length) {
ElMessage.warning("no labels");
return;
}
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
processLabels: labels.value,
startTime: date.getTime(),
duration: monitorDuration.value * 60,
targetType: "ON_CPU",
};
const res = await eBPFStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
if (!res.data.createTaskData.status) {
ElMessage.error(res.data.createTaskData.errorReason);
return;
}
ElMessage.success("Task created successfully");
emits("close");
}
function changeTimeRange(val: Date) {
time.value = val;
}
</script>
<style lang="scss" scoped>
.ebpf-task {
margin: 0 auto;
width: 400px;
}
.date {
font-size: 12px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.profile-input {
width: 300px;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
</style>

View File

@ -0,0 +1,195 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="profile-task-list flex-v">
<div class="profile-task-wrapper flex-v">
<div class="profile-t-tool flex-h">{{ t("taskList") }}</div>
<div class="profile-t-wrapper">
<div class="no-data" v-show="!ebpfStore.taskList.length">
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in ebpfStore.taskList"
@click="changeTask(i)"
:key="index"
>
<td
class="profile-td"
:class="{
selected: selectedTask.taskId === i.taskId,
}"
>
<div class="ell">
<span>{{ i.processLabels.join(" ") }}</span>
<a class="profile-btn r" @click="viewDetail = true">
<Icon iconName="view" size="middle" />
</a>
</div>
<div class="grey ell sm">
<span class="mr-10 sm">{{ dateFormat(i.taskStartTime) }}</span>
<span class="mr-10 sm">
{{
dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000)
}}
</span>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<el-dialog
v-model="viewDetail"
:destroy-on-close="true"
fullscreen
@closed="viewDetail = false"
>
<div class="profile-detail flex-v">
<div>
<h5 class="mb-10">{{ t("task") }}.</h5>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("taskId") }}:</span>
<span class="g-sm-8 wba">
{{ selectedTask.taskId }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("service") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.serviceName }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("labels") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.processLabels }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
<span class="g-sm-8 wba">
{{ dateFormat(selectedTask.taskStartTime) }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorDuration") }}:</span>
<span class="g-sm-8 wba">
{{ selectedTask.fixedTriggerDuration / 60 }} min
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("triggerType") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.triggerType }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("targetType") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.targetType }}</span>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFTaskList } from "@/types/ebpf";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const selectedTask = ref<EBPFTaskList | Record<string, never>>({});
const viewDetail = ref<boolean>(false);
async function changeTask(item: EBPFTaskList) {
selectedTask.value = item;
const res = await ebpfStore.getEBPFSchedules({
taskId: item.taskId,
});
if (res.errors) {
ElMessage.error(res.errors);
}
}
watch(
() => ebpfStore.taskList,
() => {
selectedTask.value = ebpfStore.taskList[0] || {};
}
);
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 300px;
height: calc(100% - 10px);
overflow: auto;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.item span {
height: 21px;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
}
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
border: none;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
float: right;
}
</style>

View File

@ -0,0 +1,51 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const ProfileMode: any[] = [
{ label: "Include Children", value: "include" },
{ label: "Exclude Children", value: "exclude" },
];
export const NewTaskField = {
service: { key: "", label: "None" },
monitorTime: { key: "0", label: "monitor now" },
monitorDuration: { key: 5, label: "5 min" },
minThreshold: 0,
dumpPeriod: { key: 10, label: "10ms" },
endpointName: "",
maxSamplingCount: { key: 5, label: "5" },
};
export const TargetTypes = [{ label: "ON_CPU", value: "ON_CPU" }];
export const InitTaskField = {
monitorTimeEn: [
{ value: "0", label: "monitor now" },
{ value: "1", label: "set start time" },
],
monitorTimeCn: [
{ value: "0", label: "此刻" },
{ value: "1", label: "设置时间" },
],
monitorDuration: [
{ value: "5", label: "5 min" },
{ value: "10", label: "10 min" },
{ value: "15", label: "15 min" },
],
};
export const TableHeader = [
{ property: "name", label: "Name" },
{ property: "instanceName", label: "Instance Name" },
];

View File

@ -14,19 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="flex-h header">
<!-- <div class="mr-10" v-if="dashboardStore.entity==='All'">
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
:value="service.value"
:options="profileStore.services"
placeholder="Select a service"
@change="changeService"
/>
</div> -->
<div class="mr-10">
<span class="grey mr-5">{{ t("endpointName") }}:</span>
<el-input v-model="endpointName" class="name" size="small" />
<Selector
class="name"
size="small"
:value="endpointName"
:options="profileStore.endpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeEndpoint"
@query="searchEndpoints"
/>
</div>
<el-button
class="search-btn"
@ -65,27 +64,30 @@ const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
// const service = ref<any>({});
const endpointName = ref<string>("");
const newTask = ref<boolean>(false);
searchTasks();
// getServices();
searchEndpoints("");
// async function getServices() {
// const res = await profileStore.getServices(dashboardStore.layerId);
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
// if (res.errors) {
// ElMessage.error(res.errors);
// return;
// }
// service.value = profileStore.services[0];
// searchTasks();
// }
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = profileStore.endpoints[0].value;
}
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
// function changeService(opt: any[]) {
// service.value = opt[0];
// }
async function searchTasks() {
profileStore.setConditions({
serviceId:

View File

@ -17,7 +17,16 @@ limitations under the License. -->
<div class="profile-task">
<div>
<div class="label">{{ t("endpointName") }}</div>
<el-input v-model="endpointName" class="profile-input" size="small" />
<Selector
class="profile-input"
size="small"
:value="endpointName"
:options="profileStore.endpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeEndpoint"
@query="searchEndpoints"
/>
</div>
<div>
<div class="label">{{ t("monitorTime") }}</div>
@ -105,6 +114,20 @@ const minThreshold = ref<number>(0);
const dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value);
const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value);
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.id;
const res = await profileStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = profileStore.taskEndpoints[0].value;
}
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
@ -121,10 +144,13 @@ function changeMaxSamplingCount(opt: any[]) {
maxSamplingCount.value = opt[0].value;
}
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function createTask() {
emits("close");
const date =
monitorTime.value === "0" ? appStore.durationRow.start : time.value;
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
endpointName: endpointName.value,
@ -153,7 +179,7 @@ function changeTimeRange(val: Date) {
<style lang="scss" scoped>
.profile-task {
margin: 0 auto;
width: 350px;
width: 400px;
}
.date {

View File

@ -36,7 +36,7 @@ limitations under the License. -->
<div class="ell">
<span>{{ i.endpointName }}</span>
<a class="profile-btn r" @click="viewTask($event, i)">
<Icon iconName="library_books" size="middle" />
<Icon iconName="view" size="middle" />
</a>
</div>
<div class="grey ell sm">

View File

@ -88,7 +88,7 @@ limitations under the License. -->
</div>
<div>
<el-button
class="grey"
class="grey small"
:class="{ ghost: displayMode !== 'List' }"
@click="displayMode = 'List'"
>
@ -96,7 +96,7 @@ limitations under the License. -->
{{ t("list") }}
</el-button>
<el-button
class="grey"
class="grey small"
:class="{ ghost: displayMode !== 'Tree' }"
@click="displayMode = 'Tree'"
>
@ -104,7 +104,7 @@ limitations under the License. -->
{{ t("tree") }}
</el-button>
<el-button
class="grey"
class="grey small"
:class="{ ghost: displayMode !== 'Table' }"
@click="displayMode = 'Table'"
>
@ -112,7 +112,7 @@ limitations under the License. -->
{{ t("table") }}
</el-button>
<el-button
class="grey"
class="grey small"
:class="{ ghost: displayMode !== 'Statistics' }"
@click="displayMode = 'Statistics'"
>