├── .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 | [](https://github.com/opensearch-project/perftop/actions?query=workflow%3ACD)
2 | [](https://opensearch.org/docs/monitoring-plugins/pa/dashboards/)
3 | [](https://discuss.opendistrocommunity.dev/c/performance-analyzer/)
4 | 
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------