├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── SECURITY.md ├── agent ├── pom.xml └── src │ └── main │ ├── java │ └── agent │ │ ├── AgentApplication.java │ │ └── AgentConfiguration.java │ └── resources │ ├── application.properties │ └── config.properties ├── examples ├── 1.jstack ├── 2.jstack ├── 3.jstack ├── 4.jstack ├── 5.jstack └── example.jfr ├── perfgenie ├── pom.xml └── src │ ├── main │ ├── java │ │ └── server │ │ │ ├── FtlController.java │ │ │ ├── IPerfGenieService.java │ │ │ ├── PerfGenieApplication.java │ │ │ ├── PerfGenieConfiguration.java │ │ │ ├── PerfGenieController.java │ │ │ └── PerfGenieService.java │ └── resources │ │ ├── application.properties │ │ ├── config.properties │ │ ├── static │ │ ├── css │ │ │ ├── SFlameGraph.css │ │ │ └── profiler.css │ │ ├── images │ │ │ ├── a_off.png │ │ │ ├── a_on.png │ │ │ ├── aub_off.png │ │ │ ├── aub_on.png │ │ │ ├── b_off.png │ │ │ ├── b_on.png │ │ │ ├── contexttable.jpg │ │ │ ├── flamegraph.jpg │ │ │ ├── flamegraphdiff.jpg │ │ │ ├── flow.jpg │ │ │ ├── metrictimelineview.jpg │ │ │ ├── requesttimeline.jpg │ │ │ ├── riverview.jpg │ │ │ ├── samplesexplorer.jpg │ │ │ ├── showallrequests.jpg │ │ │ ├── surfaceview.jpg │ │ │ ├── threadrequestview.jpg │ │ │ ├── threadstatepop.jpg │ │ │ ├── threadstateview.jpg │ │ │ ├── treeview.jpg │ │ │ ├── treeviewdiff.jpg │ │ │ └── ui.jpg │ │ ├── index.html │ │ ├── js │ │ │ ├── SFDataTable.js │ │ │ ├── SFlameGraph.js │ │ │ ├── input.js │ │ │ └── utils.js │ │ └── plugins │ │ │ ├── bootstrap-4.2.1 │ │ │ ├── css │ │ │ │ ├── bootstrap-grid.css │ │ │ │ ├── bootstrap-grid.css.map │ │ │ │ ├── bootstrap-grid.min.css │ │ │ │ ├── bootstrap-grid.min.css.map │ │ │ │ ├── bootstrap-reboot.css │ │ │ │ ├── bootstrap-reboot.css.map │ │ │ │ ├── bootstrap-reboot.min.css │ │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ │ ├── bootstrap.css │ │ │ │ ├── bootstrap.css.map │ │ │ │ ├── bootstrap.min.css │ │ │ │ └── bootstrap.min.css.map │ │ │ └── js │ │ │ │ ├── bootstrap.bundle.js │ │ │ │ ├── bootstrap.bundle.js.map │ │ │ │ ├── bootstrap.bundle.min.js │ │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ │ ├── bootstrap.js │ │ │ │ ├── bootstrap.js.map │ │ │ │ ├── bootstrap.min.js │ │ │ │ └── bootstrap.min.js.map │ │ │ ├── bootstrap-multiselect.css │ │ │ ├── bootstrap-multiselect.js │ │ │ ├── bootstrap-toggle.min.css │ │ │ ├── bootstrap-toggle.min.js │ │ │ ├── c3 │ │ │ ├── c3-0.7.14.min.js │ │ │ ├── c3.min.css │ │ │ └── c3.min.js │ │ │ ├── d3-tip.min.js │ │ │ ├── d3 │ │ │ ├── d3-4.10.0.min.js │ │ │ ├── d3-5.15.0.min.js │ │ │ ├── d3-tip.min.js │ │ │ ├── d3.flameGraph.min.css │ │ │ ├── d3.flameGraph.min.js │ │ │ └── d3.min.js │ │ │ ├── dataTables.jqueryui.min.css │ │ │ ├── dataTables.jqueryui.min.js │ │ │ ├── font-awesome-4.7.0 │ │ │ ├── HELP-US-OUT.txt │ │ │ ├── css │ │ │ │ ├── font-awesome.css │ │ │ │ └── font-awesome.min.css │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ │ ├── less │ │ │ │ ├── animated.less │ │ │ │ ├── bordered-pulled.less │ │ │ │ ├── core.less │ │ │ │ ├── fixed-width.less │ │ │ │ ├── font-awesome.less │ │ │ │ ├── icons.less │ │ │ │ ├── larger.less │ │ │ │ ├── list.less │ │ │ │ ├── mixins.less │ │ │ │ ├── path.less │ │ │ │ ├── rotated-flipped.less │ │ │ │ ├── screen-reader.less │ │ │ │ ├── stacked.less │ │ │ │ └── variables.less │ │ │ └── scss │ │ │ │ ├── _animated.scss │ │ │ │ ├── _bordered-pulled.scss │ │ │ │ ├── _core.scss │ │ │ │ ├── _fixed-width.scss │ │ │ │ ├── _icons.scss │ │ │ │ ├── _larger.scss │ │ │ │ ├── _list.scss │ │ │ │ ├── _mixins.scss │ │ │ │ ├── _path.scss │ │ │ │ ├── _rotated-flipped.scss │ │ │ │ ├── _screen-reader.scss │ │ │ │ ├── _stacked.scss │ │ │ │ ├── _variables.scss │ │ │ │ └── font-awesome.scss │ │ │ ├── font-awesome │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ │ ├── jquery-3.6.1.min.js │ │ │ ├── jquery-migrate-3.4.0.min.js │ │ │ ├── jquery-ui-1.13.2 │ │ │ ├── AUTHORS.txt │ │ │ ├── LICENSE.txt │ │ │ ├── external │ │ │ │ └── jquery │ │ │ │ │ └── jquery.js │ │ │ ├── images │ │ │ │ ├── ui-icons_444444_256x240.png │ │ │ │ ├── ui-icons_555555_256x240.png │ │ │ │ ├── ui-icons_777620_256x240.png │ │ │ │ ├── ui-icons_777777_256x240.png │ │ │ │ ├── ui-icons_cc0000_256x240.png │ │ │ │ └── ui-icons_ffffff_256x240.png │ │ │ ├── index.html │ │ │ ├── jquery-ui.css │ │ │ ├── jquery-ui.js │ │ │ ├── jquery-ui.min.css │ │ │ ├── jquery-ui.min.js │ │ │ ├── jquery-ui.structure.css │ │ │ ├── jquery-ui.structure.min.css │ │ │ ├── jquery-ui.theme.css │ │ │ ├── jquery-ui.theme.min.css │ │ │ └── package.json │ │ │ ├── jquery.contextMenu.min.css │ │ │ ├── jquery.contextMenu.min.js │ │ │ ├── jquery.dataTables.min.js │ │ │ ├── jquery.datetimepicker.full.min.js │ │ │ ├── jquery.datetimepicker.min.css │ │ │ ├── jquery.datetimepicker.min.js │ │ │ ├── jquery.ui.position.js │ │ │ ├── moment.min.js │ │ │ └── plotly.min.js │ │ └── templates │ │ ├── cct.ftl │ │ ├── filter-panel.ftl │ │ ├── flame.ftl │ │ ├── index.ftl │ │ ├── input.ftl │ │ ├── river.ftl │ │ ├── sample.ftl │ │ ├── surface.ftl │ │ ├── tab-filter-toolbar.ftl │ │ ├── tabs-js.ftl │ │ ├── tabs.ftl │ │ ├── test.ftl │ │ └── tsview.ftl │ └── test │ ├── java │ └── PerfGenieServiceTest.java │ └── resources │ └── test.jfr ├── pom.xml ├── simulator ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ ├── CPUEvent.java │ ├── Simulator.java │ └── TxCPUEvent.java │ └── resources │ └── class.template └── utils ├── pom.xml └── src ├── main └── java │ └── perfgenie │ └── utils │ ├── CustomJfrParser.java │ ├── EventHandler.java │ ├── EventStore.java │ └── Utils.java └── test ├── java └── DownloadUploadTest.java └── resources └── test.jfr /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | target/ 4 | trace.db/ 5 | *.out 6 | *.jstack 7 | *.iml 8 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Comment line immediately above ownership line is reserved for related other information. Please be careful while editing. 2 | #ECCN:Open Source 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | * Using welcoming and inclusive language 39 | * Being respectful of differing viewpoints and experiences 40 | * Gracefully accepting constructive criticism 41 | * Focusing on what is best for the community 42 | * Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | * The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | * Personal attacks, insulting/derogatory comments, or trolling 49 | * Public or private harassment 50 | * Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | * Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | * Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/) 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | This page lists the operational governance model of this project, as well as the recommendations and requirements for how to best contribute to it. We strive to obey these as best as possible. As always, thanks for contributing – we hope these guidelines make it easier and shed some light on our approach and processes. 4 | 5 | # Governance Model 6 | 7 | ## Published but not supported 8 | 9 | The intent and goal of open sourcing this project is because it may contain useful or interesting code/concepts that we wish to share with the larger open source community. Although occasional work may be done on it, we will not be looking for or soliciting contributions. 10 | 11 | # Getting started 12 | 13 | Please join the community. Also please make sure to take a look at the project [roadmap](ROADMAP.md), if it exists, to see where are headed. 14 | 15 | # Issues, requests & ideas 16 | 17 | Use GitHub Issues page to submit issues, enhancement requests and discuss ideas. 18 | 19 | ### Bug Reports and Fixes 20 | - If you find a bug, please search for it in the Issues, and if it isn't already tracked, 21 | create a new issue. Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still 22 | be reviewed. 23 | - Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`. 24 | - If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number. 25 | - Include tests that isolate the bug and verifies that it was fixed. 26 | 27 | ### New Features 28 | - If you'd like to add new functionality to this project, describe the problem you want to solve in a new Issue. 29 | - Issues that have been identified as a feature request will be labelled `enhancement`. 30 | - If you'd like to implement the new feature, please wait for feedback from the project 31 | maintainers before spending too much time writing the code. In some cases, `enhancement`s may 32 | not align well with the project objectives at the time. 33 | 34 | ### Tests, Documentation, Miscellaneous 35 | - If you'd like to improve the tests, you want to make the documentation clearer, you have an 36 | alternative implementation of something that may have advantages over the way its currently 37 | done, or you have any other change, we would be happy to hear about it! 38 | - If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind. 39 | - If not, open an Issue to discuss the idea first. 40 | 41 | If you're new to our project and looking for some way to make your first contribution, look for 42 | Issues labelled `good first contribution`. 43 | 44 | # Contribution Checklist 45 | 46 | - [x] Clean, simple, well styled code 47 | - [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number. 48 | - [x] Comments 49 | - Module-level & function-level comments. 50 | - Comments on complex blocks of code or algorithms (include references to sources). 51 | - [x] Tests 52 | - The test suite, if provided, must be complete and pass 53 | - Increase code coverage, not versa. 54 | - Use any of our testkits that contains a bunch of testing facilities you would need. For example: `import com.salesforce.op.test._` and borrow inspiration from existing tests. 55 | - [x] Dependencies 56 | - Minimize number of dependencies. 57 | - Prefer Apache 2.0, BSD3, MIT, ISC and MPL licenses. 58 | - [x] Reviews 59 | - Changes must be approved via peer code review 60 | 61 | # Creating a Pull Request 62 | 63 | 1. **Ensure the bug/feature was not already reported** by searching on GitHub under Issues. If none exists, create a new issue so that other contributors can keep track of what you are trying to add/fix and offer suggestions (or let you know if there is already an effort in progress). 64 | 3. **Clone** the forked repo to your machine. 65 | 4. **Create** a new branch to contain your work (e.g. `git br fix-issue-11`) 66 | 4. **Commit** changes to your own branch. 67 | 5. **Push** your work back up to your fork. (e.g. `git push fix-issue-11`) 68 | 6. **Submit** a Pull Request against the `main` branch and refer to the issue(s) you are fixing. Try not to pollute your pull request with unintended changes. Keep it simple and small. 69 | 7. **Sign** the Salesforce CLA (you will be prompted to do so when submitting the Pull Request) 70 | 71 | > **NOTE**: Be sure to [sync your fork](https://help.github.com/articles/syncing-a-fork/) before making a pull request. 72 | 73 | 74 | # Code of Conduct 75 | Please follow our [Code of Conduct](CODE_OF_CONDUCT.md). 76 | 77 | # License 78 | By contributing your code, you agree to license your contribution under the terms of our project [LICENSE](LICENSE.txt) and to sign the [Salesforce CLA](https://cla.salesforce.com/sign-cla) 79 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022 Salesforce, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | 3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # perfGenie 2 | 3 | License 4 | 5 | perfGenie is a continuous low overhead contextual profiling solution that can parse and visualize Java flight recorder(JFR) format profile and Jstacks. JFR profiles can be created using Java machine control(JMC), Java flight recorder, Async-profiler, or jcmd. It provides the ability to view profiles in context trees, samples explorer, flame Graph, thread state, river, and hotspot surface views. It helps to compare two profiles using context tree diff or flame graph diff views. It also provides functionality to filter profiles for a given custom event context, tid or thread name, etc. The request timeline view helps look at samples of a particular request context. It also can visualize thread context requests and context metric timeline views. The aggregation feature allows for combining profiles for a more extended period. Thread dumps can also be converted into supported profile views. 6 | 7 | 8 | ### Continuous low overhead contextual profiling ✨ 9 | 10 | perfGenie can be deployed as a continuous profiling solution. It uses Cantor (https://github.com/salesforce/cantor) as a data layer. Cantor can be configured to store data in H2, MySQL, or S3. This project is set up with a simple cron job to monitor a directory for any JFR files, parse and store in H2 (default configuration). 11 | 12 | 13 | 14 | ### Development 15 | 16 | #### Create a new Fork and Clone the repository 17 | 18 | ```sh 19 | $ git clone git@github.com:salesforce-misc/perfGenie.git 20 | ``` 21 | 22 | #### Build 23 | 24 | ```sh 25 | $ export JAVA_HOME= 26 | $ mvn clean install 27 | ``` 28 | 29 | #### Config 30 | 31 | add profiles and custom events to be parsed in config.properties file 32 | 33 | ```sh 34 | ex: 35 | customevents=LogContext;MqFrm;CPUEvent;MemoryEvent;Search 36 | profiles=ExecutionS;Socket;NewTLAB;OutsideTLAB 37 | jfrdir=/tmp/jfrs 38 | tenant=dev 39 | 40 | #h2,grpc,mySQL 41 | storageType=h2 42 | 43 | #h2 config 44 | h2dir=/tmp/h2.db 45 | 46 | #mySQL config 47 | mySQL.host=xxxx 48 | mySQL.port=3306 49 | mySQL.user=xxxx 50 | mySQL.pwd=xxxx 51 | 52 | #grpc, cantor server 53 | grpc.target=localhost:7443 54 | 55 | #filtering 56 | threshold=0.05 57 | filterDepth=4 58 | maxStackDepth=10 59 | 60 | #experimental to enable river view and surface views 61 | isExperimental=false 62 | 63 | 64 | 65 | ``` 66 | 67 | #### Start application server 68 | ```sh 69 | $ java -jar perfgenie/target/perfgenie.jar 70 | ``` 71 | Access URL http://localhost:8080 72 | 73 | #### How to add JFR's for testing 74 | The application server has a cron job to monitor and parse JFR (*.jfr or .jfr.gz), and Jstack (.jstack) files available at /tmp/jfrs/ 75 | 76 | Note: example files provided in the examples directory can be copied into jfrdir for testing 77 | 78 | Note: refer simulator module to generate a sample jfr with custom events added 79 | 80 | ### Agent mode 81 | ```sh 82 | $ java -jar agent/target/agent.jar 83 | ``` 84 | Agent to monitor and parse JFR (*.jfr or .jfr.gz), and Jstack (.jstack) files available at /tmp/agent/jfrs/ 85 | 86 | ### Features 87 | 88 | - Tree view (backtrace, call tree, and compare view) 89 | - Samples explorer view (grouping samples by tid, thread name, and custom event context dimensions) 90 | - Flame graph view (backtrace, call tree, and compare view) 91 | - Thread state view (This view is supported for thread dumps) 92 | - River view (Stack trace timeline view) 93 | - Hotspot surface (3D Stacktrace timeline view) 94 | - Custom event table view 95 | - Custom event context filters on the profile 96 | - Thread request view (based on custom events) 97 | - Metric timeline view (based on custom event metrics) 98 | - Request timeline view (to see samples of particular request) 99 | 100 | ## User interface: 101 | 102 | 103 | ### Context Filters 104 | 105 | #### Context table view 106 | 107 | 108 | #### Request samples view 109 | 110 | 111 | 112 | 113 | #### Thread request view 114 | 115 | 116 | #### Metric timeline view 117 | 118 | 119 | ### Profile views 120 | 121 | #### Calling context tree 122 | 123 | 124 | #### Calling context tree compare view 125 | 126 | 127 | #### Samples explorer 128 | 129 | 130 | #### Flame graph (Brendan Gregg, https://www.brendangregg.com/flamegraphs.html) 131 | 132 | 133 | #### Flame graph compare view 134 | 135 | 136 | #### Thread state view 137 | 138 | 139 | 140 | #### River view (Experimental stack trace timeline view) 141 | 142 | 143 | #### Hotspot surface view (Experimental stack trace timeline view) 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com) 4 | as soon as it is discovered. This library limits its runtime dependencies in 5 | order to reduce the total cost of ownership as much as can be, but all consumers 6 | should remain vigilant and have their security stakeholders review all third-party 7 | products (3PP) like this one and their dependencies. -------------------------------------------------------------------------------- /agent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.perfgenie 7 | perfgenie-parent 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | agent 12 | 0.0.1-SNAPSHOT 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-web 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-test 22 | test 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-devtools 28 | true 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-freemarker 33 | 34 | 35 | org.testng 36 | testng 37 | 6.14.3 38 | test 39 | 40 | 41 | com.perfgenie 42 | perfgenie.utils 43 | 0.0.1-SNAPSHOT 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-maven-plugin 53 | 54 | 55 | agent.AgentApplication 56 | 57 | 58 | 59 | 60 | repackage 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-deploy-plugin 69 | 2.7 70 | 71 | true 72 | 73 | 74 | 75 | agent 76 | 77 | 78 | -------------------------------------------------------------------------------- /agent/src/main/java/agent/AgentApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | package agent; 8 | 9 | import com.google.common.base.Stopwatch; 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.scheduling.annotation.EnableScheduling; 13 | import org.springframework.scheduling.annotation.Scheduled; 14 | 15 | import perfgenie.utils.*; 16 | 17 | import java.io.*; 18 | import java.net.InetAddress; 19 | import java.util.*; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.logging.Logger; 22 | 23 | @SpringBootApplication 24 | @EnableScheduling 25 | public class AgentApplication { 26 | private static final Logger logger = Logger.getLogger(AgentApplication.class.getName()); 27 | private static String tenant = "dev"; 28 | private static String host = "localhost"; 29 | final CustomJfrParser.Config config = new CustomJfrParser.Config(); 30 | 31 | final EventStore eventStore; 32 | final CustomJfrParser parser; 33 | 34 | public AgentApplication(EventStore eventStore, CustomJfrParser parser) { 35 | this.eventStore = eventStore; 36 | this.parser = parser; 37 | } 38 | 39 | public static void main(String[] args) { 40 | SpringApplication.run(AgentApplication.class, args); 41 | } 42 | @Scheduled(cron = "*/10 * * ? * *") 43 | private void cronJob() throws IOException { 44 | tenant = config.getTenant(); 45 | host = InetAddress.getLocalHost().getHostName(); 46 | logger.info("looking for Jfrs at " + config.getJfrdir()); 47 | File folder = new File(config.getJfrdir()); 48 | File[] listOfFiles = folder.listFiles(); 49 | Arrays.sort(listOfFiles, Comparator.comparingLong(File::lastModified)); 50 | 51 | if (listOfFiles == null) 52 | return; 53 | 54 | for (File file : listOfFiles) { 55 | 56 | logger.info("processing file: " + file.getName()); 57 | 58 | if (file.isFile() && file.getName().contains(".jfr") || file.getName().contains(".jfr.gz")) { 59 | EventHandler handler = new EventHandler(); 60 | long timestamp = System.currentTimeMillis(); 61 | String guid = Utils.generateGuid(); 62 | final Stopwatch timer = Stopwatch.createStarted(); 63 | try { 64 | parser.parseStream(handler, file.getPath()); 65 | final Map dimMap = new HashMap<>(); 66 | final Map queryMap = new HashMap<>(); 67 | queryMap.put("guid", guid); 68 | queryMap.put("tenant", tenant); 69 | queryMap.put("host", host); 70 | queryMap.put("file-name", file.getName()); 71 | 72 | List l = handler.getProfileList(); 73 | for (int i = 0; i < l.size(); i++) { 74 | Object profile = handler.getProfileTree(l.get(i)); 75 | queryMap.put("type", "jfrprofile"); 76 | queryMap.put("name", l.get(i)); 77 | final String payload = Utils.toJson(profile); 78 | int payloadSize = payload.length(); 79 | queryMap.put("size", String.valueOf(payloadSize)); 80 | eventStore.addEvent(timestamp, queryMap, dimMap, payload); 81 | } 82 | Object logContext = handler.getLogContext(); 83 | queryMap.put("type", "jfrevent"); 84 | queryMap.put("name", "customEvent"); 85 | 86 | eventStore.addEvent(timestamp, queryMap, dimMap, Utils.toJson(logContext)); 87 | } catch (Exception e) { 88 | System.out.println(e); 89 | logger.warning("Exception parsing file " + file.getPath() + ":" + e.getStackTrace()); 90 | } 91 | new File(file.getPath()).delete(); 92 | logger.info("successfully parsed " + file.getPath() + " and stored " + "time ms: " + timer.stop().elapsed(TimeUnit.MILLISECONDS)); 93 | } else if (file.isFile() && file.getName().contains(".jstack")) { 94 | EventHandler handler = new EventHandler(); 95 | long timestamp = System.currentTimeMillis(); 96 | String guid = Utils.generateGuid(); 97 | final Stopwatch timer = Stopwatch.createStarted(); 98 | try { 99 | BufferedReader reader = new BufferedReader(new FileReader(file.getPath())); 100 | StringBuilder stringBuilder = new StringBuilder(); 101 | char[] buffer = new char[10]; 102 | while (reader.read(buffer) != -1) { 103 | stringBuilder.append(new String(buffer)); 104 | buffer = new char[10]; 105 | } 106 | reader.close(); 107 | String content = stringBuilder.toString(); 108 | handler.initializeProfile("Jstack"); 109 | handler.initializePid("Jstack"); 110 | handler.processJstackEvent(timestamp * 1000000, content); 111 | final Map dimMap = new HashMap<>(); 112 | final Map queryMap = new HashMap<>(); 113 | queryMap.put("guid", guid); 114 | queryMap.put("tenant", tenant); 115 | queryMap.put("host", host); 116 | queryMap.put("file-name", file.getName()); 117 | Object profile = handler.getProfileTree("Jstack"); 118 | queryMap.put("type", "jstack"); 119 | queryMap.put("name", "Jstack"); 120 | eventStore.addEvent(timestamp, queryMap, dimMap, Utils.toJson(profile)); 121 | } catch (Exception e) { 122 | System.out.println(e); 123 | logger.warning("Exception parsing file " + file.getPath() + ":" + e.getStackTrace()); 124 | } 125 | new File(file.getPath()).delete(); 126 | logger.info("successfully parsed " + file.getPath() + " and stored event " + "time ms: " + timer.stop().elapsed(TimeUnit.MILLISECONDS)); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /agent/src/main/java/agent/AgentConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | package agent; 8 | 9 | import com.salesforce.cantor.Cantor; 10 | import com.salesforce.cantor.h2.CantorOnH2; 11 | import com.salesforce.cantor.mysql.CantorOnMysql; 12 | import com.salesforce.cantor.grpc.CantorOnGrpc; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import perfgenie.utils.CustomJfrParser; 16 | import perfgenie.utils.EventStore; 17 | 18 | import java.io.IOException; 19 | 20 | @Configuration 21 | public class AgentConfiguration { 22 | final CustomJfrParser.Config config = new CustomJfrParser.Config(); 23 | 24 | @Bean 25 | public Cantor getCantor() throws IOException { 26 | if (config.getStorageType().equals("mySQL")) { 27 | return new CantorOnMysql(config.getMySQL_host(), config.getMySQL_port(), config.getMySQL_user(), config.getMySQL_pwd()); 28 | } else if (config.getStorageType().equals("grpc")) { 29 | return new CantorOnGrpc(config.getGrpc_target()); 30 | } else { 31 | return new CantorOnH2(config.getH2dir());//default 32 | } 33 | } 34 | 35 | @Bean 36 | public EventStore getEventStore(final Cantor cantor) throws IOException { 37 | return new EventStore(cantor); 38 | } 39 | 40 | @Bean 41 | public CustomJfrParser getCustomParser() throws IOException { 42 | return new CustomJfrParser(2); 43 | } 44 | 45 | @Bean 46 | public AgentApplication getServerService(final EventStore eventStore, final CustomJfrParser parser) throws IOException { 47 | return new AgentApplication(eventStore, parser); 48 | } 49 | } -------------------------------------------------------------------------------- /agent/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port: 9090 2 | 3 | -------------------------------------------------------------------------------- /agent/src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | customevents=LogContext;MqFrm;CPUEvent;MemoryEvent;Search 2 | profiles=ExecutionS;Socket;NewTLAB;OutsideTLAB 3 | jfrdir=/tmp/jfrs 4 | tenant=dev 5 | 6 | #h2,grpc,mySQL 7 | storageType=h2 8 | 9 | #h2 config 10 | h2dir=/tmp/h2.db 11 | 12 | #mySQL config 13 | mySQL.host=xxxx 14 | mySQL.port=3306 15 | mySQL.user=xxxx 16 | mySQL.pwd=xxxx 17 | 18 | #grpc, cantor server 19 | grpc.target=localhost:7443 20 | 21 | #filtering 22 | threshold=0.05 23 | filterDepth=4 24 | maxStackDepth=10 25 | 26 | #experimental to enable river view and surface views 27 | isExperimental=false 28 | -------------------------------------------------------------------------------- /examples/example.jfr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/examples/example.jfr -------------------------------------------------------------------------------- /perfgenie/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.perfgenie 7 | perfgenie-parent 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | perfgenie 12 | 0.0.1-SNAPSHOT 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-web 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-test 22 | test 23 | 24 | 25 | com.salesforce.cantor 26 | cantor-h2 27 | 0.5.0 28 | 29 | 30 | com.salesforce.cantor 31 | cantor-grpc-client 32 | 0.5.0 33 | 34 | 35 | com.salesforce.cantor 36 | cantor-mysql 37 | 0.5.0 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-devtools 43 | true 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-freemarker 48 | 49 | 50 | org.testng 51 | testng 52 | 6.14.3 53 | test 54 | 55 | 56 | com.perfgenie 57 | perfgenie.utils 58 | 0.0.1-SNAPSHOT 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-maven-plugin 68 | 2.7.4 69 | 70 | server.PerfGenieApplication 71 | 72 | 73 | 74 | 75 | repackage 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-deploy-plugin 84 | 2.7 85 | 86 | true 87 | 88 | 89 | 90 | perfgenie 91 | 92 | 93 | -------------------------------------------------------------------------------- /perfgenie/src/main/java/server/FtlController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | package server; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | 17 | @Controller 18 | public class FtlController { 19 | private final PerfGenieService service; 20 | 21 | @Autowired 22 | public FtlController(PerfGenieService service) { 23 | this.service = service; 24 | } 25 | 26 | @RequestMapping(value = "/", method = RequestMethod.GET) 27 | public String index() { 28 | return "/templates/index"; 29 | } 30 | @GetMapping(path = {"/templates", "/templates/{tenant}"}) 31 | public String user( @PathVariable(required=false,name="tenant") String tenant) { 32 | return tenant; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /perfgenie/src/main/java/server/IPerfGenieService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | package server; 9 | 10 | import java.io.IOException; 11 | import java.util.Map; 12 | 13 | public interface IPerfGenieService { 14 | /** 15 | * Store an event iwth given metadata 16 | * 17 | * @param payload event payload 18 | * @param timestamp event timestamp 19 | * @param queryMap event filters 20 | * @param dimMap event dimentions 21 | * @return true/false 22 | */ 23 | boolean addEvent(final String payload, final long timestamp, final Map dimMap, final Map queryMap) throws IOException; 24 | 25 | /** 26 | * get event metadata 27 | * 28 | * @param start start time ms 29 | * @param end end time ms 30 | * @param queryMap event filters 31 | * @param dimMap event dimentions 32 | * @return none 33 | */ 34 | String getMeta(long start, long end, final Map queryMap, final Map dimMap) throws IOException; 35 | 36 | /** 37 | * get profile event data 38 | * 39 | * @param tenant tenant name from where event to be fetched 40 | * @param start start time ms 41 | * @param end end time ms 42 | * @param queryMap event filters 43 | * @param dimMap event dimentions 44 | * @return none 45 | */ 46 | String getProfile(String tenant, long start, long end, Map queryMap, Map dimMap) throws IOException; 47 | 48 | /** 49 | * get combined profile event data 50 | * 51 | * @param tenant tenant name from where event to be fetched 52 | * @param start start time ms 53 | * @param end end time ms 54 | * @param queryMap event filters 55 | * @param dimMap event dimentions 56 | * @return none 57 | */ 58 | String getProfiles(final String tenant, long start, long end, final Map queryMap, final Map dimMap) throws IOException; 59 | 60 | /** 61 | * get custom event data 62 | * 63 | * @param tenant tenant name from where event to be fetched 64 | * @param start start time ms 65 | * @param end end time ms 66 | * @param queryMap event filters 67 | * @param dimMap event dimentions 68 | * @return none 69 | */ 70 | String getCustomEvents(final String tenant, long start, long end, final Map queryMap, final Map dimMap) throws IOException; 71 | 72 | String getJstack(final String tenant, final long start, final long end, final Map queryMap) throws IOException; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /perfgenie/src/main/java/server/PerfGenieApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | package server; 9 | 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.scheduling.annotation.EnableScheduling; 13 | import perfgenie.utils.EventHandler; 14 | 15 | import java.util.logging.Logger; 16 | 17 | @SpringBootApplication 18 | @EnableScheduling 19 | public class PerfGenieApplication { 20 | private static final Logger logger = Logger.getLogger(EventHandler.class.getName()); 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(PerfGenieApplication.class, args); 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /perfgenie/src/main/java/server/PerfGenieConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | package server; 9 | 10 | import com.salesforce.cantor.Cantor; 11 | import com.salesforce.cantor.h2.CantorOnH2; 12 | import com.salesforce.cantor.mysql.CantorOnMysql; 13 | import com.salesforce.cantor.grpc.CantorOnGrpc; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import perfgenie.utils.CustomJfrParser; 17 | import perfgenie.utils.EventStore; 18 | 19 | import java.io.IOException; 20 | 21 | @Configuration 22 | public class PerfGenieConfiguration { 23 | final CustomJfrParser.Config config = new CustomJfrParser.Config(); 24 | 25 | @Bean 26 | public Cantor getCantor() throws IOException { 27 | if (config.getStorageType().equals("mySQL")) { 28 | return new CantorOnMysql(config.getMySQL_host(), config.getMySQL_port(), config.getMySQL_user(), config.getMySQL_pwd()); 29 | } else if (config.getStorageType().equals("grpc")) { 30 | return new CantorOnGrpc(config.getGrpc_target()); 31 | } else { 32 | return new CantorOnH2(config.getH2dir());//default 33 | } 34 | } 35 | 36 | @Bean 37 | public EventStore getEventStore(final Cantor cantor) throws IOException { 38 | return new EventStore(cantor); 39 | } 40 | 41 | @Bean 42 | public CustomJfrParser getCustomParser() throws IOException { 43 | return new CustomJfrParser(2); 44 | } 45 | 46 | @Bean 47 | public PerfGenieService getServerService(final EventStore eventStore, final CustomJfrParser parser) throws IOException { 48 | return new PerfGenieService(eventStore, parser); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /perfgenie/src/main/java/server/PerfGenieController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | package server; 9 | 10 | import com.google.common.base.Strings; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.io.IOException; 16 | import java.util.Collections; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.regex.Matcher; 21 | import java.util.regex.Pattern; 22 | 23 | @RestController 24 | public class PerfGenieController { 25 | private final PerfGenieService service; 26 | private static final Pattern queryPatterns = Pattern.compile("(?.*?)(?(>|<|=|!=|~|!~|<=|>=).*)"); 27 | 28 | @Autowired 29 | public PerfGenieController(PerfGenieService service) { 30 | this.service = service; 31 | } 32 | 33 | @GetMapping(path = {"/v1/profile", "/v1/profile/{tenant}"}, produces = MediaType.APPLICATION_JSON_VALUE) 34 | public String profile(@PathVariable(required = false, name = "tenant") String tenant, 35 | @RequestParam(required = false, name = "start") final long start, 36 | @RequestParam(required = false, name = "end") final long end, 37 | @RequestParam("metadata_query") final List metadataQuery) throws IOException { 38 | 39 | final Map queryMap = queryToMap(metadataQuery); 40 | final Map dimMap = new HashMap<>(); 41 | 42 | return service.getProfile(tenant, start, end, queryMap, dimMap); 43 | } 44 | 45 | @GetMapping(path = {"/v1/profiles", "/v1/profiles/{tenant}"}, produces = MediaType.APPLICATION_JSON_VALUE) 46 | public String profiles(@PathVariable(required = false, name = "tenant") String tenant, 47 | @RequestParam(required = false, name = "start") final long start, 48 | @RequestParam(required = false, name = "end") final long end, 49 | @RequestParam("metadata_query") final List metadataQuery) throws IOException { 50 | 51 | final Map queryMap = queryToMap(metadataQuery); 52 | final Map dimMap = new HashMap<>(); 53 | 54 | return service.getProfiles(tenant, start, end, queryMap, dimMap); 55 | } 56 | 57 | @GetMapping(path = {"/v1/customevents", "/v1/customevents/{tenant}"}, produces = MediaType.APPLICATION_JSON_VALUE) 58 | public String customevents(@PathVariable(required = false, name = "tenant") String tenant, 59 | @RequestParam(required = false, name = "start") final long start, 60 | @RequestParam(required = false, name = "end") final long end, 61 | @RequestParam("metadata_query") final List metadataQuery) throws IOException { 62 | 63 | final Map queryMap = queryToMap(metadataQuery); 64 | final Map dimMap = new HashMap<>(); 65 | 66 | return service.getCustomEvents(tenant, start, end, queryMap, dimMap); 67 | } 68 | 69 | @CrossOrigin 70 | @GetMapping(path = {"/v1/meta", "/v1/meta/{tenant}"}, produces = MediaType.APPLICATION_JSON_VALUE) 71 | public String meta(@PathVariable(required = false, name = "tenant") String tenant, 72 | @RequestParam(required = false, name = "start") final long start, 73 | @RequestParam(required = false, name = "end") final long end) throws IOException { 74 | 75 | final Map queryMap = new HashMap<>(); 76 | final Map dimMap = new HashMap<>(); 77 | return service.getMeta(start, end, queryMap, dimMap); 78 | } 79 | 80 | @CrossOrigin 81 | @GetMapping(path = {"/v1/jstack", "/v1/jstack/{tenant}"}, produces = MediaType.APPLICATION_JSON_VALUE) 82 | public String jstack(@PathVariable(required = false, name = "tenant") String tenant, 83 | @RequestParam(required = false, name = "start") final long start, 84 | @RequestParam(required = false, name = "end") final long end, 85 | @RequestParam("metadata_query") final List metadataQuery) throws IOException { 86 | 87 | final Map queryMap = queryToMap(metadataQuery); 88 | return service.getJstack(tenant, start, end, queryMap); 89 | } 90 | 91 | private static Map queryToMap(final List queryList) { 92 | if (queryList.isEmpty()) { 93 | return Collections.emptyMap(); 94 | } 95 | 96 | final Map queryMap = new HashMap<>(); 97 | for (final String query : queryList) { 98 | if (Strings.isNullOrEmpty(query)) { 99 | continue; 100 | } 101 | 102 | final Matcher matcher = queryPatterns.matcher(query); 103 | if (matcher.matches()) { 104 | if (query.contains("..") || query.contains("~")) { 105 | // remove the equals when using these operators 106 | queryMap.put(matcher.group("key"), matcher.group("value").substring(1)); 107 | } else { 108 | queryMap.put(matcher.group("key"), matcher.group("value")); 109 | } 110 | } else { 111 | throw new IllegalArgumentException("Invalid query format: " + query); 112 | } 113 | } 114 | return queryMap; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /perfgenie/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.freemarker.template-loader-path: classpath:/templates 2 | spring.freemarker.suffix: .ftl 3 | server.port: 8080 4 | server.jetty.connection-idle-timeout=50000 5 | -------------------------------------------------------------------------------- /perfgenie/src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | customevents=LogContext;MqFrm;CPUEvent;MemoryEvent;Search;CPULoad 2 | profiles=ExecutionS;Socket;NewTLAB;OutsideTLAB 3 | jfrdir=/tmp/jfrs 4 | tenant=dev 5 | 6 | #h2,grpc,mySQL 7 | storageType=h2 8 | 9 | #h2 config 10 | h2dir=/tmp/h2.db 11 | 12 | #mySQL config 13 | mySQL.host=xxxx 14 | mySQL.port=3306 15 | mySQL.user=xxxx 16 | mySQL.pwd=xxxx 17 | 18 | #grpc, cantor server 19 | grpc.target=localhost:7443 20 | 21 | #filtering 22 | threshold=0.05 23 | filterDepth=4 24 | maxStackDepth=32 25 | 26 | #experimental to enable river view and surface views 27 | isExperimental=false 28 | -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/css/SFlameGraph.css: -------------------------------------------------------------------------------- 1 | .SFlameGraphTooltip { 2 | position: absolute; 3 | text-align: left; 4 | padding: 2px; 5 | font: 14px sans-serif; 6 | background: black; 7 | color: white; 8 | border: 0px; 9 | border-radius: 5px; 10 | pointer-events: none; 11 | } -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/css/profiler.css: -------------------------------------------------------------------------------- 1 | .hide{ 2 | display: none; 3 | } 4 | 5 | #cct-actual-guid { 6 | font-family: 'Arial', serif; 7 | } 8 | ul.tree li { 9 | list-style-type: none; 10 | position: relative; 11 | } 12 | ul.tree ul { 13 | margin-left: 7px; 14 | padding-left: 0px; 15 | } 16 | ul.tree li ul { 17 | display: none; 18 | } 19 | ul.tree li.open > ul { 20 | display: block; 21 | } 22 | ul.tree li div:before { 23 | height: 1em; 24 | padding: 0 .1em; 25 | font-size: .8em; 26 | display: block; 27 | position: absolute; 28 | left: -1.3em; 29 | top: .2em; 30 | } 31 | ul.tree li > div:not(:nth-last-child(2)):before { 32 | content: '+'; 33 | } 34 | ul.tree li.open > div:not(:nth-last-child(2)):before { 35 | content: '-'; 36 | } 37 | .sc { 38 | text-decoration: underline; 39 | text-decoration-color: black; 40 | background-color: #D9D9D9; 41 | } 42 | .black { 43 | color: #5D5C5B; 44 | } 45 | .bclr { 46 | color: #1857D6; 47 | } 48 | .tclr { 49 | color: #C28304; 50 | } 51 | .ibar { 52 | vertical-align: middle; 53 | height: 10px; 54 | background: seagreen; 55 | } 56 | .rbar { 57 | vertical-align: middle; 58 | height: 10px; 59 | background: indianred; 60 | } 61 | ul.tree li > div { 62 | display: inline-block; 63 | cursor: pointer; 64 | color: #5D5C5B; 65 | text-decoration: none; 66 | } -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/a_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/a_off.png -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/a_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/a_on.png -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/aub_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/aub_off.png -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/aub_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/aub_on.png -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/b_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/b_off.png -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/b_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/b_on.png -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/contexttable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/contexttable.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/flamegraph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/flamegraph.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/flamegraphdiff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/flamegraphdiff.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/flow.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/metrictimelineview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/metrictimelineview.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/requesttimeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/requesttimeline.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/riverview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/riverview.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/samplesexplorer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/samplesexplorer.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/showallrequests.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/showallrequests.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/surfaceview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/surfaceview.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/threadrequestview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/threadrequestview.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/threadstatepop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/threadstatepop.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/threadstateview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/threadstateview.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/treeview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/treeview.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/treeviewdiff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/treeviewdiff.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/images/ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/images/ui.jpg -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/perfGenie/30ed82d2f5e8bcdc8989fa4b37f152d7a516f21d/perfgenie/src/main/resources/static/index.html -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/js/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Salesforce.com, Inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | function getMetaDataURL(start,end,tenant='dev'){ 9 | const URL = "http://localhost:8080/v1/meta/"+tenant+ 10 | "/?start=" + start + 11 | "&end=" + end; 12 | return URL; 13 | } 14 | 15 | function spinnerToggle(id){ 16 | //todo 17 | } 18 | 19 | function toastr_success(str){ 20 | console.log(str); 21 | } 22 | 23 | function toastr_warning(str){ 24 | console.log(str); 25 | } 26 | 27 | function toastr_error(str){ 28 | console.log(str); 29 | } 30 | 31 | function getEventURL(tenant,start,end,host){ 32 | const URLUnprocessedIDsOld = "http://localhost:8080/v1/events/" + tenant + 33 | "?start=" + start + 34 | "&end=" + end + 35 | "&metadata_query=" + encodeURIComponent("host=" + host) + 36 | "&metadata_query=" + encodeURIComponent("tenant-id=" + tenant) + 37 | "&metadata_query=" + encodeURIComponent("name=jfr"); 38 | } 39 | 40 | function updateUrl(key, value) { 41 | let newLocation = window.location.href.replace(new RegExp("((\\?|\\&)" + key + "=)[^\\&]*"), '$1' + encodeURIComponent(value)); 42 | if (newLocation.indexOf(key) === -1) { 43 | const separator = (newLocation.indexOf("?") === -1) ? "?" : "&"; 44 | newLocation = newLocation + separator + key + "=" + encodeURIComponent(value); 45 | } 46 | window.history.replaceState({}, "", newLocation); 47 | } 48 | 49 | function stackDigVizAjax(pod, method, endpoint, successFunc, errorFunc) { 50 | if (errorFunc === undefined) { 51 | errorFunc = defaultErrorFunc; 52 | } 53 | 54 | const headers = {}; 55 | const errorFuncWithRetry = function () { 56 | return internalPerfGenieAjax(endpoint, method, successFunc, errorFunc, headers); 57 | }; 58 | 59 | return internalPerfGenieAjax(endpoint, method, successFunc, errorFuncWithRetry, headers); 60 | } -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/plugins/bootstrap-4.2.1/css/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.2.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors 4 | * Copyright 2011-2018 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | font-family: sans-serif; 16 | line-height: 1.15; 17 | -webkit-text-size-adjust: 100%; 18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 19 | } 20 | 21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { 22 | display: block; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 28 | font-size: 1rem; 29 | font-weight: 400; 30 | line-height: 1.5; 31 | color: #212529; 32 | text-align: left; 33 | background-color: #fff; 34 | } 35 | 36 | [tabindex="-1"]:focus { 37 | outline: 0 !important; 38 | } 39 | 40 | hr { 41 | box-sizing: content-box; 42 | height: 0; 43 | overflow: visible; 44 | } 45 | 46 | h1, h2, h3, h4, h5, h6 { 47 | margin-top: 0; 48 | margin-bottom: 0.5rem; 49 | } 50 | 51 | p { 52 | margin-top: 0; 53 | margin-bottom: 1rem; 54 | } 55 | 56 | abbr[title], 57 | abbr[data-original-title] { 58 | text-decoration: underline; 59 | -webkit-text-decoration: underline dotted; 60 | text-decoration: underline dotted; 61 | cursor: help; 62 | border-bottom: 0; 63 | text-decoration-skip-ink: none; 64 | } 65 | 66 | address { 67 | margin-bottom: 1rem; 68 | font-style: normal; 69 | line-height: inherit; 70 | } 71 | 72 | ol, 73 | ul, 74 | dl { 75 | margin-top: 0; 76 | margin-bottom: 1rem; 77 | } 78 | 79 | ol ol, 80 | ul ul, 81 | ol ul, 82 | ul ol { 83 | margin-bottom: 0; 84 | } 85 | 86 | dt { 87 | font-weight: 700; 88 | } 89 | 90 | dd { 91 | margin-bottom: .5rem; 92 | margin-left: 0; 93 | } 94 | 95 | blockquote { 96 | margin: 0 0 1rem; 97 | } 98 | 99 | b, 100 | strong { 101 | font-weight: bolder; 102 | } 103 | 104 | small { 105 | font-size: 80%; 106 | } 107 | 108 | sub, 109 | sup { 110 | position: relative; 111 | font-size: 75%; 112 | line-height: 0; 113 | vertical-align: baseline; 114 | } 115 | 116 | sub { 117 | bottom: -.25em; 118 | } 119 | 120 | sup { 121 | top: -.5em; 122 | } 123 | 124 | a { 125 | color: #007bff; 126 | text-decoration: none; 127 | background-color: transparent; 128 | } 129 | 130 | a:hover { 131 | color: #0056b3; 132 | text-decoration: underline; 133 | } 134 | 135 | a:not([href]):not([tabindex]) { 136 | color: inherit; 137 | text-decoration: none; 138 | } 139 | 140 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { 141 | color: inherit; 142 | text-decoration: none; 143 | } 144 | 145 | a:not([href]):not([tabindex]):focus { 146 | outline: 0; 147 | } 148 | 149 | pre, 150 | code, 151 | kbd, 152 | samp { 153 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 154 | font-size: 1em; 155 | } 156 | 157 | pre { 158 | margin-top: 0; 159 | margin-bottom: 1rem; 160 | overflow: auto; 161 | } 162 | 163 | figure { 164 | margin: 0 0 1rem; 165 | } 166 | 167 | img { 168 | vertical-align: middle; 169 | border-style: none; 170 | } 171 | 172 | svg { 173 | overflow: hidden; 174 | vertical-align: middle; 175 | } 176 | 177 | table { 178 | border-collapse: collapse; 179 | } 180 | 181 | caption { 182 | padding-top: 0.75rem; 183 | padding-bottom: 0.75rem; 184 | color: #6c757d; 185 | text-align: left; 186 | caption-side: bottom; 187 | } 188 | 189 | th { 190 | text-align: inherit; 191 | } 192 | 193 | label { 194 | display: inline-block; 195 | margin-bottom: 0.5rem; 196 | } 197 | 198 | button { 199 | border-radius: 0; 200 | } 201 | 202 | button:focus { 203 | outline: 1px dotted; 204 | outline: 5px auto -webkit-focus-ring-color; 205 | } 206 | 207 | input, 208 | button, 209 | select, 210 | optgroup, 211 | textarea { 212 | margin: 0; 213 | font-family: inherit; 214 | font-size: inherit; 215 | line-height: inherit; 216 | } 217 | 218 | button, 219 | input { 220 | overflow: visible; 221 | } 222 | 223 | button, 224 | select { 225 | text-transform: none; 226 | } 227 | 228 | button, 229 | [type="button"], 230 | [type="reset"], 231 | [type="submit"] { 232 | -webkit-appearance: button; 233 | } 234 | 235 | button::-moz-focus-inner, 236 | [type="button"]::-moz-focus-inner, 237 | [type="reset"]::-moz-focus-inner, 238 | [type="submit"]::-moz-focus-inner { 239 | padding: 0; 240 | border-style: none; 241 | } 242 | 243 | input[type="radio"], 244 | input[type="checkbox"] { 245 | box-sizing: border-box; 246 | padding: 0; 247 | } 248 | 249 | input[type="date"], 250 | input[type="time"], 251 | input[type="datetime-local"], 252 | input[type="month"] { 253 | -webkit-appearance: listbox; 254 | } 255 | 256 | textarea { 257 | overflow: auto; 258 | resize: vertical; 259 | } 260 | 261 | fieldset { 262 | min-width: 0; 263 | padding: 0; 264 | margin: 0; 265 | border: 0; 266 | } 267 | 268 | legend { 269 | display: block; 270 | width: 100%; 271 | max-width: 100%; 272 | padding: 0; 273 | margin-bottom: .5rem; 274 | font-size: 1.5rem; 275 | line-height: inherit; 276 | color: inherit; 277 | white-space: normal; 278 | } 279 | 280 | progress { 281 | vertical-align: baseline; 282 | } 283 | 284 | [type="number"]::-webkit-inner-spin-button, 285 | [type="number"]::-webkit-outer-spin-button { 286 | height: auto; 287 | } 288 | 289 | [type="search"] { 290 | outline-offset: -2px; 291 | -webkit-appearance: none; 292 | } 293 | 294 | [type="search"]::-webkit-search-decoration { 295 | -webkit-appearance: none; 296 | } 297 | 298 | ::-webkit-file-upload-button { 299 | font: inherit; 300 | -webkit-appearance: button; 301 | } 302 | 303 | output { 304 | display: inline-block; 305 | } 306 | 307 | summary { 308 | display: list-item; 309 | cursor: pointer; 310 | } 311 | 312 | template { 313 | display: none; 314 | } 315 | 316 | [hidden] { 317 | display: none !important; 318 | } 319 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/plugins/bootstrap-4.2.1/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.2.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors 4 | * Copyright 2011-2018 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/plugins/bootstrap-multiselect.css: -------------------------------------------------------------------------------- 1 | span.multiselect-native-select{position:relative}span.multiselect-native-select select{border:0!important;clip:rect(0 0 0 0)!important;height:1px!important;margin:-1px -1px -1px -3px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;left:50%;top:30px}.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container .multiselect-reset .input-group{width:93%}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li.multiselect-group label{margin:0;padding:3px 20px;height:100%;font-weight:700}.multiselect-container>li.multiselect-group-clickable label{cursor:pointer}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.checkbox,.multiselect-container>li>a>label.radio{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.form-inline .multiselect-container label.checkbox,.form-inline .multiselect-container label.radio{padding:3px 20px 3px 40px}.form-inline .multiselect-container li a label.checkbox input[type=checkbox],.form-inline .multiselect-container li a label.radio input[type=radio]{margin-left:-20px;margin-right:0} -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/plugins/bootstrap-toggle.min.css: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | .checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} 9 | .toggle{position:relative;overflow:hidden} 10 | .toggle input[type=checkbox]{display:none} 11 | .toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} 12 | .toggle.off .toggle-group{left:-100%} 13 | .toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} 14 | .toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} 15 | .toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} 16 | .toggle.btn{min-width:59px;min-height:34px} 17 | .toggle-on.btn{padding-right:24px} 18 | .toggle-off.btn{padding-left:24px} 19 | .toggle.btn-lg{min-width:79px;min-height:45px} 20 | .toggle-on.btn-lg{padding-right:31px} 21 | .toggle-off.btn-lg{padding-left:31px} 22 | .toggle-handle.btn-lg{width:40px} 23 | .toggle.btn-sm{min-width:50px;min-height:30px} 24 | .toggle-on.btn-sm{padding-right:20px} 25 | .toggle-off.btn-sm{padding-left:20px} 26 | .toggle.btn-xs{min-width:35px;min-height:22px} 27 | .toggle-on.btn-xs{padding-right:12px} 28 | .toggle-off.btn-xs{padding-left:12px} -------------------------------------------------------------------------------- /perfgenie/src/main/resources/static/plugins/bootstrap-toggle.min.js: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | +function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('