├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── BUG_TEMPLATE.md │ └── FEATURE_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── add-untriaged.yml │ ├── cd.yml │ ├── dco.yml │ ├── gradle.yml │ └── links.yml ├── .gitignore ├── .npmignore ├── .whitesource ├── ADMINS.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPER_GUIDE.md ├── LICENSE ├── MAINTAINERS.md ├── NOTICE ├── README.md ├── RELEASING.md ├── SECURITY.md ├── THIRD-PARTY-LICENSES.txt ├── bin └── global.js ├── build.gradle ├── docs └── DesignDoc.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── images ├── ArchitectureDiagram.png ├── ClusterNetworkMemoryAnalysis.png ├── ClusterOverview.png ├── ClusterThreadAnalysis.png └── NodeAnalysis.png ├── lib ├── bin.js ├── dashboards │ ├── metrics │ │ ├── ClusterNetworkMemoryAnalysis.json │ │ ├── ClusterOverview.json │ │ ├── ClusterThreadAnalysis.json │ │ └── NodeAnalysis.json │ └── rca │ │ └── TemperatureAnalysis.json ├── env.js └── perf-top │ ├── helper.js │ ├── metric-graphs.js │ ├── metrics │ ├── generate-graphs.js │ └── util │ │ ├── generate-data.js │ │ ├── metric-bar.js │ │ ├── metric-line.js │ │ ├── metric-table.js │ │ └── validate-query-params.js │ └── rca │ ├── generate-graphs.js │ └── util │ ├── metric-donut.js │ ├── metric-line.js │ ├── metric-table.js │ └── temperature-profile │ └── generate-data.js ├── package-lock.json ├── package.json └── release-notes ├── opendistro-for-elasticsearch-perftop.release-notes-1.10.0.0.md ├── opendistro-for-elasticsearch-perftop.release-notes-1.10.1.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-0.7.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-0.8.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-0.9.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.0.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.1.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.11.0.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.12.0.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.13.0.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.2.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.2.1.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.3.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.4.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.6.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.7.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.8.0.md ├── opendistro-for-elasticsearch.perftop.release-notes-1.9.0.md ├── opensearch-perftop.release-notes-1.0.0.0-beta1.md ├── opensearch-perftop.release-notes-1.0.0.0-rc1.md ├── opensearch-perftop.release-notes-1.0.0.0.md ├── opensearch-perftop.release-notes-1.1.0.0.md ├── opensearch-perftop.release-notes-1.2.0.0.md ├── opensearch-perftop.release-notes-1.2.4.0.md └── opensearch-perftop.release-notes-1.3.0.0.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 6 9 | }, 10 | "rules": { 11 | "brace-style": "error", 12 | "indent": ["error", 2], 13 | "no-console": "off", 14 | "no-irregular-whitespace":"error", 15 | "no-octal-escape": "error", 16 | "no-undef":"error", 17 | "no-use-before-define": "off", 18 | "semi": ["error", "always"], 19 | "semi-style": ["error", "last"] 20 | } 21 | } -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @kkhatua @sruti1312 @yujias0706 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Create a report to help us improve 4 | title: '[BUG]' 5 | labels: 'bug, untriaged' 6 | assignees: '' 7 | --- 8 | 9 | **What is the bug?** 10 | A clear and concise description of the bug. 11 | 12 | **How can one reproduce the bug?** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **What is the expected behavior?** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **What is your host/environment?** 23 | - OS: [e.g. iOS] 24 | - Version [e.g. 22] 25 | - Plugins 26 | 27 | **Do you have any screenshots?** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Do you have any additional context?** 31 | Add any other context about the problem. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎆 Feature request 3 | about: Request a feature in this project 4 | title: '[FEATURE]' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | **Is your feature request related to a problem?** 9 | A clear and concise description of what the problem is, e.g. _I'm always frustrated when [...]_ 10 | 11 | **What solution would you like?** 12 | A clear and concise description of what you want to happen. 13 | 14 | **What alternatives have you considered?** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Do you have any additional context?** 18 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Is your feature request related to a problem? Please provide an existing Issue # , or describe.** 2 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 3 | 4 | **Describe the solution you are proposing** 5 | A clear and concise description of what you want to happen. 6 | 7 | **Describe alternatives you've considered** 8 | A clear and concise description of any alternative solutions or features you've considered. 9 | 10 | **Additional context** 11 | Add any other context or screenshots about the feature request here. 12 | 13 | ### Check List 14 | - [ ] New functionality includes testing. 15 | - [ ] All tests pass 16 | - [ ] New functionality has been documented. 17 | - [ ] New functionality has javadoc added 18 | - [ ] Commits are signed per the DCO using --signoff 19 | 20 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 21 | For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/performance-analyzer/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). 22 | -------------------------------------------------------------------------------- /.github/workflows/add-untriaged.yml: -------------------------------------------------------------------------------- 1 | name: Apply 'untriaged' label during issue lifecycle 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened, transferred] 6 | 7 | jobs: 8 | apply-label: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/github-script@v6 12 | with: 13 | script: | 14 | github.rest.issues.addLabels({ 15 | issue_number: context.issue.number, 16 | owner: context.repo.owner, 17 | repo: context.repo.repo, 18 | labels: ['untriaged'] 19 | }) 20 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | 14 | - name: Set up JDK 14 15 | uses: actions/setup-java@v1 16 | with: 17 | java-version: 14.0.x 18 | 19 | - name: Set up Node JS 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 10.15.0 23 | 24 | - name: Checkout perftop package 25 | uses: actions/checkout@v2 26 | 27 | - name: Build 28 | run: | 29 | ./gradlew clean 30 | ./gradlew build -Dbuild.snapshot=false -Dbuild.linux=true 31 | ./gradlew build -Dbuild.snapshot=false -Dbuild.macos=true 32 | mkdir artifacts 33 | macos_artifact=`ls build/distributions/*macos*.zip` 34 | linux_artifact=`ls build/distributions/*linux*.zip` 35 | cp $macos_artifact artifacts/ 36 | cp $linux_artifact artifacts/ 37 | 38 | - name: Configure AWS Credentials 39 | uses: aws-actions/configure-aws-credentials@v1 40 | with: 41 | aws-access-key-id: ${{ secrets.AWS_STAGING_ACCESS_KEY_ID }} 42 | aws-secret-access-key: ${{ secrets.AWS_STAGING_SECRET_ACCESS_KEY }} 43 | aws-region: us-west-2 44 | 45 | - name: Upload Artifacts to S3 46 | run: | 47 | macos=`ls artifacts/*macos*.zip` 48 | linux=`ls artifacts/*linux*.zip` 49 | 50 | # Inject the build number before the suffix 51 | macos_outfile=`basename ${macos%.zip}-build-${GITHUB_RUN_NUMBER}.zip` 52 | linux_outfile=`basename ${linux%.zip}-build-${GITHUB_RUN_NUMBER}.zip` 53 | 54 | // TODO: replace with opensearch s3 artifact 55 | s3_prefix="s3://staging.artifacts.opensearch.amazon.com/snapshots/opensearch-clients/perftop/" 56 | 57 | echo "Copying ${macos} to ${s3_prefix}${macos_outfile}" 58 | aws s3 cp --quiet $macos ${s3_prefix}${macos_outfile} 59 | 60 | echo "Copying ${linux} to ${s3_prefix}${linux_outfile}" 61 | aws s3 cp --quiet $linux ${s3_prefix}${linux_outfile} 62 | 63 | - name: Upload Workflow Artifacts 64 | uses: actions/upload-artifact@v1 65 | with: 66 | name: artifacts 67 | path: artifacts/ 68 | -------------------------------------------------------------------------------- /.github/workflows/dco.yml: -------------------------------------------------------------------------------- 1 | name: Developer Certificate of Origin Check 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | check: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Get PR Commits 11 | id: 'get-pr-commits' 12 | uses: tim-actions/get-pr-commits@v1.1.0 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | - name: DCO Check 16 | uses: tim-actions/dco@v1.1.0 17 | with: 18 | commits: ${{ steps.get-pr-commits.outputs.commits }} 19 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | 8 | pull_request: 9 | branches: 10 | - "*" 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Set up JDK 12 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 12.0.x 21 | 22 | - name: Set up Node JS 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 10.15.0 26 | 27 | - name: Checkout perftop package 28 | uses: actions/checkout@v2 29 | 30 | # TODO: Add tests and run in build 31 | - name: Build perfTop artifacts 32 | run: | 33 | ./gradlew clean 34 | ./gradlew build -Dbuild.snapshot=false -Dbuild.linux=true 35 | ./gradlew build -Dbuild.snapshot=false -Dbuild.macos=true 36 | -------------------------------------------------------------------------------- /.github/workflows/links.yml: -------------------------------------------------------------------------------- 1 | name: Link Checker 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Lychee Link Checker 11 | uses: lycheeverse/lychee-action@v1.2.0 12 | with: 13 | args: --accept=200,403,429 **/*.html **/*.md **/*.txt **/*.json 14 | fail: true 15 | env: 16 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | /.idea/ 4 | /.gradle/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .gitignore 3 | .gradle 4 | gradle 5 | images 6 | docs 7 | CODE_OF_CONDUCT.md 8 | CONTRIBUTING.md 9 | CONTRIBUTORS.md 10 | LICENSE 11 | NOTICE 12 | THIRD-PARTY-LICENSES.txt 13 | build.gradle 14 | gradlew 15 | release-notes 16 | package-lock.json 17 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "configMode": "AUTO", 4 | "configExternalURL": "", 5 | "projectToken": "", 6 | "baseBranches": [] 7 | }, 8 | "checkRunSettings": { 9 | "vulnerableCheckRunConclusionLevel": "failure", 10 | "displayMode": "diff" 11 | }, 12 | "issueSettings": { 13 | "minSeverityLevel": "LOW" 14 | }, 15 | "remediateSettings": { 16 | "workflowRules": { 17 | "enabled": true 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /ADMINS.md: -------------------------------------------------------------------------------- 1 | ## Admins 2 | 3 | | Admin | GitHub ID | Affiliation | 4 | | --------------- | --------------------------------------- | ----------- | 5 | | Charlotte | [CEHENKLE](https://github.com/CEHENKLE) | Amazon | 6 | | Henri Yandell | [hyandell](https://github.com/hyandell) | Amazon | 7 | 8 | [This document](https://github.com/opensearch-project/.github/blob/main/ADMINS.md) explains what admins do in this repo. and how they should be doing it. If you're interested in becoming a maintainer, see [MAINTAINERS](MAINTAINERS.md). If you're interested in contributing, see [CONTRIBUTING](CONTRIBUTING.md). -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This code of conduct applies to all spaces provided by the OpenSource project including in code, documentation, issue trackers, mailing lists, chat channels, wikis, blogs, social media and any other communication channels used by the project. 3 | 4 | **Our open source communities endeavor to:** 5 | 6 | * Be Inclusive: We are committed to being a community where everyone can join and contribute. This means using inclusive and welcoming language. 7 | * Be Welcoming: We are committed to maintaining a safe space for everyone to be able to contribute. 8 | * Be Respectful: We are committed to encouraging differing viewpoints, accepting constructive criticism and work collaboratively towards decisions that help the project grow. Disrespectful and unacceptable behavior will not be tolerated. 9 | * Be Collaborative: We are committed to supporting what is best for our community and users. When we build anything for the benefit of the project, we should document the work we do and communicate to others on how this affects their work. 10 | 11 | 12 | **Our Responsibility. As contributors, members, or bystanders we each individually have the responsibility to behave professionally and respectfully at all times. Disrespectful and unacceptable behaviors include, but are not limited to:** 13 | 14 | * The use of violent threats, abusive, discriminatory, or derogatory language; 15 | * Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, race, political or religious affiliation; 16 | * Posting of sexually explicit or violent content; 17 | * The use of sexualized language and unwelcome sexual attention or advances; 18 | * Public or private harassment of any kind; 19 | * Publishing private information, such as physical or electronic address, without permission; 20 | * Other conduct which could reasonably be considered inappropriate in a professional setting; 21 | * Advocating for or encouraging any of the above behaviors. 22 | * Enforcement and Reporting Code of Conduct Issues: 23 | 24 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported. [Contact us](mailto:opensource-codeofconduct@amazon.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. 25 | 26 | In the event of conflict, this **Code of Conduct** supercedes the original [Code of Conduct](https://opensearch.org/codeofconduct.html) that this project had adopted. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | OpenSearch is a community project that is built and maintained by people just like **you**. 2 | [This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. -------------------------------------------------------------------------------- /DEVELOPER_GUIDE.md: -------------------------------------------------------------------------------- 1 | - [Developer Guide](#developer-guide) 2 | - [Forking and Cloning](#forking-and-cloning) 3 | - [Install Prerequisites](#install-prerequisites) 4 | - [Node and npm](#node-and-npm) 5 | - [Building](#building) 6 | - [Using IntelliJ IDEA](#using-intellij-idea) 7 | - [Submitting Changes](#submitting-changes) 8 | 9 | ## Developer Guide 10 | 11 | So you want to contribute code to this project? Excellent! We're glad you're here. Here's what you need to do. 12 | 13 | ### Forking and Cloning 14 | 15 | Fork this repository on GitHub, and clone locally with `git clone`. 16 | 17 | ### Install Prerequisites 18 | 19 | #### Node and npm 20 | 21 | OpenSearch Perftop uses Node 10 at a minimum (version >= v10.0 < v11.0). This means you must have correct node version installed. 22 | 23 | ### Building 24 | 25 | To build from the command line, use `./gradlew`. 26 | 27 | ``` 28 | ./gradlew clean 29 | ./gradlew build -Dbuild.linux={true/false} -Dbuild.macos={true/false} 30 | ``` 31 | 32 | #### Build Issues 33 | 34 | 1. eslint should not lint through the node_modules folder by default, if it does not obey that rule, 35 | try deleting the package-lock.json file and re-run `npm install`. 36 | _This can be followed as a first step towards other build issues as well._ 37 | 38 | 39 | ### Using IntelliJ IDEA 40 | 41 | Launch Intellij IDEA, choose **Import Project**, and select the `build.gradle` file in the root of this package. 42 | 43 | ### Submitting Changes 44 | 45 | See [CONTRIBUTING](CONTRIBUTING.md). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This document contains a list of maintainers in this repo. See [opensearch-project/.github/RESPONSIBILITIES.md](https://github.com/opensearch-project/.github/blob/main/RESPONSIBILITIES.md#maintainer-responsibilities) that explains what the role of maintainer means, what maintainers do in this and other repos, and how they should be doing it. If you're interested in contributing, and becoming a maintainer, see [CONTRIBUTING](CONTRIBUTING.md). 4 | 5 | ## Current Maintainers 6 | 7 | | Maintainer | GitHub ID | Affiliation | 8 | | --------------- | ------------------------------------------- | ----------- | 9 | | Kunal Khatua | [kkhatua](https://github.com/kkhatua) | Amazon | 10 | | Sruti Parthiban | [sruti1312](https://github.com/sruti1312) | Amazon | 11 | | Yujia Sun | [yujias0706](https://github.com/yujias0706) | Amazon | 12 | 13 | ## Emeritus 14 | 15 | | Maintainer | GitHub ID | Affiliation | 16 | | -------------- | -------------------------------------------- | ----------- | 17 | | Joshua Tokle | [jotok](https://github.com/jotok) | Amazon | 18 | | Ruizhen Guo | [rguo-aws](https://github.com/rguo-aws) | Amazon | 19 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | OpenSearch (https://opensearch.org/) 2 | Copyright 2021 OpenSearch Contributors 3 | 4 | This product includes software developed by 5 | Elasticsearch (http://www.elastic.co). 6 | Copyright 2009-2018 Elasticsearch 7 | 8 | This product includes software developed by The Apache Software 9 | Foundation (http://www.apache.org/). 10 | 11 | This product includes software developed by 12 | Joda.org (http://www.joda.org/). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CD](https://github.com/opensearch-project/perftop/workflows/CD/badge.svg)](https://github.com/opensearch-project/perftop/actions?query=workflow%3ACD) 2 | [![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](https://opensearch.org/docs/monitoring-plugins/pa/dashboards/) 3 | [![Chat](https://img.shields.io/badge/chat-on%20forums-blue)](https://discuss.opendistrocommunity.dev/c/performance-analyzer/) 4 | ![PRs welcome!](https://img.shields.io/badge/PRs-welcome!-success) 5 | 6 | 7 | 8 | 9 | 10 | - [OpenSearch Perftop](#opensearch-perftop) 11 | - [Preset Dashboards](#preset-dashboards) 12 | - [Installation](#installation) 13 | - [Usage](#usage) 14 | - [Build](#build) 15 | - [Documentation](#documentation) 16 | - [Contributing](#contributing) 17 | - [Code of Conduct](#code-of-conduct) 18 | - [Security](#security) 19 | - [License](#license) 20 | - [Copyright](#copyright) 21 | 22 | 23 | 24 | # OpenSearch PerfTop 25 | 26 | The PerfTop CLI provides pre-configured dashboards for analyzing cluster, node, shard performance, and more. Use custom JSON templates to create the dashboards you need to diagnose your cluster performance. 27 | 28 | ## Preset Dashboards 29 | 30 | * All sorts are in decreasing order. 31 | * Bar graphs show aggregated metrics on cluster-level unless stated otherwise. 32 | * Line graphs generate random colors. If no data shows up, it's likely that the data is 0. 33 | 34 | ### ClusterOverview 35 | term 36 | This dashboard can be used to see what operations are running on cluster-level and on shard-level. 37 | With this, users can measure which operation/node is consuming the most CPU and what latency the cluster is experiencing. 38 | 39 | * "Resource Metrics" is sorted by CPU_Utilization. 40 | * "Shard Operation Metrics" is sorted by ShardEvents. 41 | * "Workload" is sorted by HTTP_RequestDocs. 42 | 43 | ### ClusterNetworkMemoryAnalysis 44 | term 45 | This dashboard shows shard-level operation, the network, and memory metrics. 46 | It can be used to analyze which shard is doing the most workload, the amount of data being transmitted by the network, 47 | which disk is performing poorly, and which circuit breaker type is experiencing OutOfMemory exceptions. 48 | 49 | * "Shard Operation Metrics" is sorted by ShardEvents. 50 | * "Circuit Breaker - Tripped Events / Estimated and Configured Limits" is sorted by CB_TrippedEvents. 51 | 52 | ### ClusterThreadAnalysis 53 | term 54 | This dashboard shows low-level metrics about threads/threadpools, which can be used to analyze 55 | which threadpool type is rejecting operations due to its queue being too large, 56 | which thread is running/waiting for too long and results in blocks, 57 | and which thread operation is having issues with memory and is having to load it from the disk. 58 | 59 | * "Thread Pool - Queue Size and Rejected Requests" is sorted by ThreadPool_RejectedReqs. 60 | * "Thread - Blocked Time" is sorted by Thread_Blocked_Time. 61 | * "Page Faults" is sorted by Paging_MajfltRate. 62 | * All "Context Switch" tables are sorted by Sched_*. 63 | 64 | ### NodeAnalysis 65 | term 66 | This dashboard has the most wide ranges of metric types. 67 | It shows shard-level operation metrics, thread metrics, JVM-related metrics 68 | (e.g. heap usage, garbage collection), and network packet drop rate metrics. 69 | After gaining some insights from the previous dashboards, users can specify which node to fetch metrics for. 70 | 71 | This dashboard supports `--nodename $NODENAME` command-line argument for displaying metric data for 72 | ONLY the node that starts with `$NODENAME`. If not provided, this dashboard will include all nodes. 73 | Users can also define different node names for each type of graphs from the JSON dashboard config. 74 | 75 | * "Shard Operation Metrics" is sorted by ShardEvents. 76 | * "Shard Request Cache Miss" is sorted by Cache_Request_Miss. 77 | * "Thread Pool - Queue Size and Rejected Requests" is sorted by ThreadPool_RejectedReqs. 78 | * "Heap Usage" is sorted by Heap_Used. 79 | * If no `--nodename $NODENAME` is provided, the bar graphs will be aggregated metrics on cluster-level. 80 | 81 | ## Installation 82 | Excutables: 83 | 84 | Download the executables and preset JSON dashboard configs [here](https://github.com/opensearch-project/perftop/releases). 85 | 86 | Supported platforms: Linux, macOS 87 | 88 | ## Usage 89 | 90 | Excutables: 91 | 92 | ``` 93 | ./opensearch-perf-top-${PLATFORM} --dashboard $JSON --endpoint $ENDPOINT 94 | ``` 95 | 96 | ## Build 97 | 98 | Prerequisites: 99 | - `node` (version >= v10.0 < v11.0) 100 | - `npm` 101 | 102 | 1. Clone/download from Github 103 | 2. Run `./gradlew build -Dbuild.linux={true/false} -Dbuild.macos={true/false}`. This will run the following: 104 | 1. `npm install` - locally installs dependencies 105 | 2. `npm run build-{linux/macos}` - creates "opensearch-perf-top-{linux/macos}" executables. 106 | 3. For cleaning, run `./gradlew clean` which will run: 107 | 1. `npm run clean` - deletes locally installed dependencies and executables 108 | 109 | To run PerfTop without (re)creating the executables every code change: 110 | ``` 111 | node ./lib/bin.js --dashboard $JSON 112 | ``` 113 | 114 | ## Documentation 115 | 116 | Please refer to the [technical documentation](https://opensearch.org/docs/monitoring-plugins/pa/index/) for detailed information on installing and configuring Perftop. 117 | 118 | ## Contributing 119 | 120 | See [developer guide](DEVELOPER_GUIDE.md) and [how to contribute to this project](CONTRIBUTING.md). 121 | 122 | ## Code of Conduct 123 | 124 | This project has adopted the [Amazon Open Source Code of Conduct](CODE_OF_CONDUCT.md). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq), or contact [opensource-codeofconduct@amazon.com](mailto:opensource-codeofconduct@amazon.com) with any additional questions or comments. 125 | 126 | ## Security 127 | 128 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public GitHub issue. 129 | 130 | ## License 131 | 132 | This project is licensed under the [Apache v2.0 License](LICENSE). 133 | 134 | ## Copyright 135 | 136 | Copyright OpenSearch Contributors. See [NOTICE](NOTICE) for details. 137 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | ## Releasing 2 | 3 | This project follows [OpenSearch branching, labelling, and releasing](https://github.com/opensearch-project/.github/blob/main/RELEASING.md). 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting a Vulnerability 2 | 3 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue. -------------------------------------------------------------------------------- /THIRD-PARTY-LICENSES.txt: -------------------------------------------------------------------------------- 1 | ** eslint-plugin-promise; version 4.0.1 -- https://www.npmjs.com/package/eslint-plugin-promise 2 | ISC License (ISC) 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 7 | 8 | ISC License 9 | 10 | Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC") 11 | 12 | Copyright (c) 1995-2003 by Internet Software Consortium 13 | 14 | Permission to use, copy, modify, and /or distribute this software for any 15 | purpose with or without fee is hereby granted, provided that the above 16 | copyright notice and this permission notice appear in all copies. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD 19 | TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 21 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 22 | DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 23 | ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 24 | SOFTWARE. 25 | 26 | ----- 27 | 28 | ** eslint-plugin-import; version 2.16.0 -- https://www.npmjs.com/package/eslint-plugin-import 29 | Copyright (c) 2015 Ben Mosher 30 | 31 | The MIT License (MIT) 32 | 33 | Copyright (c) 2015 Ben Mosher 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining a copy 36 | of this software and associated documentation files (the "Software"), to deal 37 | in the Software without restriction, including without limitation the rights 38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 39 | copies of the Software, and to permit persons to whom the Software is 40 | furnished to do so, subject to the following conditions: 41 | 42 | The above copyright notice and this permission notice shall be included in all 43 | copies or substantial portions of the Software. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 51 | SOFTWARE. 52 | 53 | ----- 54 | 55 | ** pkg; version 4.3.7 -- https://www.npmjs.com/package/pkg 56 | Copyright (c) 2016 Zeit, Inc. 57 | 58 | The MIT License (MIT) 59 | 60 | Copyright (c) 2016 Zeit, Inc. 61 | 62 | Permission is hereby granted, free of charge, to any person obtaining a copy 63 | of this software and associated documentation files (the "Software"), to deal 64 | in the Software without restriction, including without limitation the rights 65 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 66 | copies of the Software, and to permit persons to whom the Software is 67 | furnished to do so, subject to the following conditions: 68 | 69 | The above copyright notice and this permission notice shall be included in all 70 | copies or substantial portions of the Software. 71 | 72 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 73 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 74 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 75 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 76 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 77 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 78 | SOFTWARE. 79 | 80 | ----- 81 | 82 | ** eslint-plugin-standard; version 4.0.0 -- https://www.npmjs.com/package/eslint-plugin-standard 83 | Copyright (c) 2015 Jamund Ferguson 84 | 85 | The MIT License (MIT) 86 | 87 | Copyright (c) 2015 Jamund Ferguson 88 | 89 | Permission is hereby granted, free of charge, to any person obtaining a copy 90 | of this software and associated documentation files (the "Software"), to deal 91 | in the Software without restriction, including without limitation the rights 92 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 93 | copies of the Software, and to permit persons to whom the Software is 94 | furnished to do so, subject to the following conditions: 95 | 96 | The above copyright notice and this permission notice shall be included in all 97 | copies or substantial portions of the Software. 98 | 99 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 100 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 101 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 102 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 103 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 104 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 105 | SOFTWARE. 106 | 107 | ----- 108 | 109 | ** blessed; version 0.1.81 -- https://github.com/chjj/blessed 110 | Copyright (c) 2013-2015, Christopher Jeffrey and contributors 111 | ** blessed-contrib; version 4.8.10 -- https://github.com/yaronn/blessed-contrib 112 | Copyright (c) 2015 Yaron Naveh and blessed-contrib contributors 113 | 114 | Permission is hereby granted, free of charge, to any person obtaining a copy 115 | of this software and associated documentation files (the "Software"), to deal 116 | in the Software without restriction, including without limitation the rights 117 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 118 | copies of the Software, and to permit persons to whom the Software is 119 | furnished to do so, subject to the following conditions: 120 | 121 | The above copyright notice and this permission notice shall be included in all 122 | copies or substantial portions of the Software. 123 | 124 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 125 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 126 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 127 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 128 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 129 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 130 | SOFTWARE. 131 | 132 | ----- 133 | 134 | ** eslint-plugin-node; version 8.0.1 -- https://www.npmjs.com/package/eslint-plugin-node 135 | Copyright (c) 2015 Toru Nagashima 136 | 137 | The MIT License (MIT) 138 | 139 | Copyright (c) 2015 Toru Nagashima 140 | 141 | Permission is hereby granted, free of charge, to any person obtaining a copy 142 | of this software and associated documentation files (the "Software"), to deal 143 | in the Software without restriction, including without limitation the rights 144 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 145 | copies of the Software, and to permit persons to whom the Software is 146 | furnished to do so, subject to the following conditions: 147 | 148 | The above copyright notice and this permission notice shall be included in all 149 | copies or substantial portions of the Software. 150 | 151 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 152 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 153 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 154 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 155 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 156 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 157 | SOFTWARE. 158 | 159 | ----- 160 | 161 | ** argparse; version 1.0.10 -- https://www.npmjs.com/package/argparse 162 | Copyright (C) 2012 by Vitaly Puzrin 163 | 164 | (The MIT License) 165 | 166 | Copyright (C) 2012 by Vitaly Puzrin 167 | 168 | Permission is hereby granted, free of charge, to any person obtaining a copy 169 | of this software and associated documentation files (the "Software"), to deal 170 | in the Software without restriction, including without limitation the rights 171 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 172 | copies of the Software, and to permit persons to whom the Software is 173 | furnished to do so, subject to the following conditions: 174 | 175 | The above copyright notice and this permission notice shall be included in 176 | all copies or substantial portions of the Software. 177 | 178 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 179 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 180 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 181 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 182 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 183 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 184 | THE SOFTWARE. 185 | 186 | ----- 187 | 188 | ** console-stamp; version 0.2.7 -- https://www.npmjs.com/package/console-stamp 189 | The MIT License 190 | 191 | Permission is hereby granted, free of charge, to any person obtaining a copy 192 | of this software and associated documentation files (the "Software"), to deal 193 | in the Software without restriction, including without limitation the rights 194 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 195 | copies of the Software, and to permit persons to whom the Software is 196 | furnished to do so, subject to the following conditions: 197 | 198 | The above copyright notice and this permission notice shall be included in 199 | all copies or substantial portions of the Software. 200 | 201 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 202 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 203 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 204 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 205 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 206 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 207 | THE SOFTWARE. 208 | 209 | MIT License 210 | 211 | Copyright (c) 212 | 213 | Permission is hereby granted, free of charge, to any person obtaining a copy of 214 | this software and associated documentation files (the "Software"), to deal in 215 | the Software without restriction, including without limitation the rights to 216 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 217 | of the Software, and to permit persons to whom the Software is furnished to do 218 | so, subject to the following conditions: 219 | 220 | The above copyright notice and this permission notice shall be included in all 221 | copies or substantial portions of the Software. 222 | 223 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 224 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 225 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 226 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 227 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 228 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 229 | SOFTWARE. 230 | 231 | ----- 232 | 233 | ** eslint-config-standard; version 12.0.0 -- https://github.com/standard/eslint-config-standard 234 | Copyright (c) Feross Aboukhadijeh 235 | 236 | The MIT License (MIT) 237 | 238 | Copyright (c) Feross Aboukhadijeh 239 | 240 | Permission is hereby granted, free of charge, to any person obtaining a copy of 241 | this software and associated documentation files (the "Software"), to deal in 242 | the Software without restriction, including without limitation the rights to 243 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 244 | of 245 | the Software, and to permit persons to whom the Software is furnished to do so, 246 | subject to the following conditions: 247 | 248 | The above copyright notice and this permission notice shall be included in all 249 | copies or substantial portions of the Software. 250 | 251 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 252 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 253 | FITNESS 254 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 255 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 256 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 257 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 258 | 259 | ----- 260 | 261 | ** eslint; version 5.12.1 -- https://eslint.org/ 262 | Copyright JS Foundation and other contributors, https://js.foundation 263 | 264 | Copyright JS Foundation and other contributors, https://js.foundation 265 | 266 | Permission is hereby granted, free of charge, to any person obtaining a copy 267 | of this software and associated documentation files (the "Software"), to deal 268 | in the Software without restriction, including without limitation the rights 269 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 270 | copies of the Software, and to permit persons to whom the Software is 271 | furnished to do so, subject to the following conditions: 272 | 273 | The above copyright notice and this permission notice shall be included in 274 | all copies or substantial portions of the Software. 275 | 276 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 277 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 278 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 279 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 280 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 281 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 282 | THE SOFTWARE. -------------------------------------------------------------------------------- /bin/global.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright OpenSearch Contributors 5 | * SPDX-License-Identifier: Apache-2.0 6 | */ 7 | 8 | require('../lib/bin.js'); -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | buildscript { 7 | 8 | ext { 9 | opensearch_version = System.getProperty("opensearch.version", "1.3.0-SNAPSHOT") 10 | } 11 | // This isn't applying from repositories.gradle so repeating it here 12 | repositories { 13 | mavenCentral() 14 | mavenLocal() 15 | maven { url "https://plugins.gradle.org/m2/" } 16 | maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } 17 | } 18 | 19 | dependencies { 20 | classpath "org.opensearch.gradle:build-tools:${opensearch_version}" 21 | } 22 | } 23 | 24 | plugins { 25 | id "java" 26 | id "com.moowork.node" version "1.2.0" 27 | } 28 | 29 | node { 30 | version = "10.15.0" 31 | download = false 32 | } 33 | 34 | ext { 35 | isSnapshot = "true" == System.getProperty("build.snapshot", "true") 36 | isLinux = "true" == System.getProperty("build.linux", "false") 37 | isMacOS = "true" == System.getProperty("build.macos", "false") 38 | } 39 | 40 | group = "org.opensearch" 41 | version = opensearch_version - '-SNAPSHOT' + '.0' 42 | if (isSnapshot) { 43 | version += "-SNAPSHOT" 44 | } 45 | 46 | if (isLinux) { 47 | version += "-linux-x64" 48 | } 49 | if (isMacOS) { 50 | version += "-macos-x64" 51 | } 52 | if (isSnapshot) { 53 | version += "-SNAPSHOT" 54 | } 55 | 56 | repositories { 57 | mavenCentral() 58 | mavenLocal() 59 | maven { url "https://plugins.gradle.org/m2/" } 60 | maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } 61 | } 62 | 63 | apply plugin: 'opensearch.opensearchplugin' 64 | 65 | ext { 66 | projectSubstitutions = [:] 67 | licenseFile = rootProject.file('LICENSE.txt') 68 | noticeFile = rootProject.file('NOTICE.txt') 69 | } 70 | test { 71 | enabled = false 72 | } 73 | licenseHeaders.enabled = true 74 | validateNebulaPom.enabled = false 75 | 76 | task npmRunBuild(type: NpmTask, dependsOn: 'npmInstall') { 77 | if (isLinux) { 78 | args = ['run', 'build-linux'] 79 | } 80 | if (isMacOS) { 81 | args = ['run', 'build-macos'] 82 | } 83 | } 84 | 85 | task npmRunClean(type: NpmTask) { 86 | args = ['run', 'clean'] 87 | } 88 | 89 | compileJava { 90 | dependsOn npmRunBuild 91 | } 92 | 93 | clean { 94 | dependsOn npmRunClean 95 | } 96 | 97 | // We are excluding plugin descriptor for PerfTop 98 | // The info here could be random 99 | opensearchplugin { 100 | name 'opensearch-perf-top' 101 | description 'OpenSearch PerfTop' 102 | classname 'org.opensearch.performanceanalyzer.PerformanceAnalyzerPlugin' 103 | } 104 | 105 | 106 | bundlePlugin { 107 | exclude "opensearch-perf-top*.jar" 108 | exclude "plugin-descriptor.properties" 109 | if (isLinux) { 110 | from ("build/") { 111 | include "opensearch-perf-top-linux" 112 | } 113 | } 114 | if (isMacOS) { 115 | from ("build/") { 116 | include "opensearch-perf-top-macos" 117 | } 118 | } 119 | from ("dashboards/") { 120 | into "dashboards/" 121 | include "*.json" 122 | } 123 | } 124 | 125 | // The testingConventions task now mandates atleast one of the integ test tasks to be performed. 126 | // Since perftop is not really a plugin on its own, it fails the plugin install step during the integ tests. So, 127 | // skip running testingConventions task. 128 | gradle.startParameter.excludedTaskNames += ["testingConventions"] 129 | -------------------------------------------------------------------------------- /docs/DesignDoc.md: -------------------------------------------------------------------------------- 1 | # PerfTop Design Doc 2 | 3 | ## Overview 4 | 5 |

6 | term 7 |

8 | 9 | PerfTop CLI is a front-end visualization tool for the Performance Analyzer plugin (PA) so that users can conveniently monitor OpenSearch cluster performance in real-time. 10 | 11 | Its workflow entails each widget (1) making a GET request to the PA's REST API, (2) parsing the response, and (3) plotting the data. 12 | 13 | ## Why Node.js? 14 | 15 | Two options were considered — Python and Node.js. Both languages are well-supported and popular and have many lightweight data plotting libraries available (e.g. matplotlib for Python and blessed-contrib for Node.js). 16 | 17 | There were 2 major factors to consider: 18 | 19 | 1. Data fetching, parsing, and plotting needs to be synchronous. 20 | 1. Python has an advantage in making synchronous I/O calls. 21 | 2. Node.js can support the synchronous behavior with callbacks, but callbacks make error handling and debugging more difficult. 22 | 2. Each widget needs to be handled asynchronously so that the dashboard is as real-time and independent as possible. 23 | 1. Supporting this in Python requires multi-process/thread (non-blocking) programming, each process/thread per widget. This makes the code much more complex. 24 | 2. It is simple and easy to handle each dashboard component asynchronously in Node.js. 25 | 26 | Both languages were great candidates but Node.js was a win, because it is able to support both #1 and #2 and the drawbacks of callbacks are not major in the PerfTop's workflow architecture. 27 | 28 | ## Data Fetch / Parse 29 | 30 | PerfTop makes a HTTP request to the PA's Reader to fetch the metric data (`GET /_plugins/_performanceanalyzer/metrics?metrics=${metrics}&agg=${aggregation}&dim=${dimensions}&nodes=all`). Note that by default it queries for `nodes=all`. 31 | 32 | The returned response is in the format of: 33 | 34 | ``` 35 | { nodeName1: { "timestamp": timestamp, 36 | "data": 37 | {"fields": [{ 38 | "name": dim_type1, 39 | "type": type }, 40 | { 41 | "name": dim_type2, 42 | "type": type }], 43 | "records": [ data1, data2, ... ] }, 44 | nodeName2: { "timestamp": timestamp, 45 | "data": 46 | {"fields": [{ 47 | "name": dim_type1, 48 | "type": type }, 49 | { 50 | "name": dim_type2, 51 | "type": type }], 52 | "records": [ data1, data2, ... ] } } 53 | ``` 54 | 55 | We parse this into such data structure to make data plotting easier: 56 | 57 | ``` 58 | { nodeName1: { fields: [dim_type1, dim_type2], 59 | data: [ [data1], [data2], ... ], 60 | timestamp: timestamp }, 61 | nodeName2: { fields: [dim_type1, dim_type2], 62 | data: [ [data1], [data2], ... ], 63 | timestamp: timestamp } } 64 | ``` 65 | 66 | We cache the latest timestamp received for each node to determine if the data is stale or not. Because we don't want users to lose the data from the dashboard entirely when PA fails to update the metric data, we retry up to 3 iterations before removing it. 67 | 68 | ## Widgets and Usages 69 | 70 | The first version of the PerfTop CLI includes tables, bar graphs, and line graphs as they are the most basic form of data representations. Each widget displays data on metric-level, dimension-level, and node-level so users can utilize the widget types according to their needs. 71 | 72 | In this section, dimension type refers to the dim parameter passed into the `GET` request (e.g. Operation, IndexName, ShardID). A dimension type returns a set of dimension values. For example, dimension type “Operation” returns dimension values like “shardbulk”, “GC”, “merge”, “refresh”, etc. 73 | 74 | ### Table 75 | 76 | Table is a metric-level widget and has the most flexibility, meaning that the user can query for as many metric/dimensions as they wish. Each column represents a metric/dimension/node name and each row represents the data returned from the PA. Users can also define a column to sort by (in a decreasing order). 77 | 78 | PerfTop also makes a one-time `GET /_plugins/_performanceanalyzer/metrics/units` request to fetch the units for all metrics. Tables then display these in the column headers. 79 | 80 | ### Bar 81 | 82 | Bar graphs show data on dimension-level. The data will be summed across all nodes (cluster-wide) by default, unless the user specifies the “nodeName” field in the configuration to show data for a single node. Each bar represents a dimension value and its associated metric value. User must query for one metric that returns a numeric value and one dimension type. 83 | 84 | ### Line 85 | 86 | Line graph widget shows metrics on node-level, each line representing an OpenSearch cluster node. Because the number of nodes is arbitrary, the colors are randomized by default unless the user specifies the set of colors in the configuration. 87 | 88 | ### Global options 89 | 90 | To allow more flexibility in the data visualization, we have extended few user-configurable options below: 91 | 92 | * `nodeName` — the name of the OpenSearch cluster node for PerfTop to do a “startsWith” check during data parsing. This allows cluster-wide tables and bar graphs to be node-specific. 93 | * `dimensionFilters` — users can define which dimension values to fetch for. 94 | 95 | ## Error Handling 96 | 97 | Widgets are generated asynchronously. In case of any exceptions while generating a widget, we want to fail silently so that other widgets are not interfered and users don't lose the entire dashboard. 98 | 99 | Handled cases: 100 | 101 | * If a query parameter is invalid, make the widget blank. 102 | * If PA returns an non-parseable response format, make the widget blank. 103 | * If PA has issues and returns stale data, retry up to 3 iterations and remove the data from the dashboard afterwards. 104 | 105 | ### Logging 106 | 107 | PerfTop incorporates the simplest way of logging — sending exception messages to STDERR and allowing users to define a logfile to direct the STDERR to. Because PerfTop is not a program that runs in a background or a system application, it doesn't make sense to have a separate mechanism for logging and rotating or cleaning up the generated log files. The workflow of PerfTop is simple and the set of errors isn't very extensive, so we let users to have an option to log for debugging purposes. 108 | 109 | ## Dashboard Configuration 110 | 111 | Users are able to configure the PerfTop dashboards via a JSON file. They can specify widget types, query parameters (e.g. OpenSearch cluster endpoint and what metrics to query for.), and visualization options (e.g. colors, widget positions, etc.). 112 | 113 | Example JSON format: 114 | 115 | ``` 116 | { 117 | "endpoint": "", 118 | "graphs": { 119 | "tables": [ 120 | { 121 | "queryParams": { 122 | "metrics": "metric1,metrci2,...", 123 | "aggregates": "max,sum,...", 124 | "dimensions": "dim1,dim2,..." 125 | }, 126 | "options": { 127 | "gridPosition": {...}, 128 | "label": "widget title", 129 | "refreshInterval": 5000 130 | } 131 | }, 132 | {...} 133 | ], 134 | "bars": [ 135 | {...} 136 | ], 137 | "lines": [ 138 | {...} 139 | ] 140 | } 141 | } 142 | ``` 143 | 144 | We allow global fields like endpoint, log file path, and json file path to be defined via a command line argument for convenience. 145 | 146 | Because the first version of PerfTop is a simple, lightweight, and standalone CLI tool on the terminal, other interactive/visual tools for the dashboard configuration are not included. 147 | 148 | ## Packaging 149 | 150 | PerfTop is packaged into ready-to-use, convenient executable file for Linux and MacOS. This will eliminate the trouble of users having to install Node.js or having version conflicts incompatibilities. 151 | 152 | Windows OS is not supported due to ASCII generation incompatibilities from the package dependencies and Window's Command Prompt application. 153 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/perftop/80364bcec0e2f9225e5a2d83e1046c224901783f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /images/ArchitectureDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/perftop/80364bcec0e2f9225e5a2d83e1046c224901783f/images/ArchitectureDiagram.png -------------------------------------------------------------------------------- /images/ClusterNetworkMemoryAnalysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/perftop/80364bcec0e2f9225e5a2d83e1046c224901783f/images/ClusterNetworkMemoryAnalysis.png -------------------------------------------------------------------------------- /images/ClusterOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/perftop/80364bcec0e2f9225e5a2d83e1046c224901783f/images/ClusterOverview.png -------------------------------------------------------------------------------- /images/ClusterThreadAnalysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/perftop/80364bcec0e2f9225e5a2d83e1046c224901783f/images/ClusterThreadAnalysis.png -------------------------------------------------------------------------------- /images/NodeAnalysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/perftop/80364bcec0e2f9225e5a2d83e1046c224901783f/images/NodeAnalysis.png -------------------------------------------------------------------------------- /lib/bin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var argumentParser = require('argparse').ArgumentParser; 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | var process = require('process'); 10 | var util = require('util'); 11 | 12 | var env = require('./env'); 13 | var clusterOverviewJSON = require('./dashboards/metrics/ClusterOverview.json'); 14 | var clusterNetworkMemoryAnalysisJSON = require('./dashboards/metrics/ClusterNetworkMemoryAnalysis.json'); 15 | var clusterThreadAnalysisJSON = require('./dashboards/metrics/ClusterThreadAnalysis.json'); 16 | var nodeAnalysisJSON = require('./dashboards/metrics/NodeAnalysis.json'); 17 | 18 | var temperatureAnalysisJSON = require('./dashboards/rca/TemperatureAnalysis.json'); 19 | 20 | var metricsGraphGenerator = require('./perf-top/metrics/generate-graphs.js'); 21 | var rcaGraphGenerator = require('./perf-top/rca/generate-graphs'); 22 | 23 | // Parse command line arguments 24 | var parser = new argumentParser({ 25 | description: 'For "Getting Started" guide and documentation, visit https://docs-beta.opensearch.org/' }); 26 | 27 | parser.addArgument( 28 | [ '--dashboard' ], 29 | { required: true, 30 | help: 'Relative path to the dashboard configuration JSON. ' + 31 | 'To load preset dashboard, this may also be: ' + 32 | '(1) ClusterOverview, (2) ClusterNetworkMemoryAnalysis, ' + 33 | '(3) ClusterThreadAnalysis, (4) NodeAnalysis, or (5) TemperatureAnalysis (e.g. "--dashboard ClusterOverview")'} 34 | ); 35 | parser.addArgument( 36 | [ '--endpoint' ], 37 | { help: 'Endpoint URL for the Performance Analyzer queries. This can also be defined in the JSON. ' + 38 | 'Protocol is "http" by default, unless "https" specified in the URL.' } 39 | ); 40 | parser.addArgument( 41 | [ '--nodename' ], 42 | { help: 'Value to replace "#nodeName" in the JSON.' } 43 | ); 44 | parser.addArgument( 45 | [ '--logfile' ], 46 | { help: 'File to redirect STDERR to. If undefined, redirect to "/dev/null".' } 47 | ); 48 | parser.addArgument( 49 | [ '--mode' ], 50 | { help: 'The mode perftop is on. This can be: (1) metrics(default), (2) rca '} 51 | ); 52 | parser.addArgument( 53 | [ '--legacy' ], 54 | { help: 'Set legacy flag as true to run perfTop in the legacy mode.'} 55 | ); 56 | 57 | var args = parser.parseArgs(); 58 | // Load JSON data and set `endpoint` and `nodeName` 59 | var jsonData; 60 | if (args.dashboard === 'ClusterOverview') { 61 | jsonData = clusterOverviewJSON; 62 | } else if (args.dashboard === 'ClusterNetworkMemoryAnalysis') { 63 | jsonData = clusterNetworkMemoryAnalysisJSON; 64 | } else if (args.dashboard === 'ClusterThreadAnalysis') { 65 | jsonData = clusterThreadAnalysisJSON; 66 | } else if (args.dashboard === 'NodeAnalysis') { 67 | jsonData = nodeAnalysisJSON; 68 | } else if (args.dashboard === 'TemperatureAnalysis') { 69 | jsonData = temperatureAnalysisJSON; 70 | } else { 71 | jsonData = require(path.resolve(process.cwd(), args.dashboard)); 72 | } 73 | 74 | if (!('endpoint' in jsonData)) { 75 | jsonData.endpoint = 'localhost'; 76 | } 77 | 78 | //stringify and parse jsonData 79 | jsonData.endpoint = args.endpoint ? args.endpoint : jsonData.endpoint; 80 | jsonData = JSON.stringify(jsonData); 81 | jsonData = jsonData.replace(/#nodeName/g, args.nodename ? args.nodename : ''); 82 | jsonData = JSON.parse(jsonData); 83 | 84 | // Configure stderr to a logfile 85 | var logPath = args.logfile ? path.resolve(process.cwd(), args.logfile) : '/dev/null'; 86 | var logFile = fs.createWriteStream(logPath); 87 | console.error = function (msg) { 88 | logFile.write(util.format(msg) + '\n'); 89 | }; 90 | 91 | // Set the API url prefix 92 | if (args.legacy !== null && args.legacy.toLowerCase() == "true") { 93 | env.setLegacyBaseMetricsUrl(); 94 | env.setLegacyBaseRcaUrl(); 95 | } else { 96 | env.setBaseMetricsUrl(); 97 | env.setBaseRcaUrl(); 98 | } 99 | 100 | if (args.mode === 'rca') { 101 | rcaGraphGenerator.initAndStart(jsonData); 102 | } else { 103 | metricsGraphGenerator.initAndStart(jsonData); 104 | } 105 | -------------------------------------------------------------------------------- /lib/dashboards/metrics/ClusterNetworkMemoryAnalysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoint": "localhost:9600", 3 | "gridOptions": { 4 | "rows": 12, 5 | "cols": 12 6 | }, 7 | "graphs": { 8 | "tables": [ 9 | { 10 | "queryParams" : { 11 | "metrics": "Disk_Utilization", 12 | "aggregates": "avg", 13 | "dimensions": "DiskName", 14 | "sortBy": "Disk_Utilization" 15 | }, 16 | "options": { 17 | "gridPosition": { 18 | "row": 4, 19 | "col": 9, 20 | "rowSpan": 4, 21 | "colSpan": 3 22 | }, 23 | "label": "Average Disk Utilization Rate", 24 | "keys": false, 25 | "fg": "green", 26 | "selectedFg": "green", 27 | "selectedBg": " ", 28 | "columnSpacing": 1, 29 | "refreshInterval": 5000 30 | } 31 | }, 32 | { 33 | "queryParams" : { 34 | "metrics": "Disk_WaitTime", 35 | "aggregates": "avg", 36 | "dimensions": "DiskName", 37 | "sortBy": "Disk_WaitTime" 38 | }, 39 | "options": { 40 | "gridPosition": { 41 | "row": 0, 42 | "col": 9, 43 | "rowSpan": 4, 44 | "colSpan": 3 45 | }, 46 | "label": "Average Disk Wait Time", 47 | "keys": false, 48 | "fg": "green", 49 | "selectedFg": "green", 50 | "selectedBg": " ", 51 | "columnSpacing": 1, 52 | "refreshInterval": 5000 53 | } 54 | }, 55 | { 56 | "queryParams" : { 57 | "metrics": "Disk_ServiceRate", 58 | "aggregates": "avg", 59 | "dimensions": "DiskName", 60 | "sortBy": "Disk_ServiceRate" 61 | }, 62 | "options": { 63 | "gridPosition": { 64 | "row": 4, 65 | "col": 6, 66 | "rowSpan": 4, 67 | "colSpan": 3 68 | }, 69 | "label": "Average Disk Service Rate", 70 | "keys": false, 71 | "fg": "green", 72 | "selectedFg": "green", 73 | "selectedBg": " ", 74 | "columnSpacing": 1, 75 | "refreshInterval": 5000 76 | } 77 | }, 78 | { 79 | "queryParams" : { 80 | "metrics": "ShardBulkDocs,ShardEvents", 81 | "aggregates": "sum,sum", 82 | "dimensions": "Operation,IndexName", 83 | "sortBy": "ShardEvents" 84 | }, 85 | "options": { 86 | "gridPosition": { 87 | "row": 0, 88 | "col": 0, 89 | "rowSpan": 4, 90 | "colSpan": 5 91 | }, 92 | "label": "Shard Operation Metrics", 93 | "keys": false, 94 | "fg": "white", 95 | "selectedFg": "white", 96 | "selectedBg": " ", 97 | "columnSpacing": 1, 98 | "refreshInterval": 5000 99 | } 100 | }, 101 | { 102 | "queryParams" : { 103 | "metrics": "CB_TrippedEvents,CB_EstimatedSize,CB_ConfiguredSize", 104 | "aggregates": "sum,max,max", 105 | "dimensions": "CBType", 106 | "sortBy": "CB_TrippedEvents" 107 | }, 108 | "options": { 109 | "gridPosition": { 110 | "row": 8, 111 | "col": 6, 112 | "rowSpan": 4, 113 | "colSpan": 6 114 | }, 115 | "label": "Circuit Breaker - Tripped Events / Estimated and Configured Limits", 116 | "keys": false, 117 | "fg": "blue", 118 | "selectedFg": "blue", 119 | "selectedBg": " ", 120 | "columnSpacing": 1, 121 | "refreshInterval": 5000 122 | } 123 | } 124 | ], 125 | "bars": [ 126 | { 127 | "queryParams": { 128 | "metrics": "ShardEvents", 129 | "aggregates": "sum", 130 | "dimensions": "Operation", 131 | "nodeName": "#nodeName" 132 | }, 133 | "options": { 134 | "gridPosition": { 135 | "row": 0, 136 | "col": 5, 137 | "rowSpan": 4, 138 | "colSpan": 4 139 | }, 140 | "label": "Shard Operation - Sum by Operation (count)", 141 | "labelColor": "blue", 142 | "barWidth": 15, 143 | "xOffset": 2, 144 | "maxHeight": 6, 145 | "refreshInterval": 5000 146 | } 147 | } 148 | ], 149 | "lines": [ 150 | { 151 | "queryParams" : { 152 | "metrics": "Net_Throughput", 153 | "aggregates": "sum", 154 | "dimensions": "Direction", 155 | "dimensionFilters": ["in"] 156 | }, 157 | "options": { 158 | "gridPosition": { 159 | "row": 4, 160 | "col": 0, 161 | "rowSpan": 4, 162 | "colSpan": 6 163 | }, 164 | "label": "Network Throughput In (B/s)", 165 | "showNthLabel": 3, 166 | "showLegend": true, 167 | "legend": { "width": 10 }, 168 | "xAxis": ["0:00", "0:05", "0:10", "0:15", "0:20", "0:25", "0:30", "0:35", "0:40", "0:45", "0:50", "0:55"], 169 | "refreshInterval": 5000 170 | } 171 | }, 172 | { 173 | "queryParams" : { 174 | "metrics": "Net_Throughput", 175 | "aggregates": "sum", 176 | "dimensions": "Direction", 177 | "dimensionFilters": ["out"] 178 | }, 179 | "options": { 180 | "gridPosition": { 181 | "row": 8, 182 | "col": 0, 183 | "rowSpan": 4, 184 | "colSpan": 6 185 | }, 186 | "label": "Network Throughput Out (B/s)", 187 | "showNthLabel": 3, 188 | "showLegend": true, 189 | "legend": { "width": 10 }, 190 | "xAxis": ["0:00", "0:05", "0:10", "0:15", "0:20", "0:25", "0:30", "0:35", "0:40", "0:45", "0:50", "0:55"], 191 | "refreshInterval": 5000 192 | } 193 | } 194 | ] 195 | } 196 | } -------------------------------------------------------------------------------- /lib/dashboards/metrics/ClusterOverview.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoint": "localhost:9600", 3 | "gridOptions": { 4 | "rows": 12, 5 | "cols": 12 6 | }, 7 | "graphs": { 8 | "tables": [ 9 | { 10 | "queryParams" : { 11 | "metrics": "CPU_Utilization,IO_ReadThroughput,IO_WriteThroughput", 12 | "aggregates": "sum,sum,sum", 13 | "dimensions": "Operation,IndexName,ShardID", 14 | "sortBy": "CPU_Utilization" 15 | }, 16 | "options": { 17 | "gridPosition": { 18 | "row": 0, 19 | "col": 0, 20 | "rowSpan": 4, 21 | "colSpan": 7 22 | }, 23 | "label": "Resource Metrics", 24 | "keys": false, 25 | "fg": "green", 26 | "selectedFg": "green", 27 | "selectedBg": " ", 28 | "columnSpacing": 1, 29 | "refreshInterval": 5000 30 | } 31 | }, 32 | { 33 | "queryParams" : { 34 | "metrics": "ShardBulkDocs,ShardEvents", 35 | "aggregates": "sum,sum", 36 | "dimensions": "Operation,IndexName", 37 | "sortBy": "ShardEvents" 38 | }, 39 | "options": { 40 | "gridPosition": { 41 | "row": 0, 42 | "col": 7, 43 | "rowSpan": 4, 44 | "colSpan": 5 45 | }, 46 | "label": "Shard Operation Metrics", 47 | "keys": false, 48 | "fg": "green", 49 | "selectedFg": "green", 50 | "selectedBg": " ", 51 | "columnSpacing": 1, 52 | "refreshInterval": 5000 53 | } 54 | }, 55 | { 56 | "queryParams" : { 57 | "metrics": "Latency,HTTP_RequestDocs,HTTP_TotalRequests", 58 | "aggregates": "avg,sum,sum", 59 | "dimensions": "Operation,HTTPRespCode,Indices", 60 | "dimensionFilters": ["bulk", "search"], 61 | "sortBy": "HTTP_RequestDocs" 62 | }, 63 | "options": { 64 | "gridPosition": { 65 | "row": 4, 66 | "col": 0, 67 | "rowSpan": 4, 68 | "colSpan": 7 69 | }, 70 | "label": "Workload", 71 | "keys": false, 72 | "fg": "green", 73 | "selectedFg": "green", 74 | "selectedBg": " ", 75 | "columnSpacing": 1, 76 | "refreshInterval": 5000 77 | } 78 | } 79 | ], 80 | "bars": [ 81 | { 82 | "queryParams" : { 83 | "metrics": "IO_WriteThroughput", 84 | "aggregates": "sum", 85 | "dimensions": "Operation", 86 | "dimensionFilters": ["other", "shardbulk", "refresh", "merge"] 87 | }, 88 | "options": { 89 | "gridPosition": { 90 | "row": 8, 91 | "col": 0, 92 | "rowSpan": 4, 93 | "colSpan": 3 94 | }, 95 | "label": "Write Throughput (Bps) per Operation", 96 | "labelColor": "red", 97 | "barWidth": 15, 98 | "xOffset": 2, 99 | "maxHeight": 6, 100 | "refreshInterval": 5000 101 | } 102 | }, 103 | { 104 | "queryParams" : { 105 | "metrics": "CPU_Utilization", 106 | "aggregates": "sum", 107 | "dimensions": "Operation" 108 | }, 109 | "options": { 110 | "gridPosition": { 111 | "row": 8, 112 | "col": 3, 113 | "rowSpan": 4, 114 | "colSpan": 9 115 | }, 116 | "label": "CPU (cores) per Operation", 117 | "labelColor": "blue", 118 | "barWidth": 16, 119 | "xOffset": 2, 120 | "maxHeight": 6, 121 | "refreshInterval": 5000 122 | } 123 | } 124 | ], 125 | "lines": [ 126 | { 127 | "queryParams" : { 128 | "metrics": "CPU_Utilization", 129 | "aggregates": "sum", 130 | "dimensions": "Operation" 131 | }, 132 | "options": { 133 | "gridPosition": { 134 | "row": 4, 135 | "col": 7, 136 | "rowSpan": 4, 137 | "colSpan": 5 138 | }, 139 | "label": "OpenSearch Operation CPU (cores) per Node", 140 | "showNthLabel": 3, 141 | "showLegend": true, 142 | "legend": { "width": 10 }, 143 | "xAxis": ["0:00", "0:05", "0:10", "0:15", "0:20", "0:25", "0:30", "0:35", "0:40", "0:45", "0:50", "0:55"], 144 | "refreshInterval": 5000 145 | } 146 | } 147 | ] 148 | } 149 | } -------------------------------------------------------------------------------- /lib/dashboards/metrics/ClusterThreadAnalysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoint": "localhost:9600", 3 | "gridOptions": { 4 | "rows": 12, 5 | "cols": 12 6 | }, 7 | "graphs": { 8 | "tables": [ 9 | { 10 | "queryParams" : { 11 | "metrics": "ThreadPool_QueueSize,ThreadPool_RejectedReqs", 12 | "aggregates": "max,sum", 13 | "dimensions": "ThreadPoolType", 14 | "sortBy": "ThreadPool_RejectedReqs" 15 | }, 16 | "options": { 17 | "gridPosition": { 18 | "row": 0, 19 | "col": 0, 20 | "rowSpan": 4, 21 | "colSpan": 6 22 | }, 23 | "label": "Thread Pool - Queue Size and Rejected Requests", 24 | "keys": false, 25 | "fg": "green", 26 | "selectedFg": "green", 27 | "selectedBg": " ", 28 | "columnSpacing": 1, 29 | "refreshInterval": 5000 30 | } 31 | }, 32 | { 33 | "queryParams" : { 34 | "metrics": "Thread_Blocked_Time", 35 | "aggregates": "avg", 36 | "dimensions": "Operation,IndexName,ShardID", 37 | "sortBy": "Thread_Blocked_Time" 38 | }, 39 | "options": { 40 | "gridPosition": { 41 | "row": 4, 42 | "col": 0, 43 | "rowSpan": 4, 44 | "colSpan": 6 45 | }, 46 | "label": "Thread - Blocked Time", 47 | "keys": false, 48 | "fg": "green", 49 | "selectedFg": "green", 50 | "selectedBg": " ", 51 | "columnSpacing": 1, 52 | "refreshInterval": 5000 53 | } 54 | }, 55 | { 56 | "queryParams" : { 57 | "metrics": "Paging_MajfltRate,Paging_MinfltRate", 58 | "aggregates": "sum,sum", 59 | "dimensions": "Operation,IndexName,ShardID", 60 | "sortBy": "Paging_MajfltRate" 61 | }, 62 | "options": { 63 | "gridPosition": { 64 | "row": 8, 65 | "col": 0, 66 | "rowSpan": 4, 67 | "colSpan": 6 68 | }, 69 | "label": "Page Faults", 70 | "keys": false, 71 | "fg": "green", 72 | "selectedFg": "green", 73 | "selectedBg": " ", 74 | "columnSpacing": 1, 75 | "refreshInterval": 5000 76 | } 77 | }, 78 | { 79 | "queryParams" : { 80 | "metrics": "Sched_Runtime", 81 | "aggregates": "max", 82 | "dimensions": "Operation,IndexName,ShardID", 83 | "sortBy": "Sched_Runtime" 84 | }, 85 | "options": { 86 | "gridPosition": { 87 | "row": 0, 88 | "col": 6, 89 | "rowSpan": 4, 90 | "colSpan": 6 91 | }, 92 | "label": "Context Switch - Run Time", 93 | "keys": false, 94 | "fg": "cyan", 95 | "selectedFg": "cyan", 96 | "selectedBg": " ", 97 | "columnSpacing": 1, 98 | "refreshInterval": 5000 99 | } 100 | }, 101 | { 102 | "queryParams" : { 103 | "metrics": "Sched_Waittime", 104 | "aggregates": "max", 105 | "dimensions": "Operation,IndexName,ShardID", 106 | "sortBy": "Sched_Waittime" 107 | }, 108 | "options": { 109 | "gridPosition": { 110 | "row": 4, 111 | "col": 6, 112 | "rowSpan": 4, 113 | "colSpan": 6 114 | }, 115 | "label": "Context Switch - Wait Time", 116 | "keys": false, 117 | "fg": "cyan", 118 | "selectedFg": "cyan", 119 | "selectedBg": " ", 120 | "columnSpacing": 1, 121 | "refreshInterval": 5000 122 | } 123 | }, 124 | { 125 | "queryParams" : { 126 | "metrics": "Sched_CtxRate", 127 | "aggregates": "sum", 128 | "dimensions": "Operation,IndexName,ShardID", 129 | "sortBy": "Sched_CtxRate" 130 | }, 131 | "options": { 132 | "gridPosition": { 133 | "row": 8, 134 | "col": 6, 135 | "rowSpan": 4, 136 | "colSpan": 6 137 | }, 138 | "label": "Context Switch - Count", 139 | "keys": false, 140 | "fg": "cyan", 141 | "selectedFg": "cyan", 142 | "selectedBg": " ", 143 | "columnSpacing": 1, 144 | "refreshInterval": 5000 145 | } 146 | } 147 | ] 148 | } 149 | } -------------------------------------------------------------------------------- /lib/dashboards/metrics/NodeAnalysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoint": "localhost:9600", 3 | "gridOptions": { 4 | "rows": 12, 5 | "cols": 12 6 | }, 7 | "graphs": { 8 | "tables": [ 9 | { 10 | "queryParams" : { 11 | "metrics": "ShardBulkDocs,ShardEvents", 12 | "aggregates": "sum,sum", 13 | "dimensions": "Operation,IndexName,ShardID", 14 | "nodeName": "#nodeName", 15 | "sortBy": "ShardEvents" 16 | }, 17 | "options": { 18 | "gridPosition": { 19 | "row": 0, 20 | "col": 0, 21 | "rowSpan": 4, 22 | "colSpan": 5 23 | }, 24 | "label": "Shard Operation Metrics", 25 | "keys": false, 26 | "fg": "green", 27 | "selectedFg": "green", 28 | "selectedBg": " ", 29 | "columnSpacing": 1, 30 | "refreshInterval": 5000 31 | } 32 | }, 33 | { 34 | "queryParams" : { 35 | "metrics": "Cache_Request_Miss", 36 | "aggregates": "sum", 37 | "dimensions": "IndexName,ShardID", 38 | "nodeName": "#nodeName", 39 | "sortBy": "Cache_Request_Miss" 40 | }, 41 | "options": { 42 | "gridPosition": { 43 | "row": 0, 44 | "col": 8, 45 | "rowSpan": 4, 46 | "colSpan": 4 47 | }, 48 | "label": "Shard Request Cache Miss", 49 | "keys": false, 50 | "fg": "green", 51 | "selectedFg": "green", 52 | "selectedBg": " ", 53 | "columnSpacing": 1, 54 | "refreshInterval": 5000 55 | } 56 | }, 57 | { 58 | "queryParams" : { 59 | "metrics": "Heap_Used", 60 | "aggregates": "max", 61 | "dimensions": "MemType", 62 | "dimensionFilters": ["Heap","OldGen","Eden","NonHeap","PermGen","Survivor"], 63 | "nodeName": "#nodeName", 64 | "sortBy": "Heap_Used" 65 | }, 66 | "options": { 67 | "gridPosition": { 68 | "row": 8, 69 | "col": 0, 70 | "rowSpan": 4, 71 | "colSpan": 3 72 | }, 73 | "label": "Heap Usage", 74 | "keys": false, 75 | "fg": "yellow", 76 | "selectedFg": "yellow", 77 | "selectedBg": " ", 78 | "columnSpacing": 1, 79 | "refreshInterval": 5000 80 | } 81 | }, 82 | { 83 | "queryParams" : { 84 | "metrics": "ThreadPool_QueueSize,ThreadPool_RejectedReqs", 85 | "aggregates": "max,sum", 86 | "dimensions": "ThreadPoolType", 87 | "nodeName": "#nodeName", 88 | "sortBy": "ThreadPool_RejectedReqs" 89 | }, 90 | "options": { 91 | "gridPosition": { 92 | "row": 4, 93 | "col": 0, 94 | "rowSpan": 4, 95 | "colSpan": 5 96 | }, 97 | "label": "Thread Pool - Queue Size and Rejected Requests", 98 | "keys": false, 99 | "fg": "cyan", 100 | "selectedFg": "cyan", 101 | "selectedBg": " ", 102 | "columnSpacing": 1, 103 | "refreshInterval": 5000 104 | } 105 | } 106 | ], 107 | "bars": [ 108 | { 109 | "queryParams" : { 110 | "metrics": "GC_Collection_Event", 111 | "aggregates": "sum", 112 | "dimensions": "MemType", 113 | "dimensionFilters": ["totFullGC", "totYoungGC"], 114 | "nodeName": "#nodeName" 115 | }, 116 | "options": { 117 | "gridPosition": { 118 | "row": 8, 119 | "col": 3, 120 | "rowSpan": 4, 121 | "colSpan": 2 122 | }, 123 | "label": "Total GC Collection Events (count)", 124 | "labelColor": "white", 125 | "barWidth": 12, 126 | "xOffset": 2, 127 | "maxHeight": 6, 128 | "refreshInterval": 5000 129 | } 130 | }, 131 | { 132 | "queryParams" : { 133 | "metrics": "GC_Collection_Time", 134 | "aggregates": "sum", 135 | "dimensions": "MemType", 136 | "dimensionFilters": ["totFullGC", "totYoungGC"], 137 | "nodeName": "#nodeName" 138 | }, 139 | "options": { 140 | "gridPosition": { 141 | "row": 8, 142 | "col": 5, 143 | "rowSpan": 4, 144 | "colSpan": 2 145 | }, 146 | "label": "Total GC Collection Time (ms)", 147 | "labelColor": "white", 148 | "barWidth": 12, 149 | "xOffset": 2, 150 | "maxHeight": 6, 151 | "refreshInterval": 5000 152 | } 153 | }, 154 | { 155 | "queryParams": { 156 | "metrics": "CB_TrippedEvents", 157 | "aggregates": "sum", 158 | "dimensions": "CBType", 159 | "nodeName": "#nodeName" 160 | }, 161 | "options": { 162 | "gridPosition": { 163 | "row": 4, 164 | "col": 8, 165 | "rowSpan": 4, 166 | "colSpan": 4 167 | }, 168 | "label": "Circuit Breaker - Tripped Events (count)", 169 | "labelColor": "red", 170 | "barWidth": 20, 171 | "xOffset": 2, 172 | "maxHeight": 6, 173 | "refreshInterval": 5000 174 | } 175 | }, 176 | { 177 | "queryParams": { 178 | "metrics": "ShardEvents", 179 | "aggregates": "sum", 180 | "dimensions": "Operation", 181 | "nodeName": "#nodeName" 182 | }, 183 | "options": { 184 | "gridPosition": { 185 | "row": 0, 186 | "col": 5, 187 | "rowSpan": 2, 188 | "colSpan": 3 189 | }, 190 | "label": "Shard Operation - Sum by Operation (count)", 191 | "labelColor": "blue", 192 | "barWidth": 15, 193 | "xOffset": 2, 194 | "maxHeight": 6, 195 | "refreshInterval": 5000 196 | } 197 | } 198 | ], 199 | "lines": [ 200 | { 201 | "queryParams" : { 202 | "metrics": "CPU_Utilization", 203 | "aggregates": "sum", 204 | "dimensions": "Operation", 205 | "dimensionFilters": ["GC"], 206 | "nodeName": "#nodeName" 207 | }, 208 | "options": { 209 | "gridPosition": { 210 | "row": 8, 211 | "col": 7, 212 | "rowSpan": 4, 213 | "colSpan": 5 214 | }, 215 | "label": "Garbage Collection CPU (cores)", 216 | "showNthLabel": 3, 217 | "showLegend": true, 218 | "legend": { "width": 10 }, 219 | "xAxis": ["0:00", "0:05", "0:10", "0:15", "0:20", "0:25", "0:30", "0:35", "0:40", "0:45", "0:50", "0:55"], 220 | "refreshInterval": 5000 221 | } 222 | }, 223 | { 224 | "queryParams" : { 225 | "metrics": "Net_PacketDropRate4", 226 | "aggregates": "sum", 227 | "dimensions": "Direction", 228 | "nodeName": "#nodeName" 229 | }, 230 | "options": { 231 | "gridPosition": { 232 | "row": 2, 233 | "col": 5, 234 | "rowSpan": 3, 235 | "colSpan": 3 236 | }, 237 | "label": "Packet Drop Rate (IPv4)", 238 | "showNthLabel": 3, 239 | "showLegend": true, 240 | "legend": { "width": 10 }, 241 | "xAxis": ["0:00", "0:05", "0:10", "0:15", "0:20", "0:25", "0:30", "0:35", "0:40", "0:45", "0:50", "0:55"], 242 | "refreshInterval": 5000 243 | } 244 | }, 245 | { 246 | "queryParams" : { 247 | "metrics": "Net_PacketDropRate6", 248 | "aggregates": "sum", 249 | "dimensions": "Direction", 250 | "nodeName": "#nodeName" 251 | }, 252 | "options": { 253 | "gridPosition": { 254 | "row": 5, 255 | "col": 5, 256 | "rowSpan": 3, 257 | "colSpan": 3 258 | }, 259 | "label": "Packet Drop Rate (IPv6)", 260 | "showNthLabel": 3, 261 | "showLegend": true, 262 | "legend": { "width": 10 }, 263 | "xAxis": ["0:00", "0:05", "0:10", "0:15", "0:20", "0:25", "0:30", "0:35", "0:40", "0:45", "0:50", "0:55"], 264 | "refreshInterval": 5000 265 | } 266 | } 267 | ] 268 | } 269 | } -------------------------------------------------------------------------------- /lib/dashboards/rca/TemperatureAnalysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoint": "localhost:9600", 3 | "gridOptions": { 4 | "rows": 12, 5 | "cols": 12 6 | }, 7 | "queryParams": { 8 | "name": "AllTemperatureDimensions", 9 | "local": true 10 | }, 11 | "graphs": [ 12 | { 13 | "graphType": "donuts", 14 | "dimension": "Shard_Size_In_Bytes", 15 | "graphParams": "NodeLevelZoneSummary", 16 | "options": { 17 | "gridPosition": { 18 | "row": 0, 19 | "col": 0, 20 | "rowSpan": 3, 21 | "colSpan": 4 22 | }, 23 | "label": "Shard Distribution", 24 | "radius": 8, 25 | "arcWidth": 3, 26 | "remainColor": "black", 27 | "yPadding": 2, 28 | "refreshInterval": 5000 29 | } 30 | }, 31 | { 32 | "graphType": "lines", 33 | "dimension": "Shard_Size_In_Bytes", 34 | "graphParams": "mean", 35 | "options": { 36 | "gridPosition": { 37 | "row": 0, 38 | "col": 4, 39 | "rowSpan": 4, 40 | "colSpan": 4 41 | }, 42 | "label": "Shard_Size_In_Bytes - mean", 43 | "showNthLabel": 3, 44 | "showLegend": true, 45 | "legend": { "width": 10 }, 46 | "xAxis": [ 47 | "0:00", 48 | "0:05", 49 | "0:10", 50 | "0:15", 51 | "0:20", 52 | "0:25", 53 | "0:30", 54 | "0:35", 55 | "0:40", 56 | "0:45", 57 | "0:50", 58 | "0:55" 59 | ], 60 | "refreshInterval": 5000 61 | } 62 | }, 63 | { 64 | "graphType": "lines", 65 | "dimension": "Shard_Size_In_Bytes", 66 | "graphParams": "total", 67 | "options": { 68 | "gridPosition": { 69 | "row": 0, 70 | "col": 8, 71 | "rowSpan": 4, 72 | "colSpan": 4 73 | }, 74 | "label": "Shard_Size_In_Bytes - total", 75 | "showNthLabel": 3, 76 | "showLegend": true, 77 | "legend": { "width": 10 }, 78 | "xAxis": [ 79 | "0:00", 80 | "0:05", 81 | "0:10", 82 | "0:15", 83 | "0:20", 84 | "0:25", 85 | "0:30", 86 | "0:35", 87 | "0:40", 88 | "0:45", 89 | "0:50", 90 | "0:55" 91 | ], 92 | "refreshInterval": 5000 93 | } 94 | }, 95 | { 96 | "graphType": "tables", 97 | "dimension": "Shard_Size_In_Bytes", 98 | "graphParams": "HOT", 99 | "options": { 100 | "gridPosition": { 101 | "row": 4, 102 | "col": 0, 103 | "rowSpan": 4, 104 | "colSpan": 6 105 | }, 106 | "columns": "index_name,shard_id,CPU_Utilization,Heap_AllocRate,Shard_Size_In_Bytes", 107 | "label": "Shard_Size_In_Bytes - HOT", 108 | "keys": false, 109 | "fg": "green", 110 | "selectedFg": "green", 111 | "selectedBg": "", 112 | "columnSpacing": 1, 113 | "refreshInterval": 5000 114 | } 115 | }, 116 | { 117 | "graphType": "tables", 118 | "dimension": "Shard_Size_In_Bytes", 119 | "graphParams": "WARM", 120 | "options": { 121 | "gridPosition": { 122 | "row": 4, 123 | "col": 6, 124 | "rowSpan": 4, 125 | "colSpan": 6 126 | }, 127 | "columns": "index_name,shard_id,CPU_Utilization,Heap_AllocRate,Shard_Size_In_Bytes", 128 | "label": "Shard_Size_In_Bytes - WARM", 129 | "keys": false, 130 | "fg": "green", 131 | "selectedFg": "green", 132 | "selectedBg": "", 133 | "columnSpacing": 1, 134 | "refreshInterval": 5000 135 | } 136 | }, 137 | { 138 | "graphType": "tables", 139 | "dimension": "Shard_Size_In_Bytes", 140 | "graphParams": "LUKE_WARM", 141 | "options": { 142 | "gridPosition": { 143 | "row": 8, 144 | "col": 0, 145 | "rowSpan": 4, 146 | "colSpan": 6 147 | }, 148 | "columns": "index_name,shard_id,CPU_Utilization,Heap_AllocRate,Shard_Size_In_Bytes", 149 | "label": "Shard_Size_In_Bytes - LUKE_WARM", 150 | "keys": false, 151 | "fg": "green", 152 | "selectedFg": "green", 153 | "selectedBg": "", 154 | "columnSpacing": 1, 155 | "refreshInterval": 5000 156 | } 157 | }, 158 | { 159 | "graphType": "tables", 160 | "dimension": "Shard_Size_In_Bytes", 161 | "graphParams": "COLD", 162 | "options": { 163 | "gridPosition": { 164 | "row": 8, 165 | "col": 6, 166 | "rowSpan": 4, 167 | "colSpan": 6 168 | }, 169 | "columns": "index_name,shard_id,CPU_Utilization,Heap_AllocRate,Shard_Size_In_Bytes", 170 | "label": "Shard_Size_In_Bytes - COLD", 171 | "keys": false, 172 | "fg": "green", 173 | "selectedFg": "green", 174 | "selectedBg": "", 175 | "columnSpacing": 1, 176 | "refreshInterval": 5000 177 | } 178 | } 179 | ] 180 | } -------------------------------------------------------------------------------- /lib/env.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | const BASE_URL = "/_plugins/_performanceanalyzer"; 7 | const BASE_URL_METRICS = BASE_URL + "/metrics"; 8 | const BASE_URL_RCA = BASE_URL + "/rca"; 9 | 10 | const LEGACY_BASE_URL = "/_opendistro/_performanceanalyzer"; 11 | const LEGACY_BASE_URL_METRICS = LEGACY_BASE_URL + "/metrics"; 12 | const LEGACY_BASE_URL_RCA = LEGACY_BASE_URL + "/rca"; 13 | 14 | var metricsUrlPrefix; 15 | var rcaUrlPrefix; 16 | 17 | function setBaseMetricsUrl () { 18 | metricsUrlPrefix = BASE_URL_METRICS; 19 | } 20 | 21 | function setLegacyBaseMetricsUrl () { 22 | metricsUrlPrefix = LEGACY_BASE_URL_METRICS; 23 | } 24 | 25 | function setBaseRcaUrl () { 26 | rcaUrlPrefix = BASE_URL_RCA; 27 | } 28 | 29 | function setLegacyBaseRcaUrl () { 30 | rcaUrlPrefix = LEGACY_BASE_URL_RCA; 31 | } 32 | 33 | function getMetricsUrlPrefix () { 34 | return metricsUrlPrefix === undefined ? BASE_URL_METRICS : metricsUrlPrefix; 35 | } 36 | 37 | function getRcaUrlPrefix () { 38 | return rcaUrlPrefix === undefined ? BASE_URL_RCA : rcaUrlPrefix; 39 | } 40 | 41 | module.exports = {setBaseMetricsUrl, setLegacyBaseMetricsUrl, setBaseRcaUrl, setLegacyBaseRcaUrl, getMetricsUrlPrefix, getRcaUrlPrefix}; 42 | -------------------------------------------------------------------------------- /lib/perf-top/helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var http = require('http'); 7 | var https = require('https'); 8 | var url = require('url'); 9 | 10 | /** 11 | * Gets a URL option with host, port (default = 80), and path for http(s).request(). 12 | * 13 | * @param {string} endpoint - endpoint for the query. 14 | * @param {string} path - metric query request path. 15 | */ 16 | function getURLOptions (endpoint, path) { 17 | endpoint = ( endpoint.startsWith('http://') || endpoint.startsWith('https://') ) ? endpoint : 'http://' + endpoint; 18 | var parsedURL = url.parse(url.resolve(endpoint, path)); 19 | return { 20 | protocol: parsedURL.protocol, 21 | host: parsedURL.hostname, 22 | auth: parsedURL.auth, 23 | port: (parsedURL.port === null) ? (parsedURL.protocol === 'https:' ? 443 : 80 ): parsedURL.port, 24 | path: parsedURL.path 25 | }; 26 | } 27 | 28 | /** 29 | * Makes a HTTP(S) request and returns the response. 30 | * 31 | * @param {string} urlOptions - URL option for the HTTP(S) request. 32 | * @param {string} done - callback for the HTTP(S) response. 33 | */ 34 | function makeRequest (urlOptions, done) { 35 | var rawData = ''; 36 | var respond = function (response) { 37 | response.on('data', function (chunk) { 38 | rawData += chunk; 39 | }); 40 | response.on('end', function () { 41 | done(rawData); 42 | }); 43 | }; 44 | var client = http; 45 | if (urlOptions.protocol == "https:") { 46 | client = https; 47 | urlOptions.rejectUnauthorized = false; 48 | } 49 | var req = client.request(urlOptions, respond); 50 | req.on('error', function (error) { 51 | console.error(error); 52 | done(''); 53 | }); 54 | req.end(); 55 | } 56 | 57 | /** 58 | * Convert and parse a string to a number. 59 | * 60 | * @param {string} data - string 61 | * @returns {number|string} - if `data` cannot be converted to a number, return `data` back, 62 | * else return the number (with maximum of 2 decimal points if less than 100, 63 | * else a rounded integer). 64 | */ 65 | function parseNumberData (data) { 66 | if (data === null) { 67 | return 'null'; 68 | } else if (data === '') { 69 | return data; 70 | } 71 | var parsedData = Math.round(data * 100) / 100; 72 | if (isNaN(parsedData)) { 73 | return data; 74 | } else if (parsedData < 100) { 75 | return parsedData; 76 | } else { 77 | return Math.round(parsedData); 78 | } 79 | } 80 | 81 | module.exports.makeRequest = makeRequest; 82 | module.exports.getURLOptions = getURLOptions; 83 | module.exports.parseNumberData = parseNumberData; 84 | -------------------------------------------------------------------------------- /lib/perf-top/metric-graphs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var blessed = require('blessed'); 7 | 8 | /** 9 | * Creates a new metricGraph that has a shared screen for all metricTable, metricBar, and metricLine objects. 10 | * 11 | * @class 12 | */ 13 | function metricGraphs () { 14 | this.screen = blessed.screen(); 15 | this.screen.key(['escape', 'q', 'C-c'], function () { 16 | return process.exit(0); 17 | }); 18 | this.allGraphs = []; 19 | this.metricUnits = {}; 20 | } 21 | 22 | /** 23 | * Adjust the graphs if screen resizes 24 | */ 25 | metricGraphs.prototype.resizeGraphsToScreen = function () { 26 | // Adjust the graphs if screen resizes 27 | var graphs = this.allGraphs; 28 | var graphScreen = this.screen; 29 | this.screen.on('resize', function () { 30 | for (var i = 0; i < graphs.length; i++) { 31 | graphs[i].emit(); 32 | } 33 | graphScreen.render(); 34 | }); 35 | }; 36 | 37 | /** 38 | * Generate the graphs and update the graphs every refreshInterval milliseconds. 39 | */ 40 | metricGraphs.prototype.start = function () { 41 | for (var graph in this.allGraphs) { 42 | var metricGraph = this.allGraphs[graph]; 43 | metricGraph.generateGraph(metricGraph, this.screen); 44 | setInterval(metricGraph.generateGraph, metricGraph.refreshInterval, metricGraph, this.screen); 45 | } 46 | }; 47 | 48 | module.exports.metricGraphs = metricGraphs; 49 | -------------------------------------------------------------------------------- /lib/perf-top/metrics/generate-graphs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | require('console-stamp')(console, '[HH:MM:ss.l]'); 7 | 8 | var dataGenerator = require('./util/generate-data.js'); 9 | var metricBar = require('./util/metric-bar.js'); 10 | var metricGraphs = require('../metric-graphs'); 11 | var metricLine = require('./util/metric-line.js'); 12 | var metricTable = require('./util/metric-table.js'); 13 | 14 | /** 15 | * Initialize all the graph objects and generate them. 16 | * 17 | * @param {object} jsonData - hashmap of dashboard configuration. 18 | */ 19 | function initAndStart (jsonData) { 20 | var graphs = new metricGraphs.metricGraphs(); 21 | dataGenerator.getMetricUnits(jsonData.endpoint, function (metricUnits) { 22 | for (var graphType in jsonData.graphs) { 23 | for (var graphParamOption in jsonData.graphs[graphType]) { 24 | var graphConfig = jsonData.graphs[graphType][graphParamOption]; 25 | var graph; 26 | if ((graphType === 'bars')) { 27 | graph = new metricBar.metricBar(jsonData.endpoint, jsonData.gridOptions, graphConfig.queryParams, 28 | graphConfig.options, graphs.screen); 29 | } else if ((graphType === 'lines')) { 30 | graph = new metricLine.metricLine(jsonData.endpoint, jsonData.gridOptions, graphConfig.queryParams, 31 | graphConfig.options, graphs.screen); 32 | } else if (graphType === 'tables') { 33 | graph = new metricTable.metricTable(jsonData.endpoint, jsonData.gridOptions, graphConfig.queryParams, 34 | graphConfig.options, graphs.screen, metricUnits); 35 | } 36 | graphs.allGraphs.push(graph); 37 | } 38 | } 39 | // Generate graph on screen 40 | graphs.resizeGraphsToScreen(); 41 | graphs.start(); 42 | }); 43 | } 44 | 45 | module.exports.initAndStart = initAndStart; 46 | -------------------------------------------------------------------------------- /lib/perf-top/metrics/util/generate-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var helper = require("../../helper"); 7 | var env = require("../../../env"); 8 | /** 9 | * Makes a HTTP(S) request to "{endpoint}/_plugins/_performanceanalyzer/metrics?metrics=${metrics}&agg=${aggregates}&dim=${dimensions}&nodes=all" 10 | * and parses the response to a hashmap object. 11 | * 12 | * @param {string} endpoint - endpoint for the metric query. 13 | * @param {string} metrics - metric fields to fetch data for. 14 | * @param {string} aggregates - aggregate fields for the metric fields. 15 | * @param {string} dimensions - dimension fields to group the metrics by. 16 | * @param {function(object):void} done - callback to be called when the response is parsed into a hashmap object. 17 | */ 18 | function getMetricData (endpoint, metrics, aggregates, dimensions, done) { 19 | var metricParam = (metrics) ? `metrics=${metrics}` : 'metrics='; 20 | var aggParam = (aggregates) ? `&agg=${aggregates}` : '&agg='; 21 | var dimParam = (dimensions) ? `&dim=${dimensions}` : '&dim='; 22 | 23 | var urlOptions = helper.getURLOptions(endpoint, `${env.getMetricsUrlPrefix()}?${metricParam}${aggParam}${dimParam}&nodes=all`); 24 | 25 | helper.makeRequest(urlOptions, function (response) { 26 | if (response === '') { 27 | done({}); 28 | } else { 29 | done(getDataPerNode(response)); 30 | } 31 | }); 32 | } 33 | 34 | /** 35 | * Makes a HTTP(S) request to "{endpoint}/_plugins/_performanceanalyzer/metrics/units" 36 | * and parses the response to a hashmap object. 37 | * 38 | * @param {string} endpoint - endpoint for the query. 39 | * @param {string} done - callback for the HTTP(S) response. 40 | */ 41 | function getMetricUnits (endpoint, done) { 42 | var urlOptions = helper.getURLOptions(endpoint, `${env.getMetricsUrlPrefix()}/units`); 43 | helper.makeRequest(urlOptions, function (response) { 44 | if (response === '') { 45 | console.error('Failed to retrieve units for metrics. HTTP(S) response was empty.'); 46 | done({}); 47 | } 48 | try { 49 | var jsonData = JSON.parse(response); 50 | if (Object.keys(jsonData).length === 1 && 'error' in jsonData) { 51 | console.error(`Failed to retrieve units for metrics. HTTP(S) response was: 52 | ${response}`); 53 | done({}); 54 | } 55 | done(jsonData); 56 | } catch (e) { 57 | console.error(`HTTP(S) Response for metricUnits was not in JSON format: 58 | ${response}`); 59 | done({}); 60 | } 61 | }); 62 | } 63 | 64 | /** 65 | * Sort a 2D array by the `sortBy` column in the decreasing order. 66 | * 67 | * @param {Array} dimensions - string array that represents the name of the columns for `data`. 68 | * @param {Array} data - 2D array to sort. 69 | * @param {string} sortBy - a string value from `dimensions` to sort the `data` by. 70 | */ 71 | function sortDataByDecreasingOrder (dimensions, data, sortBy) { 72 | var sortByIndex = dimensions.indexOf(sortBy); 73 | data.sort(function (a, b) { 74 | return b[sortByIndex] - a[sortByIndex]; 75 | }); 76 | } 77 | 78 | /** 79 | * Given a 2D array of data, modify all columns with only numbers by adding a comma delimiter to the elements. 80 | * 81 | * @param {Array} data - 2D array to modify. 82 | */ 83 | function addCommaDelimiter (data) { 84 | if (data.length === 0) { 85 | return; 86 | } 87 | var numColumn = data[0].length; 88 | for (var column = 0; column < numColumn; column++) { 89 | var dataColumn = data.map( function(row, y) { return row[column] } ); 90 | if (dataColumn.every( function(dataValue) { return typeof dataValue === 'number' } )) { 91 | for (var row = 0; row < data.length; row++) { 92 | data[row][column] = data[row][column].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 93 | } 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Given a hashmap of dimensions and data per node, flatten it to a hashmap of 1 dimensions array and 1 data 2D array. 100 | * 101 | * @param {object} metricData - hashmap object in the format of 102 | * { nodeName1: { fields: [dim1, dim2] , 103 | * data: [[data1], [data2]], 104 | * timestamp: timestamp }, 105 | * nodeName2: { fields: [dim1, dim2] , 106 | * data: [[data3], ...], 107 | * timestamp: timestamp } } 108 | * @returns {object} - { dimensions: [dim1, dim2, node], 109 | * data: [[data1, nodeName1], [data2, nodeName1], [data3, nodeName2], ...]} 110 | */ 111 | function aggregateMetricData (metricData) { 112 | var allDimensions = new Set(); 113 | var allData = []; 114 | for (var nodeName in metricData) { 115 | if (nodeName === '' || typeof metricData[nodeName] === 'undefined') { 116 | console.error(`Undefined data:\n 117 | ${metricData[nodeName]}`); 118 | continue; 119 | } 120 | metricData[nodeName].fields.forEach(function (dimensions) { 121 | allDimensions.add(dimensions); 122 | }); 123 | 124 | metricData[nodeName].data.forEach(function (data) { 125 | data.push(nodeName); 126 | allData.push(data); 127 | }); 128 | } 129 | allDimensions = Array.from(allDimensions); 130 | allDimensions.push('node'); 131 | 132 | return { dimensions: allDimensions, data: allData }; 133 | } 134 | 135 | /** 136 | * Given a string HTTP(S) response, parse and return the data into a hashmap 137 | * 138 | * @param {object} rawData - string response from the HTTP(S) request that can be parsed into a JSON object. 139 | * @returns {object} - returns a hashmap in the format of 140 | * { nodeName1: { fields: [dim1, dim2, ...] , 141 | * data: [[data], [data], ...], 142 | * timestamp: timestamp }, 143 | * nodeName2: { fields: [dim1, dim2, ...] , 144 | * data: [[data], [data], ...], 145 | * timestamp: timestamp } } 146 | */ 147 | function getDataPerNode (rawData) { 148 | var jsonData = {}; 149 | try { 150 | jsonData = JSON.parse(rawData); 151 | if (Object.keys(jsonData).length === 1 && 'error' in jsonData) { 152 | console.error(`Failed to retrieve data for metrics. HTTP(S) response was: 153 | ${rawData}`); 154 | return {}; 155 | } 156 | } catch (e) { 157 | console.error(`HTTP(S) Response for per-node data was not in JSON format: 158 | ${rawData}`); 159 | return {}; 160 | } 161 | 162 | var allData = {}; 163 | for (var nodeName in jsonData) { 164 | var nodeDimensions = []; 165 | var nodeData = []; 166 | if (!('data' in jsonData[nodeName])) { 167 | console.error(`Data returned for nodeName=${nodeName} was in an unexpected format: 168 | ${JSON.stringify(jsonData[nodeName])}`); 169 | continue; 170 | } 171 | if (!('fields' in jsonData[nodeName].data) && !('records' in jsonData[nodeName].data)) { 172 | console.error(`Data returned for nodeName=${nodeName} was in an unexpected format: 173 | ${JSON.stringify(jsonData[nodeName])}`); 174 | continue; 175 | } 176 | jsonData[nodeName].data.fields.forEach(function (field) { 177 | if (field.name === null) { 178 | field.name = 'N/A'; 179 | } 180 | nodeDimensions.push(field.name); 181 | }); 182 | jsonData[nodeName].data.records.forEach(function (record) { 183 | nodeData.push(record.map(helper.parseNumberData)); 184 | }); 185 | 186 | allData[nodeName] = { fields: nodeDimensions, data: nodeData, timestamp: jsonData[nodeName].timestamp }; 187 | } 188 | return allData; 189 | } 190 | 191 | /** 192 | * Given a hashmap of dimensions and data per node, return the hashmap of data for one node. 193 | * 194 | * @param {object} metricData - hashmap object in the format of 195 | * { nodeName1: { fields: [dim1, dim2] , 196 | * data: [[data1], [data2]], 197 | * timestamp: timestamp }, 198 | * nodeName2: { fields: [dim1, dim2] , 199 | * data: [[data3], ...], 200 | * timestamp: timestamp } } 201 | * @param {object} nodeName - name or prefix of the node to return the data for. 202 | * @returns {object} - { nodeName: { fields: [dim1, dim2] , 203 | * data: [[data1], [data2]], 204 | * timestamp: timestamp } } 205 | 206 | */ 207 | function getNodeData (metricData, nodeName) { 208 | var node = Object.keys(metricData).filter(lineName => lineName.startsWith(nodeName)); 209 | var nodeData = {}; 210 | if (node.length === 0) { 211 | console.error(`No matches for nodeName=${nodeName}`); 212 | } else if (node.length > 1) { 213 | console.error(`Too many matches for nodeName=${nodeName}`); 214 | } else { 215 | nodeData[node[0]] = metricData[node]; 216 | } 217 | return nodeData; 218 | } 219 | 220 | /** 221 | * Given a hashmap of dimensions and data per node and a dimension value, 222 | * return the hashmap of data for the interested dimension(s). 223 | * 224 | * @param {object} metricData - hashmap object in the format of 225 | * { nodeName1: { fields: [dim1, dim2, dim3] , 226 | * data: [[data1], [data2]], 227 | * timestamp: timestamp }, 228 | * nodeName2: { fields: [dim1, dim2, dim3] , 229 | * data: [[data3], ...] 230 | * timestamp: timestamp } } 231 | * @param {Array} dimensionFilters - Values of dimensions to return metrics for 232 | * @returns {object} - { nodeName: { fields: [dim1, dim2] , 233 | * data: [[data1], [data2]], 234 | * timestamp: timestamp } } 235 | */ 236 | function getDimensionData (metricData, dimensionFilters) { 237 | for (var nodeName in metricData) { 238 | if (nodeName === '' || typeof metricData[nodeName] === 'undefined') { 239 | console.error(`Undefined data:\n 240 | ${metricData[nodeName]}`); 241 | continue; 242 | } 243 | var dimensionData = metricData[nodeName].data.filter(data => dimensionFilters.includes(data[0])); 244 | metricData[nodeName].data = dimensionData; 245 | } 246 | return metricData; 247 | } 248 | 249 | /** 250 | * Given a hashmap of dimensions and data per node 251 | * and a hashmap of a counter and a timestamp of the latest data per node, 252 | * remove in-place the any data that has not been updated for 3 iterations. 253 | * 254 | * @param {object} metricData - hashmap object in the format of 255 | * { nodeName1: { fields: [dim1, dim2, dim3] , 256 | * data: [[data1], [data2]], 257 | * timestamp: timestamp } 258 | * nodeName2: { fields: [dim1, dim2, dim3] , 259 | * data: [[data3], ...] 260 | * timestamp: timestamp } } 261 | * @param {Array} dataTimestamp - hashmap object in the format of 262 | * { nodeName1: { counter: 0, 263 | * timestamp: timestamp } 264 | * nodeName2: { counter: 1, 265 | * timestamp: timestamp } } 266 | */ 267 | function removeStaleData(metricData, dataTimestamp) { 268 | for (var nodeName in metricData) { 269 | // Initialize or update `dataTimestamp` 270 | if (nodeName in dataTimestamp) { 271 | if (metricData[nodeName].timestamp > dataTimestamp[nodeName].timestamp) { 272 | dataTimestamp[nodeName] = { counter: 0, timestamp: metricData[nodeName].timestamp}; 273 | } else { 274 | dataTimestamp[nodeName].counter++; 275 | } 276 | } else { 277 | dataTimestamp[nodeName] = { counter: 0, timestamp: metricData[nodeName].timestamp}; 278 | } 279 | // Remove data that has not been updated for 3 iterations 280 | if (dataTimestamp[nodeName].counter >= 3) { 281 | console.error(`Data for node '${nodeName}' has not been updated for ` + 282 | `${dataTimestamp[nodeName].counter} iterations.` + 283 | ` Last updated timestamp was ${dataTimestamp[nodeName].timestamp}.` + 284 | ` Removing the data from the dashboard.`); 285 | delete metricData[nodeName]; 286 | } 287 | } 288 | } 289 | 290 | module.exports.getMetricData = getMetricData; 291 | module.exports.getMetricUnits = getMetricUnits; 292 | module.exports.getNodeData = getNodeData; 293 | module.exports.getDimensionData = getDimensionData; 294 | module.exports.aggregateMetricData = aggregateMetricData; 295 | module.exports.removeStaleData = removeStaleData; 296 | module.exports.sortDataByDecreasingOrder = sortDataByDecreasingOrder; 297 | module.exports.addCommaDelimiter = addCommaDelimiter; 298 | -------------------------------------------------------------------------------- /lib/perf-top/metrics/util/metric-bar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var contrib = require('blessed-contrib'); 7 | var dataGenerator = require('./generate-data.js'); 8 | var queryValidator = require('./validate-query-params.js'); 9 | 10 | /** 11 | * Creates the bar graph, which aggregates data on all nodes unless 'nodeName' defined. 12 | * 13 | * @class 14 | * @param {string} endpoint - search endpoint for the HTTP request. 15 | * @param {object} gridOptions - defines the rows and columns of the screen and position of the graph on the screen. 16 | * @param {object} queryParams - hashmap of parameters for the HTTP request. 17 | * @param {object} options - options for contrib.bar() object. 18 | * @param {object} screen - blessed.screen() object. 19 | */ 20 | function metricBar (endpoint, gridOptions, queryParams, options, screen) { 21 | queryValidator.validateBarQueryParams(queryParams); 22 | this.endpoint = endpoint; 23 | this.metrics = queryParams.metrics; 24 | this.aggregates = queryParams.aggregates; 25 | this.dimensions = queryParams.dimensions; 26 | this.nodeName = queryParams.nodeName; 27 | this.dimensionFilters = queryParams.dimensionFilters; 28 | 29 | this.refreshInterval = (options.refreshInterval > 5000) ? options.refreshInterval : 5000; 30 | 31 | var grid = new contrib.grid({ rows: gridOptions.rows, cols: gridOptions.cols, screen: screen }); 32 | this.bar = grid.set(options.gridPosition.row, options.gridPosition.col, options.gridPosition.rowSpan, 33 | options.gridPosition.colSpan, contrib.bar, options); 34 | 35 | this.dataTimestamp = {}; 36 | } 37 | 38 | /** 39 | * Wrapper to attach the bar object to screen. 40 | */ 41 | metricBar.prototype.emit = function () { 42 | this.bar.emit('attach'); 43 | }; 44 | 45 | /** 46 | * Render the bar graph. 47 | * 48 | * @param {object} bar - metricBar() object. 49 | * @param {object} screen - blessed.screen() object. 50 | */ 51 | metricBar.prototype.generateGraph = function (bar, screen) { 52 | generateMetricBarData(bar, function (barData) { 53 | if (Object.keys(barData).length !== 0) { 54 | bar.bar.setData(barData); 55 | screen.render(); 56 | } else { 57 | console.error(`Metric was not found for request with queryParams:\n 58 | endpoint: ${bar.endpoint}\n 59 | metrics: ${bar.metrics}\n 60 | agg:${bar.aggregates}\n 61 | dim:${bar.dimensions}`); 62 | } 63 | }); 64 | }; 65 | 66 | /** 67 | * Make a HTTP request to fetch data and format the parsed response for the bar graph. 68 | * 69 | * @param {object} metricBar - metricBar() object 70 | * @param {function(object):void} callback - callback to return barData in the format of 71 | * barData = { titles: ['bar1', 'bar2', 'bar3', 'bar4'], 72 | * data: [data1, data2, data3, data4] } 73 | */ 74 | function generateMetricBarData (metricBar, callback) { 75 | dataGenerator.getMetricData(metricBar.endpoint, metricBar.metrics, metricBar.aggregates, metricBar.dimensions, function (metricData) { 76 | // If timestamp of the data is older 3 iterations, remove it. 77 | dataGenerator.removeStaleData(metricData, metricBar.dataTimestamp); 78 | 79 | // Get data for only one node 80 | if (metricBar.nodeName) { 81 | metricData = dataGenerator.getNodeData(metricData, metricBar.nodeName); 82 | } 83 | // Get data for only the matching dimension 84 | if (metricBar.dimensionFilters) { 85 | metricData = dataGenerator.getDimensionData(metricData, metricBar.dimensionFilters); 86 | } 87 | 88 | var aggregatedData = dataGenerator.aggregateMetricData(metricData); 89 | if (Object.keys(aggregatedData).length === 0) { 90 | callback(aggregatedData); 91 | return; 92 | } 93 | 94 | var dimensionIndex = aggregatedData.dimensions.findIndex(dimensionName => dimensionName === metricBar.dimensions); 95 | var metricIndex = aggregatedData.dimensions.findIndex(metricName => metricName === metricBar.metrics); 96 | 97 | // Aggregate (sum) the data value by dimension 98 | var aggData = {}; 99 | aggregatedData.data.forEach(function (data) { 100 | var dataDimension = data[dimensionIndex]; 101 | if (data[metricIndex] === null) { 102 | data[metricIndex] = 0; 103 | } 104 | if (!aggData[dataDimension]) { 105 | aggData[dataDimension] = data[metricIndex]; 106 | } else { 107 | aggData[dataDimension] += data[metricIndex]; 108 | aggData[dataDimension] = Math.round(aggData[dataDimension] * 100) / 100; 109 | } 110 | }); 111 | 112 | // Sort by bar names 113 | var barTitles = Object.keys(aggData); 114 | barTitles.sort(); 115 | 116 | // Format data to a 2D array 117 | var barData = []; 118 | for (var i = 0; i < barTitles.length; i++) { 119 | var title = barTitles[i]; 120 | barData.push(aggData[title]); 121 | } 122 | callback({ titles: barTitles, data: barData }); 123 | }); 124 | } 125 | 126 | module.exports.metricBar = metricBar; 127 | -------------------------------------------------------------------------------- /lib/perf-top/metrics/util/metric-line.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var contrib = require('blessed-contrib'); 7 | var dataGenerator = require('./generate-data.js'); 8 | var queryValidator = require('./validate-query-params.js'); 9 | 10 | /** 11 | * Creates the line graph, which represents data on per node level. 12 | * 13 | * @class 14 | * @param {string} endpoint - search endpoint for the HTTP request. 15 | * @param {object} gridOptions - defines the rows and columns of the screen and position of the graph on the screen. 16 | * @param {object} queryParams - hashmap of parameters for the HTTP request. 17 | * @param {object} options - options for contrib.line() object. 18 | * @param {object} screen - blessed.screen() object. 19 | */ 20 | function metricLine (endpoint, gridOptions, queryParams, options, screen) { 21 | queryValidator.validateLineQueryParams(queryParams); 22 | this.endpoint = endpoint; 23 | this.metrics = queryParams.metrics; 24 | this.aggregates = queryParams.aggregates; 25 | this.dimensions = queryParams.dimensions; 26 | this.nodeName = queryParams.nodeName; 27 | this.dimensionFilters = queryParams.dimensionFilters; 28 | 29 | this.lines = {}; 30 | 31 | this.refreshInterval = (options.refreshInterval > 5000) ? options.refreshInterval : 5000; 32 | 33 | var grid = new contrib.grid({ rows: gridOptions.rows, cols: gridOptions.cols, screen: screen }); 34 | this.line = grid.set(options.gridPosition.row, options.gridPosition.col, options.gridPosition.rowSpan, 35 | options.gridPosition.colSpan, contrib.line, options); 36 | 37 | this.xAxis = options.xAxis; 38 | // Initialize the data with '0's 39 | this.yAxis = Array.apply(null, new Array(this.xAxis.length)).map(Number.prototype.valueOf, 0); 40 | this.colors = options.colors || []; 41 | 42 | this.dataTimestamp = {}; 43 | } 44 | 45 | /** 46 | * Wrapper to attach the line object to screen. 47 | */ 48 | metricLine.prototype.emit = function () { 49 | this.line.emit('attach'); 50 | }; 51 | 52 | /** 53 | * Render the line graph. 54 | * 55 | * @param {object} line - metricLine() object. 56 | * @param {object} screen - blessed.screen() object. 57 | */ 58 | metricLine.prototype.generateGraph = function (line, screen) { 59 | generateAndUpdateLineData(line); 60 | screen.render(); 61 | }; 62 | 63 | /** 64 | * Given an array of lineData, pop the 0th data and append newData value. 65 | * 66 | * @param {Array} lineData - array of data. 67 | * @param {number} newData - most recent data. 68 | */ 69 | function updateLineData (lineData, newData) { 70 | lineData.y.shift(); 71 | lineData.y.push(newData); 72 | return lineData; 73 | } 74 | 75 | /** 76 | * Make a HTTP request to fetch data, parse and format the data, and update the lines with new data. 77 | * Line data will be in the format of 78 | * lineData = { 79 | * line1: { 80 | * title: 'title1', 81 | * x: [t1, t2, t3, t4, ...], // x-axis; defined by metricLine.xAxis 82 | * y: [data1, data2, data3, data4] // new data will be appended to the end of this list and old data popped off. 83 | * }, 84 | * line2: { 85 | * title: 'title2' 86 | * x: [t1, t2, t3, t4, ...], // x-axis; defined by metricLine.xAxis 87 | * y: [data1, data2, data3, data4] // new data will be appended to the end of this list and old data popped off. 88 | * } 89 | * } 90 | * 91 | * @param {object} metricLine - metricLine() object. 92 | */ 93 | function generateAndUpdateLineData (metricLine) { 94 | dataGenerator.getMetricData(metricLine.endpoint, metricLine.metrics, metricLine.aggregates, metricLine.dimensions, 95 | function (metricData) { 96 | // If timestamp of the data is older 3 iterations, remove it. 97 | dataGenerator.removeStaleData(metricData, metricLine.dataTimestamp); 98 | 99 | // Get data for only one node 100 | if (metricLine.nodeName) { 101 | metricData = dataGenerator.getNodeData(metricData, metricLine.nodeName); 102 | } 103 | // Get data for only the matching dimension 104 | if (metricLine.dimensionFilters) { 105 | metricData = dataGenerator.getDimensionData(metricData, metricLine.dimensionFilters); 106 | } 107 | 108 | var lineNames = Object.keys(metricData); 109 | if (lineNames.length === 0) { 110 | console.error(`Metric was not found for request with queryParams:\n 111 | endpoint: ${metricLine.endpoint}\n 112 | metrics: ${metricLine.metrics}\n 113 | agg:${metricLine.aggregates}\n 114 | dim:${metricLine.dimensions}`); 115 | return; 116 | } 117 | 118 | for (var i = 0; i < lineNames.length; i++) { 119 | var lineName = lineNames[i]; 120 | var lineData = metricData[lineName]; 121 | 122 | var metricIndex = lineData.fields.findIndex(metricName => metricName === metricLine.metrics); 123 | 124 | // Aggregate (sum) the data value 125 | var aggData = 0; 126 | lineData.data.forEach(function (data) { 127 | aggData += data[metricIndex]; 128 | }); 129 | aggData = Math.round(aggData * 100) / 100; 130 | 131 | // Initialize the line 132 | if (!Object.keys(metricLine.lines).includes(lineName)) { 133 | metricLine.lines[lineName] = { 134 | title: lineNames[i], 135 | style: { line: randomColor(metricLine.colors) }, 136 | x: metricLine.xAxis, 137 | y: metricLine.yAxis.slice(0, metricLine.yAxis.length) 138 | }; 139 | } 140 | // Update line 141 | updateLineData(metricLine.lines[lineName], aggData); 142 | } 143 | 144 | // Remove line with no data 145 | for (lineName in metricLine.lines) { 146 | if (!(lineName in metricData)) { 147 | delete metricLine.lines[lineName]; 148 | } 149 | } 150 | 151 | var allLines = Object.keys(metricLine.lines).map(function (line) { 152 | return metricLine.lines[line]; 153 | }); 154 | metricLine.line.setData(allLines); 155 | }); 156 | } 157 | 158 | /** 159 | * Return a random color. 160 | * 161 | * @param {Array} colorChoices - Array of random colors (in number format) to choose from. 162 | * If empty, then return a random color from 0-255. 163 | */ 164 | function randomColor (colorChoices) { 165 | if (colorChoices.length === 0) { 166 | return [Math.random() * 255, Math.random() * 255, Math.random() * 255]; 167 | } else { 168 | return colorChoices[Math.floor(Math.random() * colorChoices.length)]; 169 | } 170 | } 171 | 172 | module.exports.metricLine = metricLine; 173 | -------------------------------------------------------------------------------- /lib/perf-top/metrics/util/metric-table.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var contrib = require('blessed-contrib'); 7 | var dataGenerator = require('./generate-data.js'); 8 | var queryValidator = require('./validate-query-params.js'); 9 | 10 | /** 11 | * Creates a table graph, which represents data per node-level. 12 | * 13 | * @class 14 | * @param {string} endpoint - search endpoint for the HTTP request. 15 | * @param {object} gridOptions - defines the rows and columns of the screen and position of the graph on the screen. 16 | * @param {object} queryParams - hashmap of parameters for the HTTP request. 17 | * @param {object} options - options for contrib.table() object. 18 | * @param {object} screen - blessed.screen() object. 19 | * @param {object} metricUnits - hashmap of metrics/dimensions and their units. 20 | */ 21 | function metricTable (endpoint, gridOptions, queryParams, options, screen, metricUnits) { 22 | queryValidator.validateTableQueryParams(queryParams); 23 | this.endpoint = endpoint; 24 | this.metrics = queryParams.metrics; 25 | this.aggregates = queryParams.aggregates; 26 | this.dimensions = queryParams.dimensions; 27 | this.sortBy = queryParams.sortBy; 28 | this.nodeName = queryParams.nodeName; 29 | this.dimensionFilters = queryParams.dimensionFilters; 30 | 31 | this.labels = (this.dimensions + ',' + this.metrics + ',node').split(','); 32 | appendMetricUnits(this.labels, metricUnits); 33 | 34 | options.columnWidth = this.labels.map(label => label.length + 10); 35 | 36 | this.refreshInterval = (options.refreshInterval > 5000) ? options.refreshInterval : 5000; 37 | 38 | var grid = new contrib.grid({ rows: gridOptions.rows, cols: gridOptions.cols, screen: screen }); 39 | this.table = grid.set(options.gridPosition.row, options.gridPosition.col, options.gridPosition.rowSpan, options.gridPosition.colSpan, 40 | contrib.table, options); 41 | 42 | this.dataTimestamp = {}; 43 | } 44 | 45 | /** 46 | * Wrapper to attach the table object to screen. 47 | */ 48 | metricTable.prototype.emit = function () { 49 | this.table.emit('attach'); 50 | }; 51 | 52 | /** 53 | * Render the table graph. 54 | * 55 | * @param {object} table - metricTable object. 56 | * @param {object} screen - blessed.screen() object. 57 | */ 58 | metricTable.prototype.generateGraph = function (table, screen) { 59 | generateMetricTableData(table, function (tableData) { 60 | if (Object.keys(tableData).length !== 0) { 61 | table.table.setData(tableData); 62 | screen.render(); 63 | } else { 64 | console.error(`Metric was not found for request with queryParams:\n 65 | endpoint: ${table.endpoint}\n 66 | metrics: ${table.metrics}\n 67 | agg:${table.aggregates}\n 68 | dim:${table.dimensions}`); 69 | } 70 | }); 71 | }; 72 | 73 | /** 74 | * Make a HTTP request to fetch data and format the parsed response. 75 | * 76 | * @param {object} metricTable - metricTable() object 77 | * @param {function(object):void} callback - callback to return the tableData hashmap in the format of 78 | * tableData = { 79 | * dimensions: ['dim1', 'dim2', 'dim3', 'dim4'], 80 | * data: [ [data1, data2, data3, data4], // for row1 81 | * [data1, data2, data3, data4] // for row2 82 | * ] 83 | * } 84 | */ 85 | function generateMetricTableData (metricTable, callback) { 86 | dataGenerator.getMetricData(metricTable.endpoint, metricTable.metrics, metricTable.aggregates, metricTable.dimensions, 87 | function (metricData) { 88 | // If timestamp of the data is older 3 iterations, remove it. 89 | dataGenerator.removeStaleData(metricData, metricTable.dataTimestamp); 90 | 91 | // Get data for only one node 92 | if (metricTable.nodeName) { 93 | metricData = dataGenerator.getNodeData(metricData, metricTable.nodeName); 94 | } 95 | // Get data for only the matching dimension 96 | if (metricTable.dimensionFilters) { 97 | metricData = dataGenerator.getDimensionData(metricData, metricTable.dimensionFilters); 98 | } 99 | 100 | var aggregatedData = dataGenerator.aggregateMetricData(metricData); 101 | if (Object.keys(aggregatedData).length === 0) { 102 | callback({}); 103 | } else { 104 | dataGenerator.sortDataByDecreasingOrder(aggregatedData.dimensions, aggregatedData.data, metricTable.sortBy); 105 | dataGenerator.addCommaDelimiter(aggregatedData.data); 106 | callback({ 'headers': metricTable.labels, 'data': aggregatedData.data }); 107 | } 108 | }); 109 | } 110 | 111 | /** 112 | * Add '(unit)' to each table column. 113 | * 114 | * @param {object} columns - table. 115 | * @param {object} metricUnits - hashmap object of {column : unit}. 116 | */ 117 | function appendMetricUnits (columns, metricUnits) { 118 | for (var i = 0; i < columns.length; i++) { 119 | if (columns[i] in metricUnits) { 120 | columns[i] = `${columns[i]} (${metricUnits[columns[i]]})`; 121 | } 122 | } 123 | return columns; 124 | } 125 | 126 | module.exports.metricTable = metricTable; 127 | -------------------------------------------------------------------------------- /lib/perf-top/metrics/util/validate-query-params.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | /** 7 | * Validate that query parameters are valid for Bar graphs. 8 | * 9 | * @param {string} queryParams - user-defined query parameter. 10 | */ 11 | function validateBarQueryParams (queryParams) { 12 | if (queryParams.metrics.split(',').length > 1) { 13 | console.error('Only one metric is supported for bar graphs.'); 14 | } 15 | if (queryParams.dimensions.split(',').length > 1) { 16 | console.error('Only one dimension is supported for bar graphs.'); 17 | } 18 | } 19 | 20 | /** 21 | * Validate that query parameters are valid for Linw graphs. 22 | * 23 | * @param {string} queryParams - user-defined query parameter. 24 | */ 25 | function validateLineQueryParams (queryParams) { 26 | if (queryParams.metrics.split(',').length > 1) { 27 | console.error('Only one metric is supported for bar graphs.'); 28 | } 29 | if (queryParams.dimensions.split(',').length > 1) { 30 | console.error('Only one dimension is supported for bar graphs.'); 31 | } 32 | } 33 | 34 | /** 35 | * Validate that query parameters are valid for table graphs. 36 | * 37 | * @param {string} queryParams - user-defined query parameter. 38 | */ 39 | function validateTableQueryParams (queryParams) { 40 | if (!queryParams.sortBy) { 41 | console.error('Provide "sortBy" field for the table graph.'); 42 | } 43 | } 44 | 45 | module.exports.validateTableQueryParams = validateTableQueryParams; 46 | module.exports.validateBarQueryParams = validateBarQueryParams; 47 | module.exports.validateLineQueryParams = validateLineQueryParams; 48 | -------------------------------------------------------------------------------- /lib/perf-top/rca/generate-graphs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | require('console-stamp')(console, '[HH:MM:ss.l]'); 7 | 8 | var temperatureProfileDataGenerator = require('./util/temperature-profile/generate-data.js'); 9 | var metricGraphs = require('../metric-graphs'); 10 | var metricDonut = require('./util/metric-donut'); 11 | var metricLine = require('./util/metric-line.js'); 12 | var metricTable = require('./util/metric-table.js'); 13 | 14 | /** 15 | * Initialize all the graph objects and generate RCA analysis. 16 | * 17 | * @param {object} jsonData - hashmap of dashboard configuration. 18 | */ 19 | function initAndStart (jsonData) { 20 | var graphs = new metricGraphs.metricGraphs(); 21 | var dataGenerator = selectDataGenerator(jsonData); 22 | var queryParams = jsonData.queryParams; 23 | for (var i = 0; i < jsonData.graphs.length; i++) { 24 | var graphConfig = jsonData.graphs[i]; 25 | var graphType = graphConfig.graphType; 26 | if ((graphType === 'donuts')) { 27 | graph = new metricDonut.metricDonut(jsonData.endpoint, jsonData.gridOptions, queryParams, 28 | graphConfig.options, graphConfig.dimension, graphConfig.graphParams, dataGenerator, graphs.screen); 29 | } else if ((graphType === 'lines')) { 30 | graph = new metricLine.metricLine(jsonData.endpoint, jsonData.gridOptions, queryParams, 31 | graphConfig.options, graphConfig.dimension, graphConfig.graphParams, dataGenerator, graphs.screen); 32 | } else if (graphType === 'tables') { 33 | graph = new metricTable.metricTable(jsonData.endpoint, jsonData.gridOptions, queryParams, 34 | graphConfig.options, graphConfig.dimension, graphConfig.graphParams, dataGenerator, graphs.screen); 35 | } 36 | graphs.allGraphs.push(graph); 37 | } 38 | // Generate graph on screen 39 | graphs.resizeGraphsToScreen(); 40 | graphs.start(); 41 | } 42 | 43 | /** 44 | * Select the data generator based on the query name in jsonData 45 | * 46 | * @param {object} jsonData - hashmap of dashboard configuration. 47 | */ 48 | function selectDataGenerator(jsonData){ 49 | switch(jsonData.queryParams.name){ 50 | case 'AllTemperatureDimensions': 51 | return temperatureProfileDataGenerator; 52 | // case 'HighHeapUsageClusterRca': 53 | // return HeapUsageDataGenerator; 54 | default: 55 | return temperatureProfileDataGenerator; 56 | } 57 | } 58 | 59 | module.exports.initAndStart = initAndStart; 60 | -------------------------------------------------------------------------------- /lib/perf-top/rca/util/metric-donut.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | * 8 | * Modifications Copyright OpenSearch Contributors. See 9 | * GitHub history for details. 10 | */ 11 | 12 | /* 13 | * Copyright <2019> Amazon.com, Inc. or its affiliates. All Rights Reserved. 14 | * 15 | * Licensed under the Apache License, Version 2.0 (the "License"). 16 | * You may not use this file except in compliance with the License. 17 | * A copy of the License is located at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * or in the "license" file accompanying this file. This file is distributedistable 22 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 23 | * express or implied. See the License for the specific language governing 24 | * permissions and limitations under the License. 25 | */ 26 | 27 | var contrib = require('blessed-contrib'); 28 | 29 | /** 30 | * Creates the donut graph, which aggregates data on all nodes unless 'nodeName' defined. 31 | * 32 | * @class 33 | * @param {string} endpoint - search endpoint for the HTTP request. 34 | * @param {object} gridOptions - defines the rows and columns of the screen and position of the graph on the screen. 35 | * @param {object} queryParams - hashmap of parameters for the HTTP request. 36 | * @param {object} options - options for contrib.donut() object. 37 | * @param {string} dimension - dimension of RCA to be analyzed. 38 |  * @param {string} graphParams - the parameter to be rendered in the graph. 39 |  * @param {object} dataGenerator - the data generator for the rca dimension 40 | * @param {object} screen - blessed.screen() object. 41 | */ 42 | function metricDonut (endpoint, gridOptions, queryParams, options, dimension, graphParams, dataGenerator, screen) { 43 | this.endpoint = endpoint; 44 | this.name = queryParams.name; 45 | this.local = queryParams.local; 46 | this.dimension = dimension; 47 | this.graphParams = graphParams; 48 | this.dataGenerator = dataGenerator; 49 | this.refreshInterval = (options.refreshInterval > 5000) ? options.refreshInterval : 5000; 50 | 51 | var grid = new contrib.grid({ rows: gridOptions.rows, cols: gridOptions.cols, screen: screen }); 52 | this.donut = grid.set(options.gridPosition.row, options.gridPosition.col, options.gridPosition.rowSpan, options.gridPosition.colSpan, contrib.donut, options); 53 | 54 | this.dataTimestamp = {}; 55 | } 56 | 57 | 58 | /** 59 | * Wrapper to attach the donut object to screen. 60 | */ 61 | metricDonut.prototype.emit = function () { 62 | this.donut.emit('attach'); 63 | }; 64 | 65 | /** 66 | * Render the donut graph. 67 | * 68 | * @param {object} donut - metricDonut() object. 69 | * @param {object} screen - blessed.screen() object. 70 | */ 71 | metricDonut.prototype.generateGraph = function (donut, screen) { 72 | generateMetricDonutData(donut, function (donutData) { ///problem 73 | if (Object.keys(donutData).length !== 0) { 74 | donut.donut.setData(donutData); 75 | screen.render(); 76 | } else { 77 | console.error(`Metric was not found for request with queryParams:\n 78 | endpoint: ${donut.endpoint}\n 79 | name: ${donut.name}\n 80 | local: ${donut.local}\n`); 81 | } 82 | }); 83 | }; 84 | 85 | /** 86 | * Make a HTTP request to fetch data and format the parsed response for the donut graph. 87 | * 88 | * @param {object} metricDonut - metricDonut() object. 89 | * @param {function(object):void} callback - callback to return donutData in the format of 90 | * donutData = [ 91 | * { percent: Int, label: String, 'color': String }, 92 | * { percent: Int, label: String, 'color': String } 93 | * ] 94 | */ 95 | function generateMetricDonutData (metricDonut, callback) { 96 | var dataGenerator = metricDonut.dataGenerator; 97 | dataGenerator.getMetricData(metricDonut.endpoint, metricDonut.name, metricDonut.local, metricDonut.dimension, metricDonut.graphParams, "donuts", function (metricData) { 98 | dataGenerator.removeStaleData(metricData, metricDonut); 99 | var color = ["red", "yellow", "green", "blue"]; 100 | var donutData = []; 101 | var count = metricData.data.reduce(function(a, b) { 102 | return a + b; 103 | }, 0); 104 | for (var i = 0; i < metricData.fields.length; i++) { 105 | var donutLabel = metricData.fields[i]; 106 | var donutColor = color[i]; 107 | var donutPercentage = (metricData.data[i] / count) * 100; 108 | donutData.push({percent: donutPercentage, label: donutLabel, color: donutColor}); 109 | } 110 | callback(donutData); 111 | }); 112 | } 113 | 114 | module.exports.metricDonut = metricDonut; 115 | -------------------------------------------------------------------------------- /lib/perf-top/rca/util/metric-line.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var contrib = require('blessed-contrib'); 7 | 8 | /** 9 | * Creates the line graph, which represents data on per node level. 10 | * 11 | * @class 12 | * @param {string} endpoint - search endpoint for the HTTP request. 13 | * @param {object} gridOptions - defines the rows and columns of the screen and position of the graph on the screen. 14 | * @param {object} queryParams - hashmap of parameters for the HTTP request. 15 | * @param {object} options - options for contrib.line() object. 16 | * @param {string} dimension - dimension of RCA to be analyzed. 17 |  * @param {string} graphParams - the parameter to be rendered in the graph. 18 |  * @param {object} dataGenerator - the data generator for the rca dimension 19 | * @param {object} screen - blessed.screen() object. 20 | */ 21 | function metricLine (endpoint, gridOptions, queryParams, options, dimension, graphParams, dataGenerator, screen) { 22 | this.endpoint = endpoint; 23 | this.name = queryParams.name; 24 | this.local = queryParams.local; 25 | this.dimension = dimension; 26 | this.graphParams = graphParams; 27 | this.lines = {}; 28 | this.dataGenerator = dataGenerator; 29 | this.refreshInterval = (options.refreshInterval > 5000) ? options.refreshInterval : 5000; 30 | 31 | var grid = new contrib.grid({ rows: gridOptions.rows, cols: gridOptions.cols, screen: screen }); 32 | this.line = grid.set(options.gridPosition.row, options.gridPosition.col, options.gridPosition.rowSpan, 33 | options.gridPosition.colSpan, contrib.line, options); 34 | 35 | this.xAxis = options.xAxis; 36 | this.yAxis = Array.apply(null, new Array(this.xAxis.length)).map(Number.prototype.valueOf, 0); 37 | this.colors = options.colors || []; 38 | 39 | this.dataTimestamp = {}; 40 | } 41 | 42 | /** 43 | * Wrapper to attach the line object to screen. 44 | */ 45 | metricLine.prototype.emit = function () { 46 | this.line.emit('attach'); 47 | }; 48 | 49 | /** 50 | * Render the line graph. 51 | * 52 | * @param {object} line - metricLine() object. 53 | * @param {object} screen - blessed.screen() object. 54 | */ 55 | metricLine.prototype.generateGraph = function (line, screen) { 56 | generateAndUpdateLineData(line); 57 | screen.render(); 58 | }; 59 | 60 | /** 61 | * Given an array of lineData, pop the 0th data and append newData value. 62 | * 63 | * @param {Array} lineData - array of data. 64 | * @param {number} newData - most recent data. 65 | */ 66 | function updateLineData (lineData, newData) { 67 | lineData.y.shift(); 68 | lineData.y.push(newData); 69 | return lineData; 70 | } 71 | 72 | /** 73 | * Make a HTTP request to fetch data, parse and format the data, and update the lines with new data. 74 | * Line data will be in the format of 75 | * lineData = { 76 | * line1: { 77 | * title: 'title1', 78 | * x: [t1, t2, t3, t4, ...], // x-axis; defined by metricLine.xAxis 79 | * y: [data1, data2, data3, data4] // new data will be appended to the end of this list and old data popped off. 80 | * }, 81 | * line2: { 82 | * title: 'title2' 83 | * x: [t1, t2, t3, t4, ...], // x-axis; defined by metricLine.xAxis 84 | * y: [data1, data2, data3, data4] // new data will be appended to the end of this list and old data popped off. 85 | * } 86 | * } 87 | * 88 | * @param {object} metricLine - metricLine() object. 89 | */ 90 | function generateAndUpdateLineData (metricLine) { 91 | var dataGenerator = metricLine.dataGenerator; 92 | dataGenerator.getMetricData(metricLine.endpoint, metricLine.name, metricLine.local, metricLine.dimension, metricLine.graphParams, "lines", 93 | function (metricData) { 94 | if (metricData.fields === null || metricData.fields.length === 0) { 95 | console.error(`Metric was not found for request with queryParams:\n 96 | endpoint: ${metricLine.endpoint}\n 97 | name: ${metricLine.name}\n 98 | local: ${metricLine.local}\n`); 99 | return; 100 | } 101 | dataGenerator.removeStaleData(metricData, metricLine); 102 | var field = metricLine.dimension + " - " + metricLine.graphParams; 103 | if (!Object.keys(metricLine.lines).includes(field)) { 104 | metricLine.lines[field] = { 105 | title: metricLine.graphParams, 106 | style: { line: randomColor(metricLine.colors) }, 107 | x: metricLine.xAxis, 108 | y: metricLine.yAxis.slice(0, metricLine.yAxis.length) 109 | }; 110 | } 111 | updateLineData(metricLine.lines[field], metricData.data[0]); 112 | for (lineName in metricLine.lines) { 113 | if (!(metricData.fields.includes(lineName))) { 114 | delete metricLine.lines[lineName]; 115 | } 116 | } 117 | var allLines = metricLine.lines[field]; 118 | metricLine.line.setData(allLines); 119 | }); 120 | } 121 | 122 | 123 | /** 124 | * Return a random color. 125 | * 126 | * @param {Array} colorChoices - Array of random colors (in number format) to choose from. 127 | * If empty, then return a random color from 0-255. 128 | */ 129 | function randomColor (colorChoices) { 130 | if (colorChoices.length === 0) { 131 | return [Math.random() * 255, Math.random() * 255, Math.random() * 255]; 132 | } else { 133 | return colorChoices[Math.floor(Math.random() * colorChoices.length)]; 134 | } 135 | } 136 | 137 | module.exports.metricLine = metricLine; 138 | -------------------------------------------------------------------------------- /lib/perf-top/rca/util/metric-table.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var contrib = require('blessed-contrib'); 7 | 8 | /** 9 | * Creates a table graph, which represents data per node-level. 10 | * 11 | * @class 12 | * @param {string} endpoint - search endpoint for the HTTP request. 13 | * @param {object} gridOptions - defines the rows and columns of the screen and position of the graph on the screen. 14 | * @param {object} queryParams - hashmap of parameters for the HTTP request. 15 | * @param {object} options - options for contrib.table() object. 16 | * @param {string} dimension - dimension of RCA to be analyzed. 17 |  * @param {string} graphParams - the parameter to be rendered in the graph. 18 |  * @param {object} dataGenerator - the data generator for the rca dimension 19 | * @param {object} screen - blessed.screen() object. 20 | */ 21 | function metricTable (endpoint, gridOptions, queryParams, options, dimension, graphParams, dataGenerator, screen) { 22 | this.endpoint = endpoint; 23 | this.name = queryParams.name; 24 | this.local = queryParams.local; 25 | this.dimension = dimension; 26 | this.graphParams = graphParams; 27 | this.labels = (options.columns).split(','); 28 | this.dataGenerator = dataGenerator; 29 | options.columnWidth = this.labels.map(label => label.length + 10); 30 | this.refreshInterval = (options.refreshInterval > 5000) ? options.refreshInterval : 5000; 31 | 32 | var grid = new contrib.grid({ rows: gridOptions.rows, cols: gridOptions.cols, screen: screen }); 33 | this.table = grid.set(options.gridPosition.row, options.gridPosition.col, options.gridPosition.rowSpan, options.gridPosition.colSpan, 34 | contrib.table, options); 35 | 36 | this.dataTimestamp = {}; 37 | } 38 | 39 | /** 40 | * Wrapper to attach the table object to screen. 41 | */ 42 | metricTable.prototype.emit = function () { 43 | this.table.emit('attach'); 44 | }; 45 | 46 | /** 47 | * Render the table graph. 48 | * 49 | * @param {object} table - metricTable object. 50 | * @param {object} screen - blessed.screen() object. 51 | */ 52 | metricTable.prototype.generateGraph = function (table, screen) { 53 | generateMetricTableData(table, function (tableData) { 54 | if (Object.keys(tableData).length !== 0) { 55 | table.table.setData(tableData); 56 | screen.render(); 57 | } else { 58 | console.error(`Metric was not found for request with queryParams:\n 59 | endpoint: ${table.endpoint}\n 60 | name: ${table.name}\n 61 | local: ${table.local}\n`); 62 | } 63 | }); 64 | }; 65 | 66 | /** 67 | * Make a HTTP request to fetch data and format the parsed response. 68 | * 69 | * @param {object} metricTable - metricTable() object. 70 | * @param {function(object):void} callback - callback to return the tableData hashmap in the format of 71 | * tableData = { 72 | * headers: ['header1', 'header2', 'header3', 'header4'], 73 | * data: [ [data1, data2, data3, data4], // for row1 74 | * [data1, data2, data3, data4] // for row2 75 | * ] 76 | * } 77 | */ 78 | function generateMetricTableData (metricTable, callback) { 79 | var dataGenerator = metricTable.dataGenerator; 80 | dataGenerator.getMetricData(metricTable.endpoint, metricTable.name, metricTable.local, metricTable.dimension, metricTable.graphParams, "tables", 81 | function (metricData) { 82 | if (metricData.fields === null || metricData.fields.length === 0) { 83 | callback({}); 84 | } 85 | dataGenerator.removeStaleData(metricData, metricTable); 86 | if (metricData.fields.length === 0) { 87 | callback({}); 88 | } else { 89 | for (var i = 0; i < metricTable.labels.length; i++) { 90 | if (!(metricData.fields.includes(metricTable.labels[i]))) { 91 | for ( var j = 0; j < metricData.data.length; j++) { 92 | metricData.data[j].splice(i, 0, 0); 93 | } 94 | } 95 | } 96 | callback({ 'headers': metricTable.labels, 'data': metricData.data }); 97 | } 98 | }); 99 | } 100 | 101 | module.exports.metricTable = metricTable; 102 | -------------------------------------------------------------------------------- /lib/perf-top/rca/util/temperature-profile/generate-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | var helper = require('../../../helper.js'); 7 | var env = require('../../../../env.js'); 8 | /** 9 | * Makes a HTTP(S) request to "{endpoint}/_plugins/_performanceanalyzer/rca?${nameParam}${localParam}" 10 | * and parses the response to a hashmap object. 11 | * 12 | * @param {string} endpoint - endpoint for the metric query. 13 | * @param {string} name - name of the parameter, default as AllTemperatureDimensions 14 | * @param {boolean} local - running analysis in local 15 | * @param {string} dimension - dimension of RCA to be analyzed 16 |  * @param {string} graphParams - the parameter to be rendered in the graph 17 |  * @param {string} type - the type of the graph 18 | * @param {function(object):void} done - callback to be called when the response is parsed into a hashmap object. 19 | */ 20 | function getMetricData(endpoint, name, local, dimension, graphParams, type, done) { 21 | var nameParam = name ? `name=${name}` : "name=AllTemperatureDimensions"; 22 | var localParam = local ? `&local=${local}` : "&local=true"; 23 | var urlOptions = helper.getURLOptions( 24 | endpoint, 25 | `${env.getRcaUrlPrefix()}?${nameParam}${localParam}` 26 | ); 27 | 28 | helper.makeRequest(urlOptions, function (response) { 29 | if (response === "") { 30 | console.error("empty response from server"); 31 | done({}); 32 | } else { 33 | done(getData(response, dimension, graphParams, type)); 34 | } 35 | }); 36 | } 37 | 38 | /** 39 | * Given a string HTTP(S) response, parse and return the data into a hashmap. 40 | * 41 | * @param {object} rawData - string response from the HTTP(S) request that can be parsed into a JSON object. 42 | * @param {string} dimension - dimension to be analyzed 43 | * @param {string} graphParams - graph parameter to be plotted 44 | * @param {string} type - graph type 45 | * @returns {object} - returns a hashmap in the format of 46 | * { fields: [dim1, dim2, ...] , 47 | * data: [ data1, data2, ...], 48 | * timestamp: timestamp 49 | * } 50 | */ 51 | function getData(rawData, dimension, graphParams, type) { 52 | var jsonData = {}; 53 | try { 54 | jsonData = JSON.parse(rawData).AllTemperatureDimensions[0].NodeLevelDimensionalSummary; 55 | if (jsonData.length === 0 || jsonData === undefined) { 56 | console.error(`Failed to retrieve data for metrics. HTTP(S) response was: 57 | ${rawData}`); 58 | return {}; 59 | } 60 | if (graphParams.split(",").length > 1) { 61 | console.error(`Too many graph parameters to plot`); 62 | return {}; 63 | } 64 | } catch (e) { 65 | console.error(`HTTP(S) Response for per-node data was not in JSON format: 66 | ${rawData}`); 67 | return {}; 68 | } 69 | 70 | var nodeDimensions = []; 71 | var nodeData = []; 72 | var nodeTimestamp; 73 | var allData = {}; 74 | for (var i = 0; i < jsonData.length; i++) { 75 | var node = jsonData[i]; 76 | if (!("dimension" in node)) { 77 | console.error(`Data returned was in an unexpected format: 78 | ${JSON.stringify(node)}`); 79 | continue; 80 | } 81 | if (!dimension.includes(node.dimension)) continue; 82 | nodeTimestamp = node.timestamp; 83 | var zoneSummary = node.NodeLevelZoneSummary; 84 | 85 | if (type === "lines") { 86 | var field = node.dimension + " - " + graphParams; 87 | nodeDimensions.push(field); 88 | if (node.hasOwnProperty(graphParams)) { 89 | nodeData.push(helper.parseNumberData(node[graphParams])); 90 | } else { 91 | nodeData.push("null"); 92 | } 93 | } else if (type === "tables") { 94 | for (var j = 0; j < zoneSummary.length; j++) { 95 | if (zoneSummary[j].zone === graphParams) { 96 | var target = zoneSummary[j]; 97 | if (target.all_shards.length > 0) { 98 | nodeDimensions.push('index_name'); 99 | nodeDimensions.push("shard_id"); 100 | target.all_shards[0].temperature.forEach(function (field) { 101 | nodeDimensions.push(field.dimension); 102 | }); 103 | for (var k = 0; k < target.all_shards.length; k++) { 104 | var tmp = target.all_shards[k]; 105 | var tmpData = []; 106 | tmpData.push(tmp.index_name); 107 | tmpData.push(tmp.shard_id); 108 | tmp.temperature.forEach(function (data) { 109 | tmpData.push(helper.parseNumberData(data.value)); 110 | }); 111 | nodeData.push(tmpData); 112 | } 113 | } 114 | break; 115 | } 116 | } 117 | } else if (type == "donuts") { 118 | for (var j = 0; j < zoneSummary.length; j++) { 119 | nodeDimensions.push(zoneSummary[j].zone); 120 | nodeData.push(helper.parseNumberData(zoneSummary[j].all_shards.length)); 121 | } 122 | } 123 | break; 124 | } 125 | try { 126 | allData = { 127 | fields: nodeDimensions, 128 | data: nodeData, 129 | timestamp: nodeTimestamp, 130 | }; 131 | } catch (e) { 132 | console.error(`error find: ${e}`); 133 | } 134 | return allData; 135 | } 136 | 137 | /** 138 | * Given a hashmap of dimensions and data, 139 | * and an object containing a hashmap of a counter and a timestamp of each field, 140 | * remove in-place the any data that has not been updated for 3 iterations. 141 | * 142 | * @param {object} metricData - hashmap object in the format of 143 | * { fields: [dim1, dim2, ...] , 144 | * data: [ data1, data2, ...], 145 | * timestamp: timestamp} 146 | * @param {object} metricGraph - Object contains a hashmap object in the format of 147 | * { field1: { counter: 0, 148 | * timestamp: timestamp } 149 | * field2: { counter: 1, 150 | * timestamp: timestamp } } 151 | */ 152 | function removeStaleData(metricData, metricGraph) { 153 | var dataTimestamp = metricGraph.dataTimestamp; 154 | var field = metricGraph.dimension + " - " + metricGraph.graphParams; 155 | if (field in dataTimestamp) { 156 | if (metricData.timestamp > dataTimestamp[field].timestamp) { 157 | dataTimestamp[field] = { counter: 0, timestamp: metricData.timestamp }; 158 | } else { 159 | dataTimestamp[field].counter++; 160 | } 161 | } else { 162 | dataTimestamp[field] = { counter: 0, timestamp: metricData.timestamp }; 163 | } 164 | 165 | /* uncomment this chunk of code when the response becomes dynamic 166 | if (dataTimestamp[field].counter >= 3) { 167 | console.error(`Data ${fields} has not been updated for ` + 168 | `${dataTimestamp[field].counter} iterations.` + 169 | ` Last updated timestamp was ${dataTimestamp[field].timestamp}.` + 170 | ` Removing the data from the dashboard.`); 171 | delete metricData; 172 | } 173 | */ 174 | } 175 | 176 | module.exports.getMetricData = getMetricData; 177 | module.exports.removeStaleData = removeStaleData; 178 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aws/opensearch-perftop", 3 | "version": "1.0.0", 4 | "description": "PerfTop CLI tool for Open Distro Performance Analyzer", 5 | "author": "Amazon Web Services", 6 | "bin": { 7 | "opensearch-perf-top": "./bin/global.js" 8 | }, 9 | "preferGlobal": "true", 10 | "scripts": { 11 | "build-linux": "./node_modules/.bin/pkg . --targets linux --output ./build/opensearch-perf-top-linux", 12 | "build-macos": "./node_modules/.bin/pkg . --targets macos --output ./build/opensearch-perf-top-macos", 13 | "clean": "rm -rf ./build 2>/dev/null || true && rm -rf ./node_modules", 14 | "lint": "./node_modules/.bin/eslint lib/opensearch-perf-top/ lib/bin.js bin/global.js", 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/opensearch-project/perftop" 20 | }, 21 | "license": "Apache-2.0", 22 | "dependencies": { 23 | "argparse": "1.0.10", 24 | "blessed": "0.1.54", 25 | "blessed-contrib": "^4.11.0", 26 | "console-stamp": "3.0.2", 27 | "hosted-git-info": "2.8.9", 28 | "lodash": "4.17.21", 29 | "merge": "2.1.1", 30 | "pkg": "^4.4.9" 31 | }, 32 | "pkg": { 33 | "assets": "node_modules/blessed/**/*" 34 | }, 35 | "devDependencies": { 36 | "eslint": "^7.0.0", 37 | "eslint-config-standard": "^12.0.0", 38 | "eslint-plugin-import": "^2.16.0", 39 | "eslint-plugin-node": "^8.0.1", 40 | "eslint-plugin-promise": "^4.0.1", 41 | "eslint-plugin-standard": "^4.0.0" 42 | }, 43 | "engines": { 44 | "node": ">=10.0.0 <11.0.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch-perftop.release-notes-1.10.0.0.md: -------------------------------------------------------------------------------- 1 | ## 2020-08-24 Version 1.10.0.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.10. 4 | Supported Elasticsearch version 7.9.0 5 | 6 | ### Features 7 | * Add github badges ([#47](https://github.com/opendistro-for-elasticsearch/perftop/pull/47)) 8 | 9 | ### Bug fixes 10 | * Bump acorn from 6.0.6 to 6.4.1 ([#39](https://github.com/opendistro-for-elasticsearch/perftop/pull/39)) 11 | * Fix vulnerabilities ([#54](https://github.com/opendistro-for-elasticsearch/perftop/pull/54)) 12 | 13 | ### Documentation 14 | * Add release notes for 1.10 release ([#57](https://github.com/opendistro-for-elasticsearch/perftop/pull/57)) 15 | 16 | ### Maintenance 17 | * Build against elasticsearch 7.9 ([#56](https://github.com/opendistro-for-elasticsearch/perftop/pull/56)) -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch-perftop.release-notes-1.10.1.0.md: -------------------------------------------------------------------------------- 1 | ## 2020-09-03 Version 1.10.1.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.10.1. 4 | Supported Elasticsearch version 7.9.1 5 | 6 | ### Features 7 | * Add github badges ([#47](https://github.com/opendistro-for-elasticsearch/perftop/pull/47)) 8 | 9 | ### Bug fixes 10 | * Bump acorn from 6.0.6 to 6.4.1 ([#39](https://github.com/opendistro-for-elasticsearch/perftop/pull/39)) 11 | * Fix vulnerabilities ([#54](https://github.com/opendistro-for-elasticsearch/perftop/pull/54)) 12 | 13 | ### Documentation 14 | * Add release notes for 1.10 release ([#57](https://github.com/opendistro-for-elasticsearch/perftop/pull/57)) 15 | * Update release notes for 1.10.1 release ([#60](https://github.com/opendistro-for-elasticsearch/perftop/pull/60)) 16 | 17 | ### Maintenance 18 | * Build against elasticsearch 7.9 ([#56](https://github.com/opendistro-for-elasticsearch/perftop/pull/56)) 19 | * Build against elasticsearch 7.9.1 ([#59](https://github.com/opendistro-for-elasticsearch/perftop/pull/59)) 20 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-0.7.0.md: -------------------------------------------------------------------------------- 1 | ## Version 0.7.0 (2019-01-31) 2 | 3 | ### New Features 4 | 5 | This is the first release of the Open Distro PerfTop CLI tool. 6 | 7 | PerfTop enables users to visualize and monitor Elasticsearch state in real time using metrics from the Open Distro Performance Analyzer using a sleek Curses-based interface. It is based on Javascript. The dashboard is fully configurable, with options ranging from tables to bar and line graphs. PerfTop comes with four preset dashboard configuration JSON files. The source builds executable binaries for Linux and MacOS. 8 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-0.8.0.md: -------------------------------------------------------------------------------- 1 | ## Version 0.8.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 0.8. 4 | 5 | ### New Features 6 | 7 | * Extend installation capabilities via NPM [#6](https://github.com/opendistro-for-elasticsearch/perftop/pull/6) [#9](https://github.com/opendistro-for-elasticsearch/perftop/pull/9) 8 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-0.9.0.md: -------------------------------------------------------------------------------- 1 | ## Version 0.9.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 0.9. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.0.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.0.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.0. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.1.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.1.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.1. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.11.0.0.md: -------------------------------------------------------------------------------- 1 | ## 2020-10-15 Version 1.11.0.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.11. 4 | Supported Elasticsearch version 7.9.1 5 | 6 | ### Features 7 | * Feature temperature rca ([#52](https://github.com/opendistro-for-elasticsearch/perftop/pull/52)) 8 | 9 | ### Bug fixes 10 | 11 | ### Documentation 12 | 13 | ### Maintenance 14 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.12.0.0.md: -------------------------------------------------------------------------------- 1 | ## 2020-11-19 Version 1.12.0.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.12 4 | Supported Elasticsearch version 7.10.0 5 | 6 | ### Features 7 | 8 | ### Bug fixes 9 | 10 | ### Documentation 11 | 12 | ### Maintenance 13 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.13.0.0.md: -------------------------------------------------------------------------------- 1 | ## 2021-02-01 Version 1.13.0.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.13 4 | Supported Elasticsearch version 7.10.2 5 | 6 | ### Features 7 | 8 | ### Bug fixes 9 | 10 | ### Documentation 11 | 12 | ### Maintenance 13 | * Update the Perftop Package with new naming Convention for ODFE ([68](https://github.com/opendistro-for-elasticsearch/perftop/pull/68)) 14 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.2.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.2.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.2. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.2.1.md: -------------------------------------------------------------------------------- 1 | ## Version 1.2.1 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.2.1. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.3.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.3.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.3. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.4.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.4.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.4. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.6.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.6.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.6. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.7.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.7.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.7. 4 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.8.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.8.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.8. 4 | 5 | ### New Features 6 | 7 | * Support connecting to clusters with basic-auth [#35](https://github.com/opendistro-for-elasticsearch/perftop/pull/35) (contribution from [@keety](https://github.com/keety)) 8 | -------------------------------------------------------------------------------- /release-notes/opendistro-for-elasticsearch.perftop.release-notes-1.9.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.9.0 2 | 3 | This is release of the Open Distro PerfTop, which is compatible with Open Distro Performance Analyzer 1.9. -------------------------------------------------------------------------------- /release-notes/opensearch-perftop.release-notes-1.0.0.0-beta1.md: -------------------------------------------------------------------------------- 1 | ## 2021-04-26 Version 1.0.0.0-beta1 Release Notes (Current) 2 | 3 | Compatible with OpenSearch 1.0.0-beta1 4 | 5 | ### Infrastructure 6 | 7 | * OpenSearch fork changes ([#1](https://github.com/opensearch-project/perftop/pull/1)) 8 | * Add perfTop github CI on main branch ([#3](https://github.com/opensearch-project/perftop/pull/3)) 9 | * Update workflow and change opensearch version ([#4](https://github.com/opensearch-project/perftop/pull/4)) 10 | * Update plugin name and add release notes ([#5](https://github.com/opensearch-project/perftop/pull/5)) 11 | 12 | ### Documentation 13 | 14 | * Add SPDX license identifier ([#2](https://github.com/opensearch-project/perftop/pull/2)) 15 | -------------------------------------------------------------------------------- /release-notes/opensearch-perftop.release-notes-1.0.0.0-rc1.md: -------------------------------------------------------------------------------- 1 | ## Version 1.0.0.0-rc1 Release Notes 2 | 3 | Compatible with OpenSearch 1.0.0-rc1 4 | ### Enhancements 5 | * Add env.js to store global property ([#10](https://github.com/opensearch-project/perftop/pull/10)) 6 | 7 | ### Infrastructure 8 | * Update version to rc1 ([#10](https://github.com/opensearch-project/perftop/pull/10)) 9 | 10 | ### Maintenance 11 | * Upgrade the version of vulnerable dependencies ([#8](https://github.com/opensearch-project/perftop/pull/8)) 12 | * Add REST API backward compatibility ([#10](https://github.com/opensearch-project/perftop/pull/10)) 13 | 14 | ### Documentation 15 | * Update issue template with multiple labels([#7](https://github.com/opensearch-project/perftop/pull/7)) 16 | * Update OpenSearch documents link ([#9](https://github.com/opensearch-project/perftop/pull/9)) 17 | * Update REST endpoint in document ([#10](https://github.com/opensearch-project/perftop/pull/10)) -------------------------------------------------------------------------------- /release-notes/opensearch-perftop.release-notes-1.0.0.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.0.0.0 Release Notes 2 | 3 | Compatible with OpenSearch 1.0.0 4 | 5 | ### Enhancements 6 | * Add env.js to store global property ([#10](https://github.com/opensearch-project/perftop/pull/10)) 7 | 8 | ### Infrastructure 9 | * Update version to rc1 ([#10](https://github.com/opensearch-project/perftop/pull/10)) 10 | * Standardize processes across all plugins - Checklist items ([#13](https://github.com/opensearch-project/perftop/pull/13)) 11 | 12 | ### Maintenance 13 | * Upgrade the version of vulnerable dependencies ([#8](https://github.com/opensearch-project/perftop/pull/8)) 14 | * Add REST API backward compatibility ([#10](https://github.com/opensearch-project/perftop/pull/10)) 15 | * Bump glob-parent from 5.1.1 to 5.1.2 ([#12](https://github.com/opensearch-project/perftop/pull/12)) 16 | * Update OS upstream version and add release notes for 1.0.0.0 release ([#14](https://github.com/opensearch-project/perftop/pull/14)) 17 | 18 | ### Documentation 19 | * Update issue template with multiple labels([#7](https://github.com/opensearch-project/perftop/pull/7)) 20 | * Update OpenSearch documents link ([#9](https://github.com/opensearch-project/perftop/pull/9)) 21 | * Update REST endpoint in document ([#10](https://github.com/opensearch-project/perftop/pull/10)) 22 | * Add opensearch-perftop 1.0.0.0-rc1 release note ([#11](https://github.com/opensearch-project/perftop/pull/11)) -------------------------------------------------------------------------------- /release-notes/opensearch-perftop.release-notes-1.1.0.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.1.0.0 Release Notes 2 | 3 | Compatible with OpenSearch 1.1.0 4 | 5 | ### Infrastructure 6 | * Update version to 1.1 and add release notes ([#17](https://github.com/opensearch-project/perftop/pull/17)) 7 | * Switch opensearch from 1.x from 1.1 ([#20](https://github.com/opensearch-project/perftop/pull/20)) 8 | 9 | ### Maintenance 10 | * Bump path-parse from 1.0.6 to 1.0.7 ([#16](https://github.com/opensearch-project/perftop/pull/16)) 11 | -------------------------------------------------------------------------------- /release-notes/opensearch-perftop.release-notes-1.2.0.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.2.0.0 Release Notes 2 | 3 | Compatible with OpenSearch 1.2.0 4 | 5 | ### Enhancements 6 | * Add DCO check ([#24](https://github.com/opensearch-project/perftop/pull/24)) 7 | 8 | ### Infrastructure 9 | * Upgrade Perftop version to 1.2 ([#25](https://github.com/opensearch-project/perftop/pull/25)) 10 | * Add release notes for 1.2 release and update README.md ([#27](https://github.com/opensearch-project/perftop/pull/27)) 11 | 12 | ### Documentation 13 | * Update installation instructions in README.md ([#22](https://github.com/opensearch-project/perftop/pull/22)) -------------------------------------------------------------------------------- /release-notes/opensearch-perftop.release-notes-1.2.4.0.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/perftop/80364bcec0e2f9225e5a2d83e1046c224901783f/release-notes/opensearch-perftop.release-notes-1.2.4.0.md -------------------------------------------------------------------------------- /release-notes/opensearch-perftop.release-notes-1.3.0.0.md: -------------------------------------------------------------------------------- 1 | ## Version 1.3.0.0 Release Notes 2 | 3 | Compatible with OpenSearch 1.3.0 4 | 5 | ### Infrastructure 6 | * Add .whitesource configuration file ([#33](https://github.com/opensearch-project/perftop/pull/33)) 7 | 8 | ### Bug Fixes 9 | * Remove jcenter ([#44](https://github.com/opensearch-project/perftop/pull/44)) 10 | 11 | ### Maintainence 12 | * Fix and lock link checker at lycheeverse/lychee-action ([#31](https://github.com/opensearch-project/perftop/pull/31)) 13 | * Updating OS version to 1.3.0 ([#32](https://github.com/opensearch-project/perftop/pull/32)) 14 | * Bump ajv from 6.6.1 to 6.12.6 ([#37](https://github.com/opensearch-project/perftop/pull/37)) 15 | 16 | ### Documentation 17 | * Modify copyright license headers ([#45](https://github.com/opensearch-project/perftop/pull/45)) 18 | 19 | --------------------------------------------------------------------------------