Repository: johrstrom/jmeter-prometheus-plugin Branch: main Commit: 7ceb152c68ca Files: 51 Total size: 341.2 KB Directory structure: gitextract_e9g8y5hg/ ├── .github/ │ └── workflows/ │ ├── release.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── docs/ │ └── examples/ │ ├── grafana.json │ └── simple_prometheus_example.jmx ├── licenses/ │ ├── io.prometheus.LICENSE │ └── io.prometheus.NOTICE ├── makefile ├── pom.xml └── src/ ├── main/ │ └── java/ │ └── com/ │ └── github/ │ └── johrstrom/ │ ├── collector/ │ │ ├── BaseCollectorConfig.java │ │ ├── CollectorElement.java │ │ ├── JMeterCollectorRegistry.java │ │ ├── SuccessRatioCollector.java │ │ └── gui/ │ │ ├── AbstractCollectorTable.java │ │ └── Flatten.java │ ├── config/ │ │ ├── PrometheusMetricsConfig.java │ │ └── gui/ │ │ ├── ConfigCollectorTable.java │ │ └── PrometheusMetricsConfigGui.java │ └── listener/ │ ├── ListenerCollectorConfig.java │ ├── PrometheusListener.java │ ├── PrometheusServer.java │ ├── gui/ │ │ ├── ListenerCollectorTable.java │ │ └── PrometheusListenerGui.java │ └── updater/ │ ├── AbstractUpdater.java │ ├── AggregatedTypeUpdater.java │ └── CountTypeUpdater.java └── test/ ├── java/ │ └── com/ │ └── github/ │ └── johrstrom/ │ ├── collector/ │ │ ├── BaseCollectorConfigTest.java │ │ ├── JMeterCollectorRegistryTest.java │ │ └── SuccessRatioCollectorTest.java │ ├── config/ │ │ ├── PrometheusMetricsConfigTest.java │ │ └── gui/ │ │ └── ConfigGuiTest.java │ ├── listener/ │ │ ├── ListenerCollectorConfigTest.java │ │ ├── PrometheusListenerTest.java │ │ ├── PrometheusServerTest.java │ │ ├── gui/ │ │ │ └── ListenerGuiTest.java │ │ └── updater/ │ │ ├── AbstractUpdaterTest.java │ │ ├── AggregatedTypeUpdaterTest.java │ │ └── CountTypeUpdaterTest.java │ └── test/ │ ├── NOOPThreadMonitor.java │ └── TestUtilities.java └── resources/ ├── bin/ │ ├── jmeter.properties │ ├── saveservice.properties │ ├── upgrade.properties │ └── user.properties └── log4j2.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/release.yml ================================================ name: Release Jar on: push: tags: - '*' jobs: release: runs-on: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: '11' cache: maven - name: Set version id: version run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - run: mvn clean package - name: Get release id: get_release uses: bruceadams/get-release@v1.3.2 env: GITHUB_TOKEN: ${{ github.token }} - name: Upload Jar uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.get_release.outputs.upload_url }} asset_path: target/jmeter-prometheus-plugin-${{ steps.version.outputs.version }}.jar asset_name: jmeter-prometheus-plugin-${{ steps.version.outputs.version }}.jar asset_content_type: application/java-archive ================================================ FILE: .github/workflows/tests.yml ================================================ name: Tests on: push: branches: - master - main pull_request: jobs: tests: strategy: matrix: java_version: - '8' - '11' # TODO: Fix for java version 17 # - '17' runs-on: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: '${{ matrix.java_version }}' cache: maven - run: mvn clean test ================================================ FILE: .gitignore ================================================ target/ .project .settings/* .classpath ./bin/ package_and_mv.sh dependency-reduced-pom.xml jmeter.log pom.xml.releaseBackup release.properties /.idea/ /jmeter-prometheus-plugin.iml ================================================ FILE: CHANGELOG.md ================================================ # 0.6.2 * #117 is a bugfix in PrometheusListener.java to clear the collectors after the server is closed. # 0.6.1 * #116 is a bugfix in PrometheusServer.java to close the server after the delay. `prometheus.delay` will now work as expected. # 0.4.0 * #47 - bugfix so to correctly rename gui elements.\ * #51 - enable listening to assertions in counter type metrics. # 0.3.0 * #50 - listening to latency * #49 - listening to idle time * #48 - listening to connection time # 0.2.0 * #24 - success ratios to send zeros. * #46 - code is a label keyword for response code. * #11 - save jvm stats and allow for configuration. # 0.2.0-rc3 * #42 - implement counters (success, failure and total) * listener can now measure response sizes of samples in histogram and summary. * #41 - enhancement for metric re-use. # 0.2.0-rc2 Bugfixes for previous version. * #40 * #34 * undocumented problem copy/paste (serializable issues) of the config # 0.2.0-rc1 * first real release with only response time functionality in the listener. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## 1. Purpose A primary goal of Jmeter Prometheus Plugin is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. We invite all those who participate in Jmeter Prometheus Plugin to help us create safe and positive experiences for everyone. ## 2. Open Source Citizenship A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. ## 3. Expected Behavior The following behaviors are expected and requested of all community members: * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. * Exercise consideration and respect in your speech and actions. * Attempt collaboration before conflict. * Refrain from demeaning, discriminatory, or harassing behavior and speech. * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. ## 4. Unacceptable Behavior The following behaviors are considered harassment and are unacceptable within our community: * Violence, threats of violence or violent language directed against another person. * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. * Posting or displaying sexually explicit or violent material. * Posting or threatening to post other people’s personally identifying information ("doxing"). * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. * Inappropriate photography or recording. * Inappropriate physical contact. You should have someone’s consent before touching them. * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. * Deliberate intimidation, stalking or following (online or in person). * Advocating for, or encouraging, any of the above behavior. * Sustained disruption of community events, including talks and presentations. ## 5. Consequences of Unacceptable Behavior Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. Anyone asked to stop unacceptable behavior is expected to comply immediately. If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). ## 6. Reporting Guidelines If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. johrstrom@hotmail.com. Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. ## 7. Addressing Grievances If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Johrstrom with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. ## 8. Scope We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. ## 9. Contact info johrstrom@hotmail.com ## 10. License and attribution This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/) ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing * If you have a trivial fix or improvement, go ahead and create a pull request, addressing me (Jeff Ohrstrom) in the description of the pull request. * If you plan to do something more involved, you should probably open an issue so we can talk about it. However you can also just create the pull request and the issue and we can discuss the concept in the issue and the implementation in the pull request. * In terms of coding styles, I'm a native english speaker who just read [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)(tm) so I tend to value readable code. This library specifically tends towards basic Java standards and JMeter conventions. # side/related * This is an open source project, so commits/pull request are welcome. Forks are awesome. Issues are abundant and highly desired. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 Jeff Ohrstrom Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: NOTICE ================================================ Prometheus instrumentation library Apache JMeter Copyright 2016-2019 Jeff Ohrstrom This product includes software developed at AT&T Inc. (https://www.att.com) ================================================ FILE: README.md ================================================ [![Build Status](https://github.com/johrstrom/jmeter-prometheus-plugin/workflows/Tests/badge.svg)](https://github.com/johrstrom/jmeter-prometheus-plugin/actions?query=workflow%3ATests) [![Current Version](https://img.shields.io/maven-central/v/com.github.johrstrom/jmeter-prometheus-plugin.svg)](maven-central) # Prometheus Listener for Jmeter # Overview This JMeter plugin is highly configurable listener (and config element) to allow users define they're own metrics (names, types etc.) and expose them through a Prometheus /metrics API to be scraped by a Prometheus server. # Documentation More documentation can be found on [this project's wiki](https://github.com/johrstrom/jmeter-prometheus-plugin/wiki). # Listener QuickDoc Here's a simple example to get us started. This example [can be found here](/docs/examples/simple_prometheus_example.jmx). All the documentation on this README is from this jmx file. ![JMeter testplan](/docs/imgs/simple_testplan.png?raw=true) If we look closer at the very first Prometheus listener, it looks like the image below. ![JMeter testplan](/docs/imgs/listener_full.png?raw=true) Let's go through all the columns one by one. - **Name**: the name of the metric. - **Help**: The help message of the metric. - **Labels**: A comma seperated list of labels you want to apply to the metric. - `label` is a keyword. In JMeter it means the *name* of the sampler. - `code` is a keyword. It's the response code of the result. - JMeter variables can be used here. See the section [below](#Using-JMeter-variables-as-labels). - **Type**: The type of metric you're creating. - See [Prometheus documentation](https://prometheus.io/docs/concepts/metric_types/) about metric types. - [Success Ratio](#Success-Ratio) is something specific to this plugin and you can see the documentation below. - **Buckets of Quantiles**: - Buckets are comma seperated list of numbers. Can be integers or decimals. - Quantiles are comma `,` separated pair of decimals separated by a vertical bar `|`. The first decimal being the quantile and the second being the error rating. Optionally, after a `;` separator the lenght of the window used to calculate the quantile can be specified. Sample: `0.8,0.01|0.9,0.01|0.95,0.005|0.99,0.001;60` - **Listen To**: Dropdown to listen to samples or assertions. This only applies to Counters and SuccessRatio type metrics. - **Measuring**: Dropdown menu of all the things you can measure - See the [Type and Measuring compatibility matrix](#Type-and-Measuring-compatibility-matrix) section below. ### Using JMeter variables as labels Notice in the image above `jsr223_rt_as_summary` (the 2nd down) has `category,label` in it's labels column. This plugin allows you to use variables in the test plan as label values for a given metric. You can see here in the above image, I simply generated a random string and assigned it to the `category` jmeter variable, and this plugin exposed it as a label. As you can see below, it produced that metric with the `category="[A,B,C]"`. Again, this example [can be found here](/docs/examples/simple_prometheus_example.jmx). ![JMeter testplan](/docs/imgs/category_variable.png?raw=true) ### Listener output ![JMeter testplan](/docs/imgs/rt_as_sum.png?raw=true) ### Success Ratio Success ratio is a concept specific to this plugin library. Often we want measure success rates of samplers and it's difficult to do so when the failure for a given metric or labelset has never occurred. It's difficult because it involves computations with NaNs. So, we want to measure success ratios and be sure to emit zeros for both failures and success when the other is created for the first time. That way you can always safely run computations like rate() and so on. [Here's prometheus](https://www.robustperception.io/existential-issues-with-metrics) documentation on why it's important. [Here](https://github.com/johrstrom/jmeter-prometheus-plugin/issues/24) is this repositories issue for this feature. Here you can see from the example `jsr223_can_fail` turned into 3 metrics. The names have appeneded `_success`, `_failure` and `_total`. They're all counter type metrics that increment on success failure and total, respectively. From this example you can see that `4 success + 2 failures = 6 total`. Again, this metric guarantees a zero, for success or failure, for a metric that has a total of one or more. ![JMeter testplan](/docs/imgs/success_ratio_output.png?raw=true) ### Type and Measuring compatibility matrix Does it make sense to have a Counter measuring Response Time? No. Does it make sense to have a Histogram of total successes? No. This is a matrix of what metric types can measure what metrics. If you configure, say a histogram to measure count total, the plugin will likely do nothing to update that metric. **Bold** types can listen to samples or assertions (not both at the same time). Note that if you don't use `label` when listening to assertions you may get strange results. This is because *one* sample can generate many *assertion results* which are then counted. When there's no label to distinguish those counts, they'll be summed together which may or may not be expected. | | Histogram | Summary | Counter | Guage | Success Ratio | |:-----:|:------:|:------:|:------:|:------:|:------:| | Response time | x | x | | | | | Response size | x | x | | | | | Latency | x | x | | | | | Idle time | x | x | | | | | Connect time | x | x | | | | | Count total | | | **x** | | | | Failure total | | | **x** | | | | Success total | | | **x** | | | | Success Ratio | | | | | **x** | #### What about gauges I'm not quite sure how Guages make sense in the plugin. If you have a use case, I'd love to hear it. I wrote them in without actually having one, so you can technically create one, I just don't know how the listener may update it. # Config QuickDoc This library provides not only a listener, but a configuration element as well. This is useful when users have to make some computation for a specific use case and then want to expose that metric in Prometheus. These use cases for this are mostly functional, where say you're validating something about a response that's specific the thing under test. It works like this. Define a metric with this configuration element, and at test run-time this library will place that object in the jmeter variables from which you can access it. Let's consider this use case. You, have an API that tells you the current number animals categorized by `color` `size`and `mammal` - perhaps running down your street. You may create a counter like this as you watch them go by. ![JMeter testplan](/docs/imgs/prometheus_cfg.png?raw=true) So I then access the object I've created above like so. This is an absolutely trivial case of randomly generating the variables but the example is only trying to convey how one may interact with the objects created by the configuration element. However you extract the data, you can access and interact with the Prometheus metric you've created. See the [Prometheus Javadocs](https://prometheus.github.io/client_java/) for more information on their API. ![JMeter testplan](/docs/imgs/jsr223_use_prometheus_cfg.png?raw=true) Which will expose a counter with all the appropriate labels. ![JMeter testplan](/docs/imgs/prom_cfg_output.png?raw=true) # Usage Tips ### Skipping samplers or other elements You can re-use metrics in multiple listeners so long as they're defined in the **exact** same way. There will be undefined behavior if two or more listeners have the same metric (the same metric name) with different configurations. Here you see `first_random_sampler` and `second_random_sampler` in the labels of this metric, but you do not see `want_to_skip`, the thing that we're trying to skip. Note the composition of the test plan as shown [at the top of this page](#Listener-QuickDoc) ![JMeter testplan](/docs/imgs/rt_as_hist.png?raw=true) ### Visualization This plugin has limited "out of the box" functionality because it gives you, the user, total control over what the metric names may be. That said, here's a sample dashboard given [here in examples](/docs/examples/grafana_jsr223_test.json) such that if you have a local prometheus/grafana stack you can a dashboard that looks something like this. ![JMeter testplan](/docs/imgs/grafana_jsr223_test.png?raw=true) # Properties you can override To overrider properties, add the Properties in the jmeter.properties file (JMETER_HOME/bin folder) and restart Jmeter to take effect |Property | default | description| |:----------:|:-----------:|:-------------------------------:| |prometheus.port|9270|The port the http server will bind to | |prometheus.ip|127.0.0.1|The ip the http server will bind to. Containers may need `0.0.0.0`| |prometheus.delay|0|The delay (in seconds) the http server will wait before being destroyed| |prometheus.save.threads|true|True or false value to save and collect jmeter thread metrics| |prometheus.save.threads.name|jmeter\_threads|The name of the metric describing jmeter threads| |prometheus.save.jvm|true|Collect metrics from the JVM| # Download ## Maven Dependency We're now hosted on maven central! If you want to download this jar in a maven style project, simply add this dependency: ```xml com.github.johrstrom jmeter-prometheus-plugin ${jmeter-prometheus-plugin.version} ``` ## Programatically This URL below seems to be the only way to download jars from maven through `curl` or `wget`. Again, replace `0.6.0` here with the current version, which can be viewed at the top of this README. `https://search.maven.org/remotecontent?filepath=com/github/johrstrom/jmeter-prometheus-plugin/0.6.0/jmeter-prometheus-plugin-0.6.0.jar` ## Web Browser Search [maven central](https://search.maven.org/search?q=a:jmeter-prometheus-plugin) to get the latest version. This project is hosted [here](https://oss.sonatype.org/content/groups/public/com/github/johrstrom/jmeter-prometheus-plugin/) on [OSS sonatype org](https://oss.sonatype.org). ## Verifying I sign these release jars so you can verify with this method (of course the version is going to change): ```bash gpg --verify jmeter-prometheus-plugin-0.5.0.jar.asc ``` You should see output similar to this. ```bash gpg: assuming signed data in 'jmeter-prometheus-plugin-0.5.0.jar' gpg: Signature made Thu 22 Aug 2019 09:35:15 PM EDT gpg: using RSA key 6F5EAC674B279301932EC1FEAC2AEC6C76D4AF12 gpg: Good signature from "Jeff Ohrstrom (Jeff Ohrstrom's personal key) " [ultimate] ``` # Building To build, simply maven package: ``` mvn clean package ``` This creates 2 jars, a shaded jar that has all the dependencies within it (this is the one you want) and the original jar. Both are in the target directory. Simply move the jar to your $JMETER\_HOME/lib/ext directory as with any JMeter plugin and you're ready to go! ## Feedback Feel free to open issues against this project, even to ask questions. ================================================ FILE: docs/examples/grafana.json ================================================ { "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "description": "A grafana dashboard to inspect jmeter metrics via prometheus exporter", "editable": true, "gnetId": 2492, "graphTooltip": 1, "id": 2, "iteration": 1582126578426, "links": [], "panels": [ { "cacheTimeout": null, "colorBackground": true, "colorValue": false, "colors": [ "#3274D9", "#3274D9", "#3274D9" ], "datasource": "Prometheus", "format": "none", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 2, "w": 6, "x": 0, "y": 0 }, "id": 12, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": " vus", "postfixFontSize": "100%", "prefix": "", "prefixFontSize": "50%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "#447ebc", "full": true, "lineColor": "#447ebc", "show": false }, "tableColumn": "", "targets": [ { "expr": "sum(jmeter_threads{state=\"active\"})", "format": "time_series", "hide": false, "instant": true, "interval": "", "intervalFactor": 1, "legendFormat": "", "refId": "A", "step": 4 } ], "thresholds": "", "title": "", "type": "singlestat", "valueFontSize": "100%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": true, "colorValue": false, "colors": [ "#A352CC", "#A352CC", "#A352CC" ], "datasource": "Prometheus", "decimals": 1, "format": "ops", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 2, "w": 6, "x": 6, "y": 0 }, "id": 7, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": "", "postfixFontSize": "50%", "prefix": "", "prefixFontSize": "50%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "#508642", "full": true, "lineColor": "#508642", "show": false }, "tableColumn": "", "targets": [ { "expr": "sum(irate(Ratio_success[$interval]))", "format": "time_series", "instant": true, "intervalFactor": 1, "legendFormat": "", "refId": "E" } ], "thresholds": "", "title": "", "type": "singlestat", "valueFontSize": "100%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": true, "colorValue": false, "colors": [ "#E0B400", "#E0B400", "#E0B400" ], "datasource": "Prometheus", "decimals": 1, "format": "ms", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 2, "w": 6, "x": 12, "y": 0 }, "id": 14, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": "", "postfixFontSize": "50%", "prefix": "Avg. ", "prefixFontSize": "100%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "#e5ac0e", "full": true, "lineColor": "#e5ac0e", "show": false }, "tableColumn": "", "targets": [ { "expr": "avg((rate(ResponseTime_sum{code=\"200\"}[$interval])/(rate(ResponseTime_count{code=\"200\"}[$interval])>0)))", "format": "time_series", "hide": false, "instant": true, "interval": "", "intervalFactor": 1, "legendFormat": "Response time", "metric": "", "refId": "A", "step": 4 } ], "thresholds": "", "title": "", "type": "singlestat", "valueFontSize": "100%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": true, "colorValue": false, "colors": [ "#37872D", "#FF9830", "#E02F44" ], "datasource": "Prometheus", "decimals": 1, "format": "percentunit", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 2, "w": 6, "x": 18, "y": 0 }, "id": 8, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": " error rate", "postfixFontSize": "100%", "prefix": "", "prefixFontSize": "50%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "#bf1b00", "full": true, "lineColor": "#bf1b00", "show": false }, "tableColumn": "", "targets": [ { "expr": "(avg(rate(Ratio_failure[$interval]))/avg(rate(Ratio_total[$interval])))", "format": "time_series", "instant": true, "intervalFactor": 1, "legendFormat": "", "refId": "A", "step": 4 } ], "thresholds": "0.1,10", "title": "", "type": "singlestat", "valueFontSize": "100%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 2 }, "id": 17, "panels": [], "repeat": null, "title": "Summary", "type": "row" }, { "aliasColors": { "Failure ": "#bf1b00", "OK": "dark-green" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fill": 0, "fillGradient": 0, "gridPos": { "h": 10, "w": 12, "x": 0, "y": 3 }, "height": "400", "hiddenSeries": false, "id": 36, "legend": { "alignAsTable": true, "avg": true, "current": false, "hideEmpty": true, "hideZero": true, "max": true, "min": true, "rightSide": false, "show": true, "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 3, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "Virtual Users", "color": "#3274D9", "fill": 4, "lines": true, "linewidth": 3, "yaxis": 2 }, { "alias": "Request OK per second", "color": "#19730E" }, { "alias": "Request KO per second", "color": "#AD0317" } ], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(jmeter_threads{state=\"active\"})", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "Virtual Users", "refId": "B", "step": 1 }, { "expr": "sum(rate(Ratio_success[$interval]))", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "Request OK per second", "refId": "A" }, { "expr": "sum(rate(Ratio_failure[$interval])) ", "legendFormat": "Request KO per second", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Virtual Users vs OK/KO", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "none", "label": "ops", "logBase": 1, "max": null, "min": null, "show": true }, { "decimals": null, "format": "short", "label": "VU", "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "KO": "#890F02", "Requests KO": "#bf1b00", "Requests OK": "#508642", "Requests per second": "#447ebc", "failure": "#890F02", "success": "#7EB26D" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 10, "fillGradient": 0, "gridPos": { "h": 10, "w": 12, "x": 12, "y": 3 }, "height": "400", "hiddenSeries": false, "id": 3, "legend": { "alignAsTable": true, "avg": true, "current": false, "max": true, "min": true, "show": true, "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "Total Requests per second", "color": "#A352CC", "fill": 0, "linewidth": 3, "steppedLine": false }, { "alias": "OK", "yaxis": 2 }, { "alias": "KO", "yaxis": 2 } ], "spaceLength": 10, "stack": true, "steppedLine": true, "targets": [ { "expr": "(sum(rate(Ratio_success[$interval]))/sum(rate(Ratio_total[$interval])))", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "OK", "refId": "A", "step": 2 }, { "expr": "(sum(rate(Ratio_failure[$interval]))/sum(rate(Ratio_total[$interval])))", "format": "time_series", "intervalFactor": 1, "legendFormat": "KO", "refId": "B" }, { "expr": "sum(rate(Ratio_total[$interval]))", "format": "time_series", "intervalFactor": 1, "legendFormat": "Total Requests per second", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total Requests vs OK/KO", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "none", "label": "ops", "logBase": 1, "max": null, "min": null, "show": true }, { "decimals": 0, "format": "percentunit", "label": "", "logBase": 1, "max": "1", "min": "0", "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "Failure ": "#bf1b00", "Virtual Users": "semi-dark-blue" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fill": 0, "fillGradient": 0, "gridPos": { "h": 10, "w": 12, "x": 0, "y": 13 }, "height": "400", "hiddenSeries": false, "id": 30, "legend": { "alignAsTable": true, "avg": true, "current": false, "hideEmpty": true, "hideZero": true, "max": true, "min": true, "rightSide": false, "show": true, "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 3, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "Avg Response Time", "color": "#F2CC0C" }, { "alias": "Virtual Users", "color": "#3274D9", "fill": 4, "yaxis": 2 } ], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "avg((rate(ResponseTime_sum{code=\"200\"}[$interval])/(rate(ResponseTime_count{code=\"200\"}[$interval])>0)))", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "Avg Response Time", "refId": "B", "step": 1 }, { "expr": "sum(jmeter_threads{state=\"active\"})", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "Virtual Users", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Avg. Response Time vs Virtual Users", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "decimals": null, "format": "short", "label": "VU", "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "Failure ": "#bf1b00" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fill": 0, "fillGradient": 0, "gridPos": { "h": 10, "w": 12, "x": 12, "y": 13 }, "height": "400", "hiddenSeries": false, "id": 29, "legend": { "alignAsTable": true, "avg": true, "current": false, "hideEmpty": true, "hideZero": true, "max": true, "min": true, "rightSide": false, "show": true, "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 3, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "OK", "color": "#19730E", "yaxis": 2 }, { "alias": "Avg Response Time", "color": "#F2CC0C" }, { "alias": "KO", "color": "#AD0317", "yaxis": 2 } ], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "avg((rate(ResponseTime_sum{code=\"200\"}[$interval])/(rate(ResponseTime_count{code=\"200\"}[$interval])>0)))", "format": "time_series", "hide": false, "instant": false, "interval": "", "intervalFactor": 1, "legendFormat": "Avg Response Time", "refId": "B", "step": 1 }, { "expr": "sum(rate(Ratio_success[$interval]))", "format": "time_series", "hide": true, "interval": "", "intervalFactor": 1, "legendFormat": "OK", "refId": "A" }, { "expr": "sum(rate(Ratio_failure[$interval])) ", "format": "time_series", "hide": true, "intervalFactor": 1, "legendFormat": "KO", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Avg. Response Time vs OK/KO", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "decimals": null, "format": "short", "label": "ops", "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "columns": [], "datasource": "Prometheus", "fontSize": "100%", "gridPos": { "h": 8, "w": 12, "x": 0, "y": 23 }, "id": 33, "links": [], "options": {}, "pageSize": 20, "scroll": true, "showHeader": true, "sort": { "col": 1, "desc": false }, "styles": [ { "alias": "Time", "align": "auto", "dateFormat": "YYYY-MM-DD HH:mm:ss", "link": false, "pattern": "Time", "type": "hidden" }, { "alias": "Label", "align": "auto", "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "dateFormat": "YYYY-MM-DD HH:mm:ss", "decimals": 2, "mappingType": 1, "pattern": "label", "thresholds": [], "type": "number", "unit": "short" }, { "alias": "Count", "align": "auto", "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "dateFormat": "YYYY-MM-DD HH:mm:ss", "decimals": 0, "mappingType": 1, "pattern": "Value", "thresholds": [], "type": "number", "unit": "locale" }, { "alias": "", "align": "auto", "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "decimals": 2, "pattern": "/.*/", "thresholds": [], "type": "number", "unit": "short" } ], "targets": [ { "expr": "sum(Ratio_success) by (label) ", "format": "table", "instant": true, "intervalFactor": 1, "legendFormat": "", "refId": "A" } ], "title": "OK", "transform": "table", "type": "table" }, { "columns": [], "datasource": "Prometheus", "fontSize": "100%", "gridPos": { "h": 8, "w": 12, "x": 12, "y": 23 }, "id": 34, "links": [], "options": {}, "pageSize": 25, "scroll": true, "showHeader": true, "sort": { "col": 1, "desc": false }, "styles": [ { "alias": "Time", "align": "auto", "dateFormat": "YYYY-MM-DD HH:mm:ss", "link": false, "pattern": "Time", "type": "hidden" }, { "alias": "Label", "align": "auto", "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "dateFormat": "YYYY-MM-DD HH:mm:ss", "decimals": 2, "mappingType": 1, "pattern": "label", "thresholds": [], "type": "number", "unit": "short" }, { "alias": "Count", "align": "auto", "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0)" ], "dateFormat": "YYYY-MM-DD HH:mm:ss", "decimals": 0, "mappingType": 1, "pattern": "Value", "thresholds": [ "" ], "type": "number", "unit": "none" }, { "alias": "", "align": "auto", "colorMode": null, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "decimals": 2, "pattern": "/.*/", "thresholds": [], "type": "number", "unit": "short" } ], "targets": [ { "expr": "sum(Ratio_failure) by (label) ", "format": "table", "instant": true, "intervalFactor": 1, "legendFormat": "", "refId": "A" } ], "title": "KO", "transform": "table", "type": "table" }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 31 }, "id": 18, "panels": [], "repeat": null, "title": "Response times", "type": "row" }, { "aliasColors": { "avg jmeter": "light-green" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fill": 0, "fillGradient": 0, "gridPos": { "h": 11, "w": 24, "x": 0, "y": 32 }, "height": "400", "hiddenSeries": false, "id": 35, "legend": { "alignAsTable": true, "avg": true, "current": true, "hideEmpty": true, "hideZero": true, "max": true, "min": true, "rightSide": false, "show": true, "sort": "avg", "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 3, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": " avg (rate(ResponseTime_sum{code=~\"1.*|2.*|3.*|4.*|5.*\"}[$interval]) / (rate(ResponseTime_count{code=~\"1.*|2.*|3.*|4.*|5.*\"}[$interval])>0)) by (label)", "format": "time_series", "hide": false, "instant": false, "interval": "", "intervalFactor": 1, "legendFormat": "{{label}} ", "refId": "A", "step": 1 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Avg. response time", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fill": 1, "fillGradient": 0, "gridPos": { "h": 10, "w": 24, "x": 0, "y": 43 }, "height": "400", "hiddenSeries": false, "id": 13, "legend": { "alignAsTable": true, "avg": true, "current": true, "hideEmpty": true, "hideZero": true, "max": true, "min": true, "rightSide": false, "show": true, "sort": "avg", "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 3, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "avg(ResponseTime{code=\"200\"}) by (quantile)", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "{{quantile}}", "refId": "B", "step": 1 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Pct response times", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "decimals": null, "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 53 }, "id": 19, "panels": [], "repeat": null, "title": "Requests per second", "type": "row" }, { "aliasColors": { "KO": "semi-dark-red", "OK": "semi-dark-green", "failure": "#890F02" }, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 0, "fillGradient": 0, "gridPos": { "h": 10, "w": 24, "x": 0, "y": 54 }, "height": "400", "hiddenSeries": false, "id": 4, "legend": { "alignAsTable": true, "avg": true, "current": false, "hideEmpty": false, "hideZero": false, "max": true, "min": true, "show": true, "total": false, "values": true }, "lines": false, "linewidth": 3, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "repeatDirection": "h", "seriesOverrides": [ { "alias": "KO", "color": "#E02F44" }, { "alias": "OK", "color": "#56A64B" } ], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "sum(rate(Ratio_success[$interval]))", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "OK", "metric": "", "refId": "A", "step": 2 }, { "expr": "sum(rate(Ratio_failure[$interval]))", "format": "time_series", "hide": false, "instant": false, "intervalFactor": 1, "legendFormat": "KO", "refId": "B", "step": 2 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "requests": "#64B0C8" }, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 5, "fillGradient": 0, "gridPos": { "h": 10, "w": 24, "x": 0, "y": 64 }, "height": "400", "hiddenSeries": false, "id": 5, "legend": { "alignAsTable": true, "avg": true, "current": false, "max": true, "min": true, "rightSide": false, "show": true, "sort": "max", "sortDesc": true, "total": false, "values": true }, "lines": false, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "sum(rate(Ratio_total[$interval])) by (label)", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "{{label}}", "refId": "A", "step": 2 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "by label", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "HTTP 404": "#BF1B00" }, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 5, "fillGradient": 0, "gridPos": { "h": 10, "w": 24, "x": 0, "y": 74 }, "height": "400", "hiddenSeries": false, "id": 6, "legend": { "alignAsTable": true, "avg": true, "current": false, "max": true, "min": true, "rightSide": false, "show": true, "total": false, "values": true }, "lines": false, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "sum by (code) (rate(Ratio_total[$interval]))", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "HTTP {{code}}", "refId": "A", "step": 2 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "by HTTP code", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 84 }, "id": 20, "panels": [ { "aliasColors": { "moviri.com/404 - HTTP 404": "#58140C" }, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 5, "fillGradient": 0, "gridPos": { "h": 10, "w": 24, "x": 0, "y": 86 }, "height": "400", "hiddenSeries": false, "id": 15, "legend": { "alignAsTable": true, "avg": true, "current": false, "max": true, "min": true, "show": true, "total": false, "values": true }, "lines": false, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "sum by(code) (rate(Ratio_failure[$interval]))", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 1, "legendFormat": "HTTP {{code}}", "refId": "A", "step": 2 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "by HTTP code", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "moviri.com/servicesKO - servicesKO": "#58140C" }, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 5, "fillGradient": 0, "gridPos": { "h": 10, "w": 24, "x": 0, "y": 96 }, "height": "400", "hiddenSeries": false, "id": 16, "legend": { "alignAsTable": true, "avg": true, "current": false, "max": true, "min": true, "show": true, "sort": "max", "sortDesc": true, "total": false, "values": true }, "lines": false, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "sum by (label) (rate(Ratio_failure[$interval]))", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "{{label}}", "refId": "B", "step": 2 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "by label", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "title": "Errors", "type": "row" }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 85 }, "id": 22, "panels": [], "title": "Test farm", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 24, "x": 0, "y": 86 }, "hiddenSeries": false, "id": 24, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "jmeter - PS Scavenge v1", "yaxis": 1 } ], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "rate(jvm_gc_collection_seconds_sum{job=~\"jmeter\"}[1m])", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "{{instance}} - {{gc}} ", "refId": "A" }, { "expr": "rate(jvm_gc_collection_seconds_sum{job=~\"jmeter\"}[$interval]) / ignoring(gc) group_left rate(process_cpu_seconds_total{job=~\"jmeter\"}[$interval])", "format": "time_series", "hide": true, "intervalFactor": 1, "legendFormat": "{{instance}} - {{gc}} v2", "refId": "B" }, { "expr": "rate(jvm_gc_collection_seconds_sum{job=~\"jmeter\"}[1m]) / rate(jvm_gc_collection_seconds_count{job=~\"jmeter\"}[1m])", "hide": true, "legendFormat": "{{instance}} - {{gc}} v3", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "GC % time", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": null, "format": "percentunit", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 24, "x": 0, "y": 94 }, "hiddenSeries": false, "id": 27, "legend": { "alignAsTable": true, "avg": true, "current": true, "max": false, "min": false, "rightSide": true, "show": true, "sort": "avg", "sortDesc": false, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "(jvm_memory_pool_bytes_committed {job=~\"jmeter\"}) - (jvm_memory_pool_bytes_used{job=~\"jmeter\"}) ", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "{{instance}} - {{pool}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM heap - free memory by pool", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 24, "x": 0, "y": 102 }, "hiddenSeries": false, "id": 26, "legend": { "alignAsTable": true, "avg": true, "current": true, "max": false, "min": false, "rightSide": true, "show": true, "sort": "avg", "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "(jvm_memory_pool_bytes_used{job=\"jmeter\"}) ", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "{{instance}} - {{pool}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM heap - used memory by pool", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 24, "x": 0, "y": 110 }, "hiddenSeries": false, "id": 25, "legend": { "alignAsTable": true, "avg": true, "current": true, "max": false, "min": false, "rightSide": true, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "(jvm_memory_bytes_max {job=\"jmeter\"}) - (jvm_memory_bytes_used{job=\"jmeter\"})", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "{{instance}} - {{area}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM free memory", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "refresh": "30s", "schemaVersion": 22, "style": "dark", "tags": [ "jmeter" ], "templating": { "list": [ { "allValue": null, "current": { "selected": false, "text": "30s", "value": "30s" }, "hide": 0, "includeAll": false, "label": "interval", "multi": false, "name": "interval", "options": [ { "selected": true, "text": "30s", "value": "30s" }, { "selected": false, "text": "1m", "value": "1m" }, { "selected": false, "text": "5m", "value": "5m" }, { "selected": false, "text": "10m", "value": "10m" } ], "query": "30s,1m,5m,10m", "skipUrlSync": false, "type": "custom" } ] }, "time": { "from": "now-15m", "to": "now" }, "timepicker": { "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ], "time_options": [ "5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d" ] }, "timezone": "browser", "title": "JMeter", "uid": "jbtLA0-Wk5", "version": 2 } ================================================ FILE: docs/examples/simple_prometheus_example.jmx ================================================ false true false continue false -1 1 1 false This sampler just does something to generate data for the prometheus listener. Note that just by calling the function in the parameters section assigns the variable 'category' which the PrometheusListener plugin will pick up on. true ${__RandomString(1,ABC,category)} import java.util.Random; import org.apache.commons.lang3.RandomStringUtils; Random rand = new Random(); int maxWait = 3500; // emulate different 'categories' being slower than others if(args[0].equals("A")) { maxWait = 1000; } else if (args[0].equals("B")) { maxWait = 1750; } else if (args[0].equals("C")) { maxWait = 3000; } int wait = rand.nextInt(maxWait); int fail = rand.nextInt(10); log.info("sleeping for {} ms", wait); Thread.currentThread().sleep(wait); if(fail >= 8 ){ SampleResult.setSuccessful(false); SampleResult.setResponseCode("404"); }else{ SampleResult.setResponseCode("204"); } SampleResult.setResponseData(new byte[wait]); groovy SizeAssertion.response_network_size 2000 4 the response time for a jsr223 sampler jsr223_rt_as_hist HISTOGRAM label 100,500,1000,3000 samples ResponseTime the response time for a jsr223 sampler jsr223_rt_as_summary SUMMARY category label code 0.75,0.5|0.95,0.1|0.99,0.01 ResponseTime the total number of samplers jsr223_count_total COUNTER label CountTotal the total number of successful samplers jsr223_success_total COUNTER label SuccessTotal the response size for a jsr223 sampler jsr223_rsize_as_hist HISTOGRAM 100,500,1000,3000 ResponseSize success ratio of the can_fail_sampler jsr223_can_fail SUCCESS_RATIO SuccessRatio the latency (ttfb) for a jsr223 sampler jsr223_latency_as_hist HISTOGRAM label 100,500,1000,3000 Latency the idle time for a jsr223 sampler jsr223_idle_time SUMMARY 0.75,0.5|0.95,0.1|0.99,0.01 IdleTime default help string jsr223_assertions SUCCESS_RATIO label SuccessRatio assertions This listener "measures" everything, sometimes in summaries, sometimes in histograms. continue false -1 1 1 false default help string jsr223_animals_total COUNTER color size mammal groovy ${__RandomString(1,RGB,color)} ${__RandomString(1,SML,size)} ${__RandomString(1,YN,mammal)} true import io.prometheus.client.*; String color = vars.get("color"); String size = vars.get("size"); String mammal = vars.get("mammal"); Counter counter = (Counter) vars.getObject("jsr223_animals_total"); counter.labels(color,size,mammal).inc(); 3000 continue false -1 1 1 false This sampler just does something to generate data for the prometheus listener. Note that just by calling the function in the parameters section assigns the variable 'category' which the PrometheusListener plugin will pick up on. true ${__RandomString(1,ABC,category)} import java.util.Random; Random rand = new Random(); int wait = rand.nextInt(3500); log.info("sleeping for {} ms", wait); Thread.currentThread().sleep(wait); groovy the response time for a jsr223 sampler jsr223_rt_as_hist HISTOGRAM label 100,500,1000,3000 samples ResponseTime This sampler just does something to generate data for the prometheus listener. Note that just by calling the function in the parameters section assigns the variable 'category' which the PrometheusListener plugin will pick up on. true ${__RandomString(1,ABC,category)} import java.util.Random; Random rand = new Random(); int wait = rand.nextInt(3500); log.info("sleeping for {} ms", wait); Thread.currentThread().sleep(wait); groovy This sampler just does something to generate data for the prometheus listener. Note that just by calling the function in the parameters section assigns the variable 'category' which the PrometheusListener plugin will pick up on. true ${__RandomString(1,ABC,category)} import java.util.Random; Random rand = new Random(); int wait = rand.nextInt(3500); log.info("sleeping for {} ms", wait); Thread.currentThread().sleep(wait); groovy the response time for a jsr223 sampler jsr223_rt_as_hist HISTOGRAM label 100,500,1000,3000 samples ResponseTime false saveConfig true true true true true true true false true true false false false true false false false true 0 true true true true true ================================================ FILE: licenses/io.prometheus.LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: licenses/io.prometheus.NOTICE ================================================ Prometheus instrumentation library for JVM applications Copyright 2012-2015 The Prometheus Authors This product includes software developed at Boxever Ltd. (http://www.boxever.com/). This product includes software developed at SoundCloud Ltd. (http://soundcloud.com/). This product includes software developed as part of the Ocelli project by Netflix Inc. (https://github.com/Netflix/ocelli/). ================================================ FILE: makefile ================================================ .DEFAULT_GOAL := help SHELL := /bin/bash THIS_FILE := $(lastword $(MAKEFILE_LIST)) THIS_FOLDER := $(shell basename $(CURDIR)) #Service version, tag and image name master_version := $$(git show master:pom.xml | xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" -) this_version := $$(git show master:pom.xml | xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" -) this_branch := $(shell git rev-parse --abbrev-ref HEAD) repo_location := $(strip $(shell git rev-parse --show-toplevel)) clean ?= false ifeq ($(strip $(clean)),true) CLEAN := clean endif .PHONY: help help: ## Show this help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: clean clean: ## Cleans the repo @mvn clean .PHONY: build build: ## Builds jar @mvn install -Dgpg.skip .PHONY: test test: ## run tests @mvn verify -Dgpg.skip .PHONY: info info: ## Print some info on the repo @echo "this_version: $(this_version)" && \ echo "this_branch: $(this_branch)" && \ echo "repo_location: $(repo_location)" &&\ echo "master_version: $(master_version)" ================================================ FILE: pom.xml ================================================ 4.0.0 com.github.johrstrom jmeter-prometheus-plugin 0.7.2-SNAPSHOT Jmeter-Prometheus Listener Plugin A Jmeter plugin that creates a Prometheus endpoint of results. https://github.com/johrstrom/jmeter-prometheus-plugin 0.16.0 5.5 1.8 1.8 UTF-8 1.6 https://github.com/johrstrom/jmeter-prometheus-plugin/issues GitHub Issues Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license https://github.com/johrstrom/jmeter-prometheus-plugin scm:git:git://github.com/johrstrom/jmeter-prometheus-plugin.git scm:git:git@github.com:johrstrom/jmeter-prometheus-plugin.git HEAD ossrh https://oss.sonatype.org/content/repositories/snapshots ossrh https://oss.sonatype.org/service/local/staging/deploy/maven2 johrstrom@hotmail.com Jeff Ohrstrom https://github.com/johrstrom kevinsawicki giovanni.gibilisco@akamas.io Giovanni Paolo Gibilisco https://github.com/GiovanniPaoloGibilisco GiovanniPaoloGibilisco org.apache.jmeter ApacheJMeter_core ${jmeter.version} provided org.apache.jmeter ApacheJMeter_java ${jmeter.version} provided org.apache.jmeter ApacheJMeter_components ${jmeter.version} provided io.prometheus simpleclient ${prometheus.version} io.prometheus simpleclient_servlet ${prometheus.version} io.prometheus simpleclient_httpserver ${prometheus.version} io.prometheus simpleclient_hotspot ${prometheus.version} junit junit 4.13.1 test licenses * . LICENSE NOTICE org.apache.maven.plugins maven-surefire-plugin 3.1.0 methods 4 false org.apache.maven.plugins maven-shade-plugin 3.0.0 org.apache.jmeter:* *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA package shade maven-resources-plugin 3.0.1 copy-resources process-test-resources copy-resources ${basedir}/target/test-classes docs/examples true org.apache.maven.plugins maven-release-plugin 2.5.3 true false forked-path org.apache.maven.scm maven-scm-provider-gitexe 1.9.5 org.apache.maven.plugins maven-gpg-plugin ${maven-gpg-plugin.version} sign-artifacts verify sign maven-deploy-plugin 2.8.2 default-deploy deploy deploy org.sonatype.plugins nexus-staging-maven-plugin 1.6.7 true ossrh https://oss.sonatype.org/ true org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar ================================================ FILE: src/main/java/com/github/johrstrom/collector/BaseCollectorConfig.java ================================================ package com.github.johrstrom.collector; import io.prometheus.client.*; import org.apache.commons.lang3.RandomStringUtils; import org.apache.jmeter.testelement.AbstractTestElement; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.JMeterProperty; import org.apache.jmeter.testelement.property.NullProperty; import org.apache.jmeter.testelement.property.PropertyIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; /** * The base class for turning text/strings (from the JMeter GUI, or a .jmx file, etc.) into Prometheus Collector * objects. Along with being a TestElement so that it can be serialized (for the .jmx file) it also handles all * the logic of parsing strings into double arrays. * * There are also static utility functions to actually instantiate a Collector of a given type from a configuration. * * * buckets are comma seperated list of decimals. An example: * 100,200,300,400.3 * quantiles are comma seperated pair of decimals seperated by |. The first decimal being the quantile and the second being the * error rating. An example: * 0.999,0.1|0.99,0.2|0.75,0.3 * * @author Jeff Ohrstrom * */ public class BaseCollectorConfig extends AbstractTestElement { private static final long serialVersionUID = 1520731432941268549L; public static String HELP = "collector.help"; public static String NAME = "collector.metric_name"; public static String TYPE = "collector.type"; public static String LABELS = "collector.labels"; public static String QUANTILES_OR_BUCKETS = "collector.quantiles_or_buckets"; public static double[] DEFAULT_BUCKET_SIZES = {100,500,1000,3000}; public static String DEFAULT_BUCKET_SIZES_STRING = "100,500,1000,3000"; public static long DEFAULT_QUANTILE_WINDOW_LENGHT = 60; public static QuantileDefinition[] DEFAULT_QUANTILES = QuantileDefinition.defaultQuantiles(); public static String DEFAULT_QUANTILES_STRING = QuantileDefinition.arrayToString(DEFAULT_QUANTILES); public static String DEFAULT_HELP_STRING = "default help string"; public static String METRIC_NAME_BASE = "jmeter_autogenerated_metric_"; private static Logger log = LoggerFactory.getLogger(BaseCollectorConfig.class); public enum JMeterCollectorType { COUNTER, GAUGE, HISTOGRAM, SUMMARY, SUCCESS_RATIO } public BaseCollectorConfig(){ this.setHelp(DEFAULT_HELP_STRING); this.setMetricName(this.getRandomMetricName()); this.setType(JMeterCollectorType.COUNTER.toString()); this.setLabels(new String[0]); this.setQuantileOrBucket(""); } public String getHelp() { return this.getPropertyAsString(HELP, DEFAULT_HELP_STRING); } public void setHelp(String help) { if(help == null || help.isEmpty()) this.setProperty(HELP, DEFAULT_HELP_STRING); else this.setProperty(HELP, help); } public String getType() { return this.getPropertyAsString(TYPE, JMeterCollectorType.COUNTER.name()); } public JMeterCollectorType getCollectorType() { return JMeterCollectorType.valueOf(this.getType()); } public void setType(String type) { this.setProperty(TYPE, type); if(type != null && type.equals(JMeterCollectorType.HISTOGRAM.name()) && this.getQuantileOrBucket().isEmpty()) this.setQuantileOrBucket(DEFAULT_BUCKET_SIZES_STRING); if(type != null && type.equals(JMeterCollectorType.SUMMARY.name()) && this.getQuantileOrBucket().isEmpty()) this.setQuantileOrBucket(DEFAULT_QUANTILES_STRING); } public String getQuantileOrBucket() { return this.getPropertyAsString(QUANTILES_OR_BUCKETS,""); } public void setQuantileOrBucket(String quantileOrBucket) { this.setProperty(QUANTILES_OR_BUCKETS, quantileOrBucket); } public double[] getBuckets() { String buckets = getQuantileOrBucket(); if(buckets == null || buckets.isEmpty()) { return DEFAULT_BUCKET_SIZES; }else { return this.parseBucketsFromString(buckets); } } public QuantileDefinition[] getQuantiles() { String quantiles = getQuantileOrBucket(); if(quantiles == null || quantiles.isEmpty()) { return DEFAULT_QUANTILES; }else { return QuantileDefinition.parseQuantilesFromString(quantiles); } } public long getQuantileWindowLength() { String quantiles = getQuantileOrBucket(); if (quantiles == null || quantiles.isEmpty()) { return DEFAULT_QUANTILE_WINDOW_LENGHT; } else { return QuantileDefinition.parseQuantilesWindowLengthFromString(quantiles); } } public String getMetricName() { return this.getPropertyAsString(NAME, getRandomMetricName()); } public void setMetricName(String name) { if(name == null || name.isEmpty()) this.setProperty(NAME, getRandomMetricName()); else this.setProperty(NAME, name); } public String getRandomMetricName() { return METRIC_NAME_BASE + RandomStringUtils.randomAlphanumeric(8); } public void setLabels(String labels) { this.setLabels(labels.split(",")); } public void setLabels(String[] labels) { List list = new ArrayList(Arrays.asList(labels)); Iterator it = list.iterator(); while(it.hasNext()) { // can't have empty strings from Gui String item = it.next(); if(item == null || item.isEmpty()) { it.remove(); } } this.setProperty(new CollectionProperty(LABELS, list)); } public String[] getLabels() { JMeterProperty prop = this.getProperty(LABELS); if(prop == null || prop instanceof NullProperty) { return new String[0]; } CollectionProperty colletion = (CollectionProperty) prop; String[] retArray = new String[colletion.size()]; PropertyIterator it = colletion.iterator(); int i=0; while(it.hasNext()) { String next = it.next().getStringValue(); retArray[i] = next; i++; } return retArray; } public String getLabelsAsString() { StringBuilder sb = new StringBuilder(); String[] labels = this.getLabels(); for(int i = 0; i < labels.length; i++) { sb.append(labels[i]); if(i+1 < labels.length) sb.append(","); } return sb.toString(); } public static Counter newCounter(BaseCollectorConfig cfg) throws Exception { io.prometheus.client.Counter.Builder builder = new Counter.Builder() .help(cfg.getHelp()) .name(cfg.getMetricName()); String[] labels = cfg.getLabels(); if(labels.length != 0) { builder.labelNames(labels); } return builder.create(); } public static Summary newSummary(BaseCollectorConfig cfg) throws Exception { io.prometheus.client.Summary.Builder builder = new Summary.Builder() .name(cfg.getMetricName()) .help(cfg.getHelp()) .maxAgeSeconds(cfg.getQuantileWindowLength()); String[] labels = cfg.getLabels(); if(labels.length != 0) { builder.labelNames(labels); } for(QuantileDefinition def : cfg.getQuantiles()) { builder.quantile(def.quantile, def.error); } return builder.create(); } public static Histogram newHistogram(BaseCollectorConfig cfg) throws Exception { io.prometheus.client.Histogram.Builder builder = new Histogram.Builder() .name(cfg.getMetricName()) .help(cfg.getHelp()) .buckets(cfg.getBuckets()); String[] labels = cfg.getLabels(); if(labels.length != 0) { builder.labelNames(labels); } return builder.create(); } public static Gauge newGauge(BaseCollectorConfig cfg) throws Exception { io.prometheus.client.Gauge.Builder builder = new Gauge.Builder() .name(cfg.getMetricName()) .help(cfg.getHelp()); String[] labels = cfg.getLabels(); if(labels.length != 0) { builder.labelNames(labels); } return builder.create(); } public static Collector fromConfig(BaseCollectorConfig cfg) { JMeterCollectorType t = cfg.getCollectorType(); Collector c = null; try { if(t.equals(JMeterCollectorType.COUNTER)) { c = BaseCollectorConfig.newCounter(cfg); }else if(t.equals(JMeterCollectorType.SUMMARY)) { c = BaseCollectorConfig.newSummary(cfg); }else if(t.equals(JMeterCollectorType.HISTOGRAM)) { c = BaseCollectorConfig.newHistogram(cfg); }else if(t.equals(JMeterCollectorType.GAUGE)) { c = BaseCollectorConfig.newGauge(cfg); }else if(t.equals(JMeterCollectorType.SUCCESS_RATIO)) { c = new SuccessRatioCollector(cfg); } } catch(Exception e) { log.error(String.format("Didn't create collector from definition %s because of an error", cfg), e); } return c; } @Override public boolean equals(Object o) { if(o instanceof BaseCollectorConfig) { BaseCollectorConfig other = (BaseCollectorConfig) o; boolean sameName = this.getName().equals(other.getName()); boolean sameHelp = this.getHelp().equals(other.getHelp()); boolean sameType = this.getType().equals(other.getType()); boolean sameQB = this.getQuantileOrBucket().equals(other.getQuantileOrBucket()); boolean sameLabels = this.getLabelsAsString().equalsIgnoreCase(other.getLabelsAsString()); return sameName && sameHelp && sameType && sameQB & sameLabels; } return false; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.getMetricName().hashCode(); result = prime * result + this.getHelp().hashCode(); result = prime * result + this.getType().hashCode(); result = prime * result + this.getQuantileOrBucket().hashCode(); result = prime * result + this.getLabelsAsString().hashCode(); return result; } @Override public String toString() { PropertyIterator it = this.propertyIterator(); StringBuilder sb = new StringBuilder(); sb.append("["); while(it.hasNext()) { JMeterProperty prop = it.next(); sb.append(prop.getName()).append(": ").append(prop.getStringValue()).append(", "); } sb.append("]"); return sb.toString(); } protected double[] parseBucketsFromString(String fullBucketString) { String[] bucketStrings = fullBucketString.split(","); List buckets = new ArrayList(); for(String bucket : bucketStrings) { try { double d = Double.parseDouble(bucket); buckets.add(d); }catch(Exception e) { log.warn("couldn't parse {} because of error {}:{}. It wont be included in buckets for the metric {}", bucket, e.getClass().toString(), e.getMessage(), this.getMetricName()); } } if(buckets.isEmpty()) { log.warn("Did not parse any buckets for metric {}. Returning defaults", this.getMetricName()); return DEFAULT_BUCKET_SIZES; }else { return buckets.stream().mapToDouble(Double::doubleValue).toArray(); } } /** * A very simple POJO for holding Quantiles and the error rating for them. * * @author Jeff ohrstrom * */ public static class QuantileDefinition { public double quantile; public double error; public static final String QUANTILE_ERROR_SEPERATOR = ","; public static final String QUANTILE_DEFINITION_SEPERATOR = "|"; public static final String QUANTILE_DEFINITION_SEPERATOR_REGEX = "\\|"; public static final String QUANTILE_LENGTH_SEPERATOR_REGEX = "\\;"; QuantileDefinition(double quantile, double error) { this.quantile = quantile; this.error = error; } QuantileDefinition(String quantile, String error) throws NumberFormatException { this(new String[]{quantile, error}); } QuantileDefinition(String[] definition) throws NumberFormatException { if(definition.length != 2) { throw new IllegalArgumentException(String.format("Quantiles need exactly 2 parameters. %d given.", definition.length)); } this.quantile = Double.parseDouble(definition[0]); this.error = Double.parseDouble(definition[1]); } @Override public String toString() { return new StringBuilder() .append(this.quantile) .append(QUANTILE_ERROR_SEPERATOR) .append(this.error) .toString(); } public static QuantileDefinition[] defaultQuantiles() { QuantileDefinition[] def = new QuantileDefinition[3]; def[0] = new QuantileDefinition(0.75,0.5); def[1] = new QuantileDefinition(0.95,0.1); def[2] = new QuantileDefinition(0.99,0.01); return def; } public static String arrayToString(QuantileDefinition[] definitions) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < definitions.length; i++) { sb.append(definitions[i]); if(i+1 < definitions.length) sb.append(QUANTILE_DEFINITION_SEPERATOR); } return sb.toString(); } public static QuantileDefinition[] parseQuantilesFromString(String fullQuantileString) { String quantileOnlyString = fullQuantileString.split(QUANTILE_LENGTH_SEPERATOR_REGEX)[0]; String[] quantileDefStrings = quantileOnlyString.split(QUANTILE_DEFINITION_SEPERATOR_REGEX); List quantiles = new ArrayList(); for(String quantile : quantileDefStrings) { try { QuantileDefinition q = new QuantileDefinition(quantile.split(QUANTILE_ERROR_SEPERATOR)); quantiles.add(q); }catch(Exception e) { log.warn("couldn't parse {} because of error {}:{}. It wont be included in quantiles for the metric", quantile, e.getClass().toString(), e.getMessage()); } } if(quantiles.isEmpty()) { log.warn("Did not parse any quantiles, returning defaults."); return DEFAULT_QUANTILES; }else { return quantiles.toArray(new QuantileDefinition[quantiles.size()]); } } public static long parseQuantilesWindowLengthFromString(String fullQuantileString) { String[] quantileDefStrings = fullQuantileString.split(QUANTILE_LENGTH_SEPERATOR_REGEX); if (quantileDefStrings.length < 2) { log.debug("Using default quantile window lenght of " + DEFAULT_QUANTILE_WINDOW_LENGHT + " seconds"); return DEFAULT_QUANTILE_WINDOW_LENGHT; } return Long.parseLong(quantileDefStrings[1]); } } } ================================================ FILE: src/main/java/com/github/johrstrom/collector/CollectorElement.java ================================================ package com.github.johrstrom.collector; import java.util.*; import java.util.Map; import java.util.Map.Entry; import org.apache.jmeter.testelement.AbstractTestElement; import org.apache.jmeter.testelement.property.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.prometheus.client.Collector; public abstract class CollectorElement extends AbstractTestElement { public static final String COLLECTOR_DEF = "prometheus.collector_definitions"; protected Map collectors = new HashMap(); protected transient JMeterCollectorRegistry registry = JMeterCollectorRegistry.getInstance(); private static Logger log = LoggerFactory.getLogger(CollectorElement.class); private static final long serialVersionUID = 963612021269632269L; public CollectorElement() { log.debug("making a new config element: " + this.toString()); this.setCollectorConfigs(new ArrayList()); } public CollectionProperty getCollectorConfigs() { JMeterProperty collectorDefinitions = this.getProperty(COLLECTOR_DEF); if (collectorDefinitions == null || collectorDefinitions instanceof NullProperty) { collectorDefinitions = new CollectionProperty(COLLECTOR_DEF, new ArrayList()); collectorDefinitions.setName(COLLECTOR_DEF); } return (CollectionProperty) collectorDefinitions; } public void setCollectorConfigs(List collectors) { log.debug("setting new collectors. size is: " + collectors.size()); this.setCollectorConfigs(new CollectionProperty(COLLECTOR_DEF, collectors)); } public void setCollectorConfigs(CollectionProperty collectors) { this.setProperty(collectors); } protected void clearCollectors() { Iterator> iter = this.collectors.entrySet().iterator(); while (iter.hasNext()) { Entry entry = iter.next(); this.registry.unregister(entry.getKey()); iter.remove(); } } protected void makeNewCollectors() { this.clearCollectors(); CollectionProperty collectorDefs = this.getCollectorConfigs(); PropertyIterator iter = collectorDefs.iterator(); while (iter.hasNext()) { try { @SuppressWarnings("unchecked") C config = (C) iter.next().getObjectValue(); Collector collector = registry.getOrCreateAndRegister(config); this.collectors.put(config, collector); log.debug("added " + config.getMetricName() + " to list of collectors"); } catch (Exception e) { log.error("Didn't create new collector because of error, ", e); } } } } ================================================ FILE: src/main/java/com/github/johrstrom/collector/JMeterCollectorRegistry.java ================================================ package com.github.johrstrom.collector; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; import io.prometheus.client.hotspot.*; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterContextService.ThreadCounts; import org.apache.jmeter.util.JMeterUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.ConcurrentHashMap; public class JMeterCollectorRegistry extends CollectorRegistry { private static JMeterCollectorRegistry instance = null; private static Logger log = LoggerFactory.getLogger(JMeterCollectorRegistry.class); private ConcurrentHashMap registered = new ConcurrentHashMap<>(); private static final boolean saveThreads = JMeterUtils.getPropDefault(ThreadCollector.COLLECT_THREADS, ThreadCollector.COLLECT_THREADS_DEFAULT); public static final String COLLECT_JVM = "prometheus.save.jvm"; public static final boolean COLLECT_JVM_DEFAULT = true; private static final boolean saveJVM = JMeterUtils.getPropDefault(COLLECT_JVM, COLLECT_JVM_DEFAULT); public synchronized static JMeterCollectorRegistry getInstance() { if (instance == null) { log.debug("Creating prometheus collector registry"); instance = new JMeterCollectorRegistry(); } return instance; } private JMeterCollectorRegistry() { super(true); this.initDefaultExports(); this.createJMeterExports(); } private void initDefaultExports() { if(saveJVM) { new StandardExports().register(this); new MemoryPoolsExports().register(this); new MemoryAllocationExports().register(this); new BufferPoolsExports().register(this); new GarbageCollectorExports().register(this); new ThreadExports().register(this); new ClassLoadingExports().register(this); new VersionInfoExports().register(this); } } private void createJMeterExports() { if(saveThreads) { ThreadCollector tc = new ThreadCollector(); this.register(tc); this.registered.put(ThreadCollector.getConfig(), tc); } } public synchronized void unregister(BaseCollectorConfig cfg) { log.debug("unregistering {}", cfg.getMetricName()); if(registered.containsKey(cfg)) { Collector collector = registered.get(cfg); try { this.unregister(collector); this.registered.remove(cfg); } catch(Exception e) { log.error("can't unregister collector because error: ", e); } } } public synchronized Collector getOrCreateAndRegister(BaseCollectorConfig cfg) { if(registered.containsKey(cfg)) { log.trace("{} found already registered.", cfg.getMetricName()); return registered.get(cfg); }else { Collector c = BaseCollectorConfig.fromConfig(cfg); this.register(c); //throws exception here if it fails to register this.registered.put(cfg, c); log.debug("created and registered {}", cfg); return c; } } @Override public synchronized void clear() { super.clear(); this.registered.clear(); } private static class ThreadCollector extends Collector { public static final String COLLECT_THREADS_NAME = "prometheus.save.threads.name"; public static final String COLLECT_THREADS_NAME_DEFAULT = "jmeter_threads"; public static final String COLLECT_THREADS = "prometheus.save.threads"; public static final boolean COLLECT_THREADS_DEFAULT = true; private final Gauge innerCollector; protected ThreadCollector() { BaseCollectorConfig cfg = getConfig(); innerCollector = Gauge.build() .name(cfg.getMetricName()) .labelNames(cfg.getLabels()) .help(cfg.getHelp()) .create(); } protected static BaseCollectorConfig getConfig() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setHelp("Gauge for jmeter threads"); cfg.setMetricName(threadMetricName()); cfg.setLabels(new String[] {"state"}); cfg.setType(Type.GAUGE.name()); return cfg; } public static String threadMetricName() { return JMeterUtils.getPropDefault(COLLECT_THREADS_NAME, COLLECT_THREADS_NAME_DEFAULT); } @Override public List collect() { ThreadCounts tc = JMeterContextService.getThreadCounts(); innerCollector.labels("active").set(tc.activeThreads); innerCollector.labels("finished").set(tc.finishedThreads); innerCollector.labels("started").set(tc.startedThreads); return innerCollector.collect(); } } } ================================================ FILE: src/main/java/com/github/johrstrom/collector/SuccessRatioCollector.java ================================================ package com.github.johrstrom.collector; import io.prometheus.client.Collector; import io.prometheus.client.Counter; import java.util.ArrayList; import java.util.List; public class SuccessRatioCollector extends Collector { private final Counter success, failure, total; public SuccessRatioCollector(BaseCollectorConfig config) { this.success = new Counter.Builder() .help(config.getHelp()) .name(extendedName(config.getMetricName(), "success")) .labelNames(config.getLabels()) .create(); this.failure = new Counter.Builder() .help(config.getHelp()) .name(extendedName(config.getMetricName(), "failure")) .labelNames(config.getLabels()) .create(); this.total = new Counter.Builder() .help(config.getHelp()) .name(extendedName(config.getMetricName(), "total")) .labelNames(config.getLabels()) .create(); } public void incrementSuccess(String[] labels) { this.success.labels(labels).inc(); this.total.labels(labels).inc(); // this ensures that we emit 0 when this set of labels has // never failed. i.e, total = success when failure = 0. if(this.getFailure(labels) < 1 ) { this.failure.labels(labels).inc(0); } } public void incrementFailure(String[] labels) { this.failure.labels(labels).inc(); this.total.labels(labels).inc(); // this ensures that we emit 0 when this set of labels has // never succeeded. i.e, total = failures when success = 0. if(this.getSuccess(labels) < 1) { this.success.labels(labels).inc(0); } } public double getSuccess(String[] labels) { return this.success.labels(labels).get(); } public double getFailure(String[] labels) { return this.failure.labels(labels).get(); } public double getTotal(String[] labels) { return this.total.labels(labels).get(); } @Override public List collect() { ArrayList metrics = new ArrayList(); metrics.addAll(this.success.collect()); metrics.addAll(this.failure.collect()); metrics.addAll(this.total.collect()); return metrics; } private static String extendedName(String orig, String append) { StringBuilder sb = new StringBuilder(32); sb.append(orig); if(!orig.endsWith("_")) { sb.append("_"); } sb.append(append); return sb.toString(); } } ================================================ FILE: src/main/java/com/github/johrstrom/collector/gui/AbstractCollectorTable.java ================================================ package com.github.johrstrom.collector.gui; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.CollectorElement; import org.apache.jmeter.gui.util.HorizontalPanel; import org.apache.jmeter.gui.util.VerticalPanel; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.PropertyIterator; import org.apache.jorphan.gui.ObjectTableModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public abstract class AbstractCollectorTable extends JPanel implements ActionListener { public static final String ADD = "Add"; public static final String DELETE = "Delete"; protected transient JTable table; protected transient ObjectTableModel model; protected JButton add,delete; private final Class clazzType; private static final long serialVersionUID = 2027712606129940455L; private Logger log = LoggerFactory.getLogger(AbstractCollectorTable.class); /** * @return */ public abstract Flatten getGuiHelper(); /** * */ public abstract void modifyColumns(); public AbstractCollectorTable(Class collectorType) { clazzType = collectorType; this.init(); this.modifyColumns(); } public List getRowsAsCollectors(){ ArrayList collectors = new ArrayList<>(); @SuppressWarnings("unchecked") Iterator iter = (Iterator) this.model.iterator(); while(iter.hasNext()) { C cfg = this.clazzType.cast(iter.next()); collectors.add(cfg); log.debug("populated config: " + cfg.toString() + " from table."); } return collectors; } public void clearModelData() { this.model.clearData(); } // public void modifyTestElement(CollectorElement ele) { // // if(!(ele instanceof CollectorElement)) { // return; // } // // int rows = this.model.getRowCount(); // ArrayList collectors = new ArrayList<>(); // // @SuppressWarnings("unchecked") // CollectorElement config = (CollectorElement) ele; // // log.debug("modifying test element " + ele.toString() + ". row count in model is " + rows); // // @SuppressWarnings("unchecked") // Iterator iter = (Iterator) model.iterator(); // // while(iter.hasNext()) { // C cfg = this.clazzType.cast(iter.next()); // collectors.add(cfg); // log.debug("populated config: " + cfg.toString() + " from table."); // } // // config.setCollectorConfigs(collectors); // this.setCollector(config); // } public void populateTable(CollectorElement config) { CollectionProperty collectors = config.getCollectorConfigs(); log.debug("Configuring table with " + collectors.size() + " collectors."); this.model.clearData(); PropertyIterator it = collectors.iterator(); while(it.hasNext()) { BaseCollectorConfig cfg = (BaseCollectorConfig) it.next().getObjectValue(); this.model.addRow(cfg); log.debug("added row into table: " + cfg.toString()); } } /** * Private helper function to initialize all the Swing components. */ protected void init() { this.setLayout(new BorderLayout(0, 5)); VerticalPanel panel = new VerticalPanel(); panel.add(makeTablePanel()); panel.add(makeButtonPanel()); this.add(panel, BorderLayout.CENTER); } protected Component makeTablePanel() { Flatten helper = this.getGuiHelper(); this.model = new ObjectTableModel( helper.getHeaders(), this.clazzType, helper.getReadFunctors(), helper.getWriteFunctors(), helper.getEditorClasses() ); this.table = new JTable(this.model); JScrollPane scrollPane = new JScrollPane(this.table); table.setFillsViewportHeight(true); return scrollPane; } protected JPanel makeButtonPanel() { add = new JButton(ADD); add.setActionCommand(ADD); add.setEnabled(true); add.addActionListener(this); delete = new JButton(DELETE); delete.setActionCommand(DELETE); delete.setEnabled(true); delete.addActionListener(this); HorizontalPanel panel = new HorizontalPanel(); panel.add(add); panel.add(delete); return panel; } @Override public void actionPerformed(ActionEvent event) { switch (event.getActionCommand()) { case ADD: try { this.model.addRow(this.clazzType.getDeclaredConstructor().newInstance()); } catch (Exception e) { log.error("Couldn't add to model. ", e); } break; case DELETE: deleteSelectedRows(); break; default: break; } } protected void deleteSelectedRows() { int[] rows = table.getSelectedRows(); for(int i = 0; i < rows.length; i++) { this.model.removeRow(rows[i]); } } } ================================================ FILE: src/main/java/com/github/johrstrom/collector/gui/Flatten.java ================================================ package com.github.johrstrom.collector.gui; import org.apache.jorphan.reflect.Functor; public interface Flatten { public Functor[] getReadFunctors(); public Functor[] getWriteFunctors(); public String[] getHeaders(); public Class[] getEditorClasses(); } ================================================ FILE: src/main/java/com/github/johrstrom/config/PrometheusMetricsConfig.java ================================================ package com.github.johrstrom.config; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.CollectorElement; import io.prometheus.client.Collector; import org.apache.jmeter.engine.util.NoThreadClone; import org.apache.jmeter.testelement.TestStateListener; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.JMeterProperty; import org.apache.jmeter.threads.JMeterVariables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map.Entry; public class PrometheusMetricsConfig extends CollectorElement implements NoThreadClone, TestStateListener { private static final long serialVersionUID = 7602510312126862226L; private Logger log = LoggerFactory.getLogger(PrometheusMetricsConfig.class); @Override public void testEnded() { this.setRunningVersion(false); JMeterVariables variables = getThreadContext().getVariables(); for (Entry entry : this.collectors.entrySet()) { BaseCollectorConfig cfg = entry.getKey(); variables.remove(cfg.getMetricName()); } this.clearCollectors(); } @Override public void testEnded(String arg0) { this.testEnded(); } @Override public void testStarted() { this.setRunningVersion(true); this.makeNewCollectors(); JMeterVariables variables = getThreadContext().getVariables(); log.debug("Test started, adding {} collectors to variables", this.collectors.size()); for (Entry entry : this.collectors.entrySet()) { BaseCollectorConfig cfg = entry.getKey(); variables.putObject(cfg.getMetricName(), entry.getValue()); log.debug("Added ({},{}) to variables.", entry.getKey(), entry.getValue().toString()); } } @Override public void testStarted(String arg0) { this.testStarted(); } @Override public PrometheusMetricsConfig clone() { PrometheusMetricsConfig clone = new PrometheusMetricsConfig(); clone.setCollectorConfigs(this.getCollectorConfigs()); return clone; } @Override public boolean equals(Object o) { if (o instanceof PrometheusMetricsConfig) { PrometheusMetricsConfig other = (PrometheusMetricsConfig) o; CollectionProperty thisConfig = this.getCollectorConfigs(); CollectionProperty otherConfig = other.getCollectorConfigs(); boolean sameSize = thisConfig.size() == otherConfig.size(); for (int i = 0; i < thisConfig.size(); i++) { JMeterProperty left = thisConfig.get(i); JMeterProperty right = otherConfig.get(i); if(!left.equals(right)) { return false; } } return true && sameSize; } return false; } } ================================================ FILE: src/main/java/com/github/johrstrom/config/gui/ConfigCollectorTable.java ================================================ package com.github.johrstrom.config.gui; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.BaseCollectorConfig.JMeterCollectorType; import com.github.johrstrom.collector.gui.AbstractCollectorTable; import com.github.johrstrom.collector.gui.Flatten; import org.apache.jorphan.reflect.Functor; import javax.swing.*; import javax.swing.table.TableColumn; public class ConfigCollectorTable extends AbstractCollectorTable implements Flatten { public static JComboBox typeComboBox; public static int METRIC_NAME_INDEX = 0; public static int HELP_INDEX = 1; public static int LABEL_NAME_INDEX = 2; public static int TYPE_INDEX = 3; public static int QUANTILE_OR_BUCKET_INDEX = 4; public static int BASE_COLUMN_SIZE = 5; private static final long serialVersionUID = 8675797078488652676L; static { typeComboBox = new JComboBox<>(); typeComboBox.addItem(JMeterCollectorType.COUNTER.toString()); typeComboBox.addItem(JMeterCollectorType.SUMMARY.toString()); typeComboBox.addItem(JMeterCollectorType.HISTOGRAM.toString()); typeComboBox.addItem(JMeterCollectorType.GAUGE.toString()); typeComboBox.addItem(JMeterCollectorType.SUCCESS_RATIO.toString()); } public ConfigCollectorTable() { super(BaseCollectorConfig.class); } @Override public Flatten getGuiHelper() { return this; } @Override public void modifyColumns() { TableColumn column = this.table.getColumnModel().getColumn(TYPE_INDEX); column.setCellEditor(new DefaultCellEditor(typeComboBox)); } @Override public Functor[] getReadFunctors() { Functor[] functors = new Functor[BASE_COLUMN_SIZE]; functors[METRIC_NAME_INDEX] = new Functor("getMetricName"); functors[HELP_INDEX] = new Functor("getHelp"); functors[LABEL_NAME_INDEX] = new Functor("getLabelsAsString"); functors[TYPE_INDEX] = new Functor("getType"); functors[QUANTILE_OR_BUCKET_INDEX] = new Functor("getQuantileOrBucket"); return functors; } @Override public Functor[] getWriteFunctors() { Functor[] functors = new Functor[BASE_COLUMN_SIZE]; functors[METRIC_NAME_INDEX] = new Functor("setMetricName"); functors[HELP_INDEX] = new Functor("setHelp"); functors[LABEL_NAME_INDEX] = new Functor("setLabels"); functors[TYPE_INDEX] = new Functor("setType"); functors[QUANTILE_OR_BUCKET_INDEX] = new Functor("setQuantileOrBucket"); return functors; } @Override public String[] getHeaders() { String[] headers = new String[BASE_COLUMN_SIZE]; headers[METRIC_NAME_INDEX] = "Name"; headers[HELP_INDEX] = "Help"; headers[LABEL_NAME_INDEX] = "Labels"; headers[TYPE_INDEX] = "Type"; headers[QUANTILE_OR_BUCKET_INDEX] = "Buckets or Quantiles"; return headers; } @Override public Class[] getEditorClasses() { Class[] clazzes = new Class[BASE_COLUMN_SIZE]; clazzes[METRIC_NAME_INDEX] = String.class; clazzes[HELP_INDEX] = String.class; clazzes[LABEL_NAME_INDEX] = String.class; clazzes[TYPE_INDEX] = ComboBoxEditor.class; clazzes[QUANTILE_OR_BUCKET_INDEX] = String.class; return clazzes; } } ================================================ FILE: src/main/java/com/github/johrstrom/config/gui/PrometheusMetricsConfigGui.java ================================================ package com.github.johrstrom.config.gui; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.CollectorElement; import com.github.johrstrom.config.PrometheusMetricsConfig; import org.apache.jmeter.config.gui.AbstractConfigGui; import org.apache.jmeter.testelement.TestElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.*; import java.util.List; public class PrometheusMetricsConfigGui extends AbstractConfigGui { private static final long serialVersionUID = 6741986237897976082L; // private PrometheusMetricsConfig config; private ConfigCollectorTable table = new ConfigCollectorTable(); private Logger log = LoggerFactory.getLogger(PrometheusMetricsConfigGui.class); public PrometheusMetricsConfigGui(){ super(); log.debug("making a new config gui: {}", this.toString()); init(); } @Override public TestElement createTestElement() { PrometheusMetricsConfig cfg = new PrometheusMetricsConfig(); cfg.setProperty(TestElement.GUI_CLASS, PrometheusMetricsConfigGui.class.getName()); cfg.setProperty(TestElement.TEST_CLASS, PrometheusMetricsConfig.class.getName()); this.modifyTestElement(cfg); return cfg; } @Override public String getLabelResource() { return getClass().getCanonicalName(); } /* * (non-Javadoc) * * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#getStaticLabel() */ @Override public String getStaticLabel() { return "Prometheus Metrics"; } /* * (non-Javadoc) * * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#getName() */ @Override public String getName() { return super.getName() == null ? this.getStaticLabel() : super.getName(); } @SuppressWarnings("unchecked") @Override public void modifyTestElement(TestElement ele) { if(!(ele instanceof CollectorElement)) { return; } CollectorElement config = (CollectorElement) ele; List collectors = this.table.getRowsAsCollectors(); config.setCollectorConfigs(collectors); config.setName(this.getName()); config.setComment(this.getComment()); } private void init() { this.setLayout(new BorderLayout(0, 5)); this.add(makeTitlePanel(), BorderLayout.NORTH); this.add(this.table, BorderLayout.CENTER); } @SuppressWarnings("unchecked") @Override public void configure(TestElement ele) { super.configure(ele); if(ele instanceof CollectorElement) { try { this.table.populateTable((CollectorElement) ele); } catch(Exception e) { log.error("didn't modify test element because {}:{}", e.getClass(), e.getMessage()); } } this.setName(ele.getName()); this.setComment(ele.getComment()); } @Override public void clearGui() { super.clearGui(); this.table.clearModelData(); } @Override protected PrometheusMetricsConfigGui clone() { return new PrometheusMetricsConfigGui(); } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/ListenerCollectorConfig.java ================================================ package com.github.johrstrom.listener; import com.github.johrstrom.collector.BaseCollectorConfig; public class ListenerCollectorConfig extends BaseCollectorConfig { private static final long serialVersionUID = -8968099072667146399L; public static final String LISTEN_TO = "listener.collector.listen_to"; public static final String MEASURING = "listener.collector.measuring"; public static final String SAMPLES = "samples"; public static final String ASSERTIONS = "assertions"; public ListenerCollectorConfig() { this(new BaseCollectorConfig()); } public ListenerCollectorConfig(BaseCollectorConfig base) { this.setMetricName(base.getMetricName()); this.setType(base.getType()); this.setHelp(base.getHelp()); this.setLabels(base.getLabels()); } public enum Measurable { // for aggregated type updater ResponseTime, ResponseSize, Latency, IdleTime, ConnectTime, // for count type updater SuccessTotal, FailureTotal, CountTotal, SuccessRatio; } public void setListenTo(String listenTo) { if(listenTo.equalsIgnoreCase(SAMPLES)) { this.setProperty(LISTEN_TO, SAMPLES); }else if(listenTo.equalsIgnoreCase(ASSERTIONS)) { this.setProperty(LISTEN_TO, ASSERTIONS); } else { this.setProperty(LISTEN_TO, SAMPLES); } } public String getListenTo() { return this.getPropertyAsString(LISTEN_TO, SAMPLES); } public void setMeasuring(String measuring) { this.setProperty(MEASURING, measuring); } public String getMeasuring() { return this.getPropertyAsString(MEASURING, Measurable.ResponseTime.toString()); } public Measurable getMeasuringAsEnum() { return Measurable.valueOf(this.getMeasuring()); } public boolean listenToSamples() { return this.getListenTo().equalsIgnoreCase(SAMPLES); } public boolean listenToAssertions() { return this.getListenTo().equalsIgnoreCase(ASSERTIONS); } // @Override // public boolean equals(Object o) { // if(o instanceof ListenerCollectorConfig) { // boolean base = super.equals(o); // ListenerCollectorConfig other = (ListenerCollectorConfig) o; // // boolean measuring = this.getMeasuring().equals(other.getMeasuring()); // boolean listenTo = this.getListenTo().equals(other.getListenTo()); // // return base && measuring && listenTo; // } // // return false; // } // // @Override // public int hashCode() { // // final int prime = 31; // int result = 1; // result = prime * result + this.getName().hashCode(); // result = prime * result + this.getHelp().hashCode(); // result = prime * result + this.getType().hashCode(); // result = prime * result + this.getQuantileOrBucket().hashCode(); // result = prime * result + this.getLabelsAsString().hashCode(); // result = prime * result + this.getListenTo().hashCode(); // result = prime * result + this.getMeasuring().hashCode(); // // return result; // } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/PrometheusListener.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.johrstrom.listener; import com.github.johrstrom.collector.CollectorElement; import com.github.johrstrom.collector.JMeterCollectorRegistry; import com.github.johrstrom.listener.updater.AbstractUpdater; import com.github.johrstrom.listener.updater.AggregatedTypeUpdater; import com.github.johrstrom.listener.updater.CountTypeUpdater; import io.prometheus.client.Collector; import org.apache.jmeter.engine.util.NoThreadClone; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.samplers.SampleListener; import org.apache.jmeter.testelement.TestStateListener; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.JMeterProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * The main test element listener class of this library. Jmeter updates this * class through the SampleListener interface and it in turn updates the * CollectorRegistry. This class is also a TestStateListener to control when it * starts up or shuts down the server that ultimately serves Prometheus the * results through an http api. * * @author Jeff Ohrstrom */ public class PrometheusListener extends CollectorElement implements SampleListener, Serializable, TestStateListener, NoThreadClone { private static final long serialVersionUID = -4833646252357876746L; private static final Logger log = LoggerFactory.getLogger(PrometheusListener.class); private transient PrometheusServer server = PrometheusServer.getInstance(); private List updaters; /* * (non-Javadoc) * * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache. * jmeter.samplers.SampleEvent) */ @Override public void sampleOccurred(SampleEvent event) { for (AbstractUpdater updater : this.updaters) { updater.update(event); } } /* * (non-Javadoc) * * @see * org.apache.jmeter.samplers.SampleListener#sampleStarted(org.apache.jmeter * .samplers.SampleEvent) */ @Override public void sampleStarted(SampleEvent arg0) { // do nothing } /* * (non-Javadoc) * * @see * org.apache.jmeter.samplers.SampleListener#sampleStopped(org.apache.jmeter * .samplers.SampleEvent) */ @Override public void sampleStopped(SampleEvent arg0) { // do nothing } /* * (non-Javadoc) * * @see org.apache.jmeter.testelement.TestStateListener#testEnded() */ @Override public void testEnded() { try { this.server.stop(); } catch (Exception e) { log.error("Couldn't stop http server", e); } this.clearCollectors(); } /* * (non-Javadoc) * * @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang. * String) */ @Override public void testEnded(String arg0) { this.testEnded(); } /* * (non-Javadoc) * * @see org.apache.jmeter.testelement.TestStateListener#testStarted() */ @Override public void testStarted() { // update the configuration this.makeNewCollectors(); // this.registerAllCollectors(); try { if (server == null) { log.warn("Prometheus server has not yet been initialized, doing it now"); server = PrometheusServer.getInstance(); } server.start(); } catch (Exception e) { log.error("Couldn't start http server", e); } } /* * (non-Javadoc) * * @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang. * String) */ @Override public void testStarted(String arg0) { this.testStarted(); } @Override protected void makeNewCollectors() { // this.clearCollectors(); if (this.registry == null) { log.warn("Collector registry has not yet been initialized, doing it now"); registry = JMeterCollectorRegistry.getInstance(); } this.updaters = new ArrayList(); CollectionProperty collectorDefs = this.getCollectorConfigs(); for (JMeterProperty collectorDef : collectorDefs) { try { ListenerCollectorConfig config = (ListenerCollectorConfig) collectorDef.getObjectValue(); log.debug("Creating collector from configuration: " + config); Collector collector = this.registry.getOrCreateAndRegister(config); AbstractUpdater updater = null; switch (config.getMeasuringAsEnum()) { case CountTotal: case FailureTotal: case SuccessTotal: case SuccessRatio: updater = new CountTypeUpdater(config); break; case ResponseSize: case ResponseTime: case Latency: case IdleTime: case ConnectTime: updater = new AggregatedTypeUpdater(config); break; default: // hope our IDEs are telling us to use all possible enums! log.error(config.getMeasuringAsEnum() + " triggered default case, which means there's " + "no functionality for this and is likely a bug"); break; } this.collectors.put(config, collector); this.updaters.add(updater); log.debug("added " + config.getMetricName() + " to list of collectors"); } catch (Exception e) { log.error("Didn't create new collector because of error, ", e); } } } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/PrometheusServer.java ================================================ package com.github.johrstrom.listener; import com.github.johrstrom.collector.JMeterCollectorRegistry; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.common.TextFormat; import org.apache.jmeter.util.JMeterUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URLDecoder; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.zip.GZIPOutputStream; import java.util.concurrent.TimeUnit; /** * Expose Prometheus metrics using a plain Java HttpServer. *

* Example Usage: *

 * {@code
 * HTTPServer server = new HTTPServer(1234);
 * }
 * 
* */ public class PrometheusServer { public static final String PROMETHEUS_PORT = "prometheus.port"; public static final int PROMETHEUS_PORT_DEFAULT = 9270; public static final String PROMETHEUS_DELAY = "prometheus.delay"; public static final int PROMETHEUS_DELAY_DEFAULT = 0; public static final String PROMETHEUS_IP = "prometheus.ip"; public static final String PROMETHEUS_IP_DEFAULT = "127.0.0.1"; private static final Logger log = LoggerFactory.getLogger(PrometheusServer.class); private static class LocalByteArray extends ThreadLocal { protected ByteArrayOutputStream initialValue() { return new ByteArrayOutputStream(1 << 20); } } static class HTTPMetricHandler implements HttpHandler { private CollectorRegistry registry; private final LocalByteArray response = new LocalByteArray(); HTTPMetricHandler(CollectorRegistry registry) { this.registry = registry; } public void handle(HttpExchange t) throws IOException { String query = t.getRequestURI().getRawQuery(); ByteArrayOutputStream response = this.response.get(); response.reset(); OutputStreamWriter osw = new OutputStreamWriter(response); TextFormat.write004(osw, registry.filteredMetricFamilySamples(parseQuery(query))); osw.flush(); osw.close(); response.flush(); response.close(); t.getResponseHeaders().set("Content-Type", TextFormat.CONTENT_TYPE_004); if (shouldUseCompression(t)) { t.getResponseHeaders().set("Content-Encoding", "gzip"); t.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0); final GZIPOutputStream os = new GZIPOutputStream(t.getResponseBody()); response.writeTo(os); os.close(); } else { t.getResponseHeaders().set("Content-Length", String.valueOf(response.size())); t.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.size()); response.writeTo(t.getResponseBody()); } t.close(); } } protected static boolean shouldUseCompression(HttpExchange exchange) { List encodingHeaders = exchange.getRequestHeaders().get("Accept-Encoding"); if (encodingHeaders == null) return false; for (String encodingHeader : encodingHeaders) { String[] encodings = encodingHeader.split(","); for (String encoding : encodings) { if (encoding.trim().toLowerCase().equals("gzip")) { return true; } } } return false; } protected static Set parseQuery(String query) throws IOException { Set names = new HashSet(); if (query != null) { String[] pairs = query.split("&"); for (String pair : pairs) { int idx = pair.indexOf("="); if (idx != -1 && URLDecoder.decode(pair.substring(0, idx), "UTF-8").equals("name[]")) { names.add(URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } } } return names; } private HttpServer server; private static PrometheusServer instance = null; private int port = JMeterUtils.getPropDefault(PROMETHEUS_PORT, PROMETHEUS_PORT_DEFAULT); private int delay = JMeterUtils.getPropDefault(PROMETHEUS_DELAY, PROMETHEUS_DELAY_DEFAULT); private String ip = JMeterUtils.getPropDefault(PROMETHEUS_IP, PROMETHEUS_IP_DEFAULT); protected static final HTTPMetricHandler metricHandler = new HTTPMetricHandler(JMeterCollectorRegistry.getInstance()); public synchronized static PrometheusServer getInstance() { if(instance == null) { log.debug("Creating Prometheus Server"); instance = new PrometheusServer(); } return instance; } private PrometheusServer() { } public synchronized void start() throws IOException { if(server != null){ server.stop(0); ((ExecutorService) this.server.getExecutor()).shutdown(); } server = HttpServer.create(); InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName(ip), port); server.bind(addr, 3); server.createContext("/", metricHandler); server.createContext("/metrics", metricHandler); server.setExecutor(Executors.newSingleThreadExecutor()); server.start(); } public synchronized void stop() throws InterruptedException { TimeUnit.SECONDS.sleep(delay); server.stop(0); ((ExecutorService) this.server.getExecutor()).shutdown(); } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/gui/ListenerCollectorTable.java ================================================ package com.github.johrstrom.listener.gui; import com.github.johrstrom.collector.gui.AbstractCollectorTable; import com.github.johrstrom.collector.gui.Flatten; import com.github.johrstrom.config.gui.ConfigCollectorTable; import com.github.johrstrom.listener.ListenerCollectorConfig; import com.github.johrstrom.listener.ListenerCollectorConfig.Measurable; import org.apache.jorphan.reflect.Functor; import javax.swing.*; import javax.swing.table.TableColumn; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ListenerCollectorTable extends AbstractCollectorTable implements Flatten { private static final long serialVersionUID = 4429063284832140575L; public static JComboBox listenToComboBox, measuringComboBox; private static ConfigCollectorTable stealFrom = new ConfigCollectorTable(); public static int LISTEN_TO_INDEX = ConfigCollectorTable.BASE_COLUMN_SIZE; public static int MEASURING_INDEX = LISTEN_TO_INDEX + 1; static { listenToComboBox = new JComboBox<>(); listenToComboBox.addItem(ListenerCollectorConfig.SAMPLES); listenToComboBox.addItem(ListenerCollectorConfig.ASSERTIONS); measuringComboBox = measuringBox(); } public ListenerCollectorTable() { super(ListenerCollectorConfig.class); } @Override public Flatten getGuiHelper() { return this; } @Override public void modifyColumns() { TableColumn column = table.getColumnModel().getColumn(ConfigCollectorTable.TYPE_INDEX); column.setCellEditor(new DefaultCellEditor(ConfigCollectorTable.typeComboBox)); column = table.getColumnModel().getColumn(ListenerCollectorTable.LISTEN_TO_INDEX); column.setCellEditor(new DefaultCellEditor(listenToComboBox)); column = table.getColumnModel().getColumn(ListenerCollectorTable.MEASURING_INDEX); column.setCellEditor(new DefaultCellEditor(measuringComboBox)); } @Override public Functor[] getReadFunctors() { List functors = new ArrayList<>(Arrays.asList(stealFrom.getReadFunctors())); functors.add(new Functor("getListenTo")); functors.add(new Functor("getMeasuring")); return functors.toArray(new Functor[functors.size()]); } @Override public Functor[] getWriteFunctors() { List functors = new ArrayList<>(Arrays.asList(stealFrom.getWriteFunctors())); functors.add(new Functor("setListenTo")); functors.add(new Functor("setMeasuring")); return functors.toArray(new Functor[functors.size()]); } @Override public String[] getHeaders() { List headers = new ArrayList<>(Arrays.asList(stealFrom.getHeaders())); headers.add("Listen to"); headers.add("Measuring"); return headers.toArray(new String[headers.size()]); } @Override public Class[] getEditorClasses() { List> editors = new ArrayList<>(Arrays.asList(stealFrom.getEditorClasses())); editors.add(ComboBoxEditor.class); editors.add(ComboBoxEditor.class); return editors.toArray(new Class[editors.size()]); } public static JComboBox measuringBox() { JComboBox box = new JComboBox(); for (Measurable value : Measurable.values()) { box.addItem(value.toString()); } return box; } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/gui/PrometheusListenerGui.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.johrstrom.listener.gui; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.CollectorElement; import com.github.johrstrom.listener.ListenerCollectorConfig; import com.github.johrstrom.listener.PrometheusListener; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.visualizers.gui.AbstractListenerGui; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.*; import java.util.ArrayList; import java.util.List; /** * The GUI class for the Prometheus Listener. *

* Currently, all configurations are done through properties files so this class * shows nothing visually other than comments. * * @author Jeff Ohrstrom */ public class PrometheusListenerGui extends AbstractListenerGui { private static final long serialVersionUID = 4984653136457108054L; private ListenerCollectorTable table = new ListenerCollectorTable(); private Logger log = LoggerFactory.getLogger(PrometheusListenerGui.class); public PrometheusListenerGui() { super(); log.debug("making a new listener gui: {}", this.toString()); init(); } /* * (non-Javadoc) * * @see org.apache.jmeter.gui.JMeterGUIComponent#getLabelResource() */ @Override public String getLabelResource() { return getClass().getCanonicalName(); } @Override protected PrometheusListenerGui clone() throws CloneNotSupportedException { return new PrometheusListenerGui(); } /* * (non-Javadoc) * * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#getStaticLabel() */ @Override public String getStaticLabel() { return "Prometheus Listener"; } /* * (non-Javadoc) * * @see org.apache.jmeter.gui.AbstractJMeterGuiComponent#getName() */ @Override public String getName() { return super.getName() == null ? this.getStaticLabel() : super.getName(); } @SuppressWarnings("unchecked") @Override public void configure(TestElement ele) { super.configure(ele); if (ele instanceof CollectorElement) { try { this.table.populateTable((CollectorElement) ele); } catch (Exception e) { log.error("didn't modify test element because {}. {}", e.getClass(), e.getMessage()); } } //ele.getName() == null ? this.setName(ele.getName()) : this.setName(getStaticLabel());; this.setName(ele.getName() == null ? getStaticLabel() : ele.getName()); this.setComment(ele.getComment() == null ? "" : ele.getComment()); } @Override public TestElement createTestElement() { PrometheusListener listener = new PrometheusListener(); listener.setProperty(TestElement.GUI_CLASS, PrometheusListenerGui.class.getName()); listener.setProperty(TestElement.TEST_CLASS, PrometheusListener.class.getName()); this.modifyTestElement(listener); listener.setCollectorConfigs(defaultCollectors()); return listener; } private void init() { this.setLayout(new BorderLayout(0, 5)); this.add(makeTitlePanel(), BorderLayout.NORTH); this.add(this.table, BorderLayout.CENTER); } @Override public void modifyTestElement(TestElement ele) { if (!(ele instanceof CollectorElement)) { return; } @SuppressWarnings("unchecked") CollectorElement config = (CollectorElement) ele; List collectors = this.table.getRowsAsCollectors(); config.setCollectorConfigs(collectors); config.setName(this.getName()); config.setComment(this.getComment()); } @Override public void clearGui() { super.clearGui(); this.table.clearModelData(); } private List defaultCollectors() { List collectors = new ArrayList<>(); collectors.add(buildResponseTimeCollector()); collectors.add(buildSuccessRatioCollector()); return collectors; } private ListenerCollectorConfig buildSuccessRatioCollector() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setMetricName("Ratio"); cfg.setHelp("Success and failure ratio"); cfg.setLabels("label,code"); cfg.setType(BaseCollectorConfig.JMeterCollectorType.SUCCESS_RATIO.toString()); ListenerCollectorConfig listenerCfg = new ListenerCollectorConfig(cfg); listenerCfg.setListenTo(ListenerCollectorConfig.SAMPLES.toString()); listenerCfg.setMeasuring(ListenerCollectorConfig.Measurable.SuccessRatio.toString()); return listenerCfg; } private ListenerCollectorConfig buildResponseTimeCollector() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setMetricName("ResponseTime"); cfg.setHelp("Sampler Response Time"); cfg.setLabels("label,code"); cfg.setType(BaseCollectorConfig.JMeterCollectorType.SUMMARY.toString()); cfg.setQuantileOrBucket("0.75,0.5|0.95,0.1|0.99,0.01;60"); ListenerCollectorConfig listenerCfg = new ListenerCollectorConfig(cfg); listenerCfg.setListenTo(ListenerCollectorConfig.SAMPLES.toString()); listenerCfg.setMeasuring(ListenerCollectorConfig.Measurable.ResponseTime.toString()); return listenerCfg; } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/updater/AbstractUpdater.java ================================================ package com.github.johrstrom.listener.updater; import com.github.johrstrom.collector.JMeterCollectorRegistry; import com.github.johrstrom.listener.ListenerCollectorConfig; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterVariables; import java.util.HashMap; import java.util.Map; /** * The Updater family of classes are meant to update the actual Collectors given the configuration. The main problem * it tries to solve is tying a Prometheus Collector (with a type like 'Historgram', labels, etc.) to the JMeter data * that collector is measuring. * * Note: This class assumes that the Collector object passed into the constructor is valid. I.e., it is not null and * registered. Of course, being null has much more serious consequences. * * @author Jeff Ohrstrom * */ public abstract class AbstractUpdater { public static String NULL = "null"; protected ListenerCollectorConfig config; protected static final JMeterCollectorRegistry registry = JMeterCollectorRegistry.getInstance(); // helper lookup table for sample variables, so we don't loop over arrays every update. private Map varIndexLookup; /** * All subclasses should have this and only this constructor signature. * * @param cfg the configuration of the collector */ public AbstractUpdater(ListenerCollectorConfig cfg) { //this.collector = c; this.config = cfg; this.buildVarLookup(); } /** * Updates the collector it was instantiated with with the given event e. * * @param e */ public abstract void update(SampleEvent e); public static class AssertionContext { public AssertionResult assertion; public SampleEvent event; public AssertionContext(AssertionResult a, SampleEvent e) { this.assertion = a; this.event = e; } } /** * Helper function to extract the label values from the Sample Event. Values * depend on how the Updater was configured. * * @param event * @return the label values. */ protected String[] labelValues(SampleEvent event) { String[] labels = config.getLabels(); String[] values = new String[labels.length]; JMeterVariables vars = JMeterContextService.getContext().getVariables(); for (int i = 0; i < labels.length; i++) { String name = labels[i]; String value = null; // reserved keywords for the sampler's label name. if (name.equalsIgnoreCase("label")) { value = event.getResult().getSampleLabel(); } else if (name.equalsIgnoreCase("code")) { // code also reserved value = event.getResult().getResponseCode(); } else if (name.equalsIgnoreCase("thread_group")) { value = event.getThreadGroup(); // try to find it as a plain'ol variable. } else if (this.varIndexLookup.get(name) != null) { int idx = this.varIndexLookup.get(name); value = event.getVarValue(idx); // lastly look in sample_variables } else if (vars != null) { value = vars.get(name); } values[i] = (value == null || value.isEmpty()) ? NULL : value; } return values; } protected String[] labelValues(AssertionContext ctx) { String[] labels = config.getLabels(); String[] values = new String[labels.length]; JMeterVariables vars = JMeterContextService.getContext().getVariables(); for(int i = 0; i < labels.length; i++) { String name = labels[i]; String value = null; if(name.equalsIgnoreCase("label")) { value = ctx.assertion.getName(); // try to find it as a plain'ol variable. } else if (this.varIndexLookup.get(name) != null){ int idx = this.varIndexLookup.get(name); value = ctx.event.getVarValue(idx); // lastly look in sample_variables }else if (vars != null){ value = vars.get(name); } values[i] = (value == null || value.isEmpty()) ? NULL : value; } return values; } private void buildVarLookup() { this.varIndexLookup = new HashMap(); for(int i = 0; i < SampleEvent.getVarCount(); i++) { String name = SampleEvent.getVarName(i); if(inLabels(name)) { this.varIndexLookup.put(name, i); } } } private boolean inLabels(String searchFor) { String[] labels = config.getLabels(); for(int i = 0; i < labels.length; i++) { if(labels[i].equalsIgnoreCase(searchFor)) { return true; } } return false; } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/updater/AggregatedTypeUpdater.java ================================================ package com.github.johrstrom.listener.updater; import com.github.johrstrom.listener.ListenerCollectorConfig; import io.prometheus.client.Collector; import io.prometheus.client.Histogram; import io.prometheus.client.Summary; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.samplers.SampleResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AggregatedTypeUpdater extends AbstractUpdater { private static final Logger log = LoggerFactory.getLogger(AggregatedTypeUpdater.class); public AggregatedTypeUpdater(ListenerCollectorConfig cfg) { super(cfg); } @Override public void update(SampleEvent event) { try { Collector collector = registry.getOrCreateAndRegister(this.config); String[] labels = this.labelValues(event); long measurement = this.measure(event); if(collector instanceof Histogram) { Histogram hist = (Histogram) collector; hist.labels(labels).observe(measurement); }else if(collector instanceof Summary) { Summary sum = (Summary) collector; sum.labels(labels).observe(measurement); } } catch(Exception e) { log.error("Did not update {} because of error: {}", this.config.getMetricName(), e.getMessage()); log.debug(e.getMessage(), e); } } protected long measure(SampleEvent event) { SampleResult result = event.getResult(); switch(this.config.getMeasuringAsEnum()) { case ResponseSize: return result.getBodySizeAsLong(); case ResponseTime: return result.getTime(); case Latency: return result.getLatency(); case IdleTime: return result.getIdleTime(); case ConnectTime: return result.getConnectTime(); default: return 0; } } } ================================================ FILE: src/main/java/com/github/johrstrom/listener/updater/CountTypeUpdater.java ================================================ package com.github.johrstrom.listener.updater; import com.github.johrstrom.collector.SuccessRatioCollector; import com.github.johrstrom.listener.ListenerCollectorConfig; import io.prometheus.client.Collector; import io.prometheus.client.Counter; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is the AbstractUpdater sub-type that can handle updating any kind of Counter metrics * along with {@link SuccessRatioCollector} type. * * @author Jeff ohrstrom * */ public class CountTypeUpdater extends AbstractUpdater { private static final Logger log = LoggerFactory.getLogger(CountTypeUpdater.class); public CountTypeUpdater(ListenerCollectorConfig cfg) { super(cfg); } @Override public void update(SampleEvent event) { if(this.config.listenToSamples()) { boolean successful = event.getResult().isSuccessful(); this.inc(this.labelValues(event), successful); } else if(this.config.listenToAssertions()) { for(AssertionResult assertion : event.getResult().getAssertionResults()) { updateAssertions(new AssertionContext(assertion, event)); } } } protected void inc(String[] labels, boolean successful) { try { Collector collector = registry.getOrCreateAndRegister(this.config); if(collector instanceof Counter) { Counter c = (Counter) collector; switch (config.getMeasuringAsEnum()) { case CountTotal: c.labels(labels).inc(); break; case FailureTotal: if(!successful) { c.labels(labels).inc(); } break; case SuccessTotal: if(successful) { c.labels(labels).inc(); } break; default: break; } } else if(collector instanceof SuccessRatioCollector) { SuccessRatioCollector c = (SuccessRatioCollector) collector; if(successful) { c.incrementSuccess(labels); } else { c.incrementFailure(labels); } } } catch (Exception e) { log.error("Did not update {} because of error: {}", this.config.getMetricName(), e.getMessage()); log.debug(e.getMessage(), e); } } protected void updateAssertions(AssertionContext ctx) { String[] labels = this.labelValues(ctx); boolean successful = !ctx.assertion.isFailure(); this.inc(labels, successful); } } ================================================ FILE: src/test/java/com/github/johrstrom/collector/BaseCollectorConfigTest.java ================================================ package com.github.johrstrom.collector; import com.github.johrstrom.collector.BaseCollectorConfig.JMeterCollectorType; import com.github.johrstrom.collector.BaseCollectorConfig.QuantileDefinition; import com.github.johrstrom.test.TestUtilities; import io.prometheus.client.*; import org.junit.Assert; import org.junit.Test; import java.util.regex.Pattern; //import io.prometheus.client.Collector.Type; public class BaseCollectorConfigTest { @Test public void emptyLabelsOK() { BaseCollectorConfig cfg = TestUtilities.simpleCounterCfg(); // first try with a brand new String array cfg.setLabels(new String[]{}); Collector collector = BaseCollectorConfig.fromConfig(cfg); Assert.assertNotNull(collector); // Now try with a String array with an empty string in it cfg.setLabels(new String[]{""}); collector = BaseCollectorConfig.fromConfig(cfg); Assert.assertNotNull(collector); // Now just for kicks, try with several empty strings in it cfg.setLabels(new String[]{"a", "", "b", ""}); collector = BaseCollectorConfig.fromConfig(cfg); Assert.assertNotNull(collector); } @Test public void parseSingleQuantilesCorrectly() { BaseCollectorConfig cfg = TestUtilities.simpleSummaryCfg(); cfg.setQuantileOrBucket("0.95,0.1"); QuantileDefinition[] quantiles = cfg.getQuantiles(); Assert.assertEquals(1, quantiles.length); Assert.assertEquals(0.95, quantiles[0].quantile,0.001); Assert.assertEquals(0.1, quantiles[0].error,0.001); } @Test public void parseMultipleQuantilesCorrectly() { BaseCollectorConfig cfg = TestUtilities.simpleSummaryCfg(); cfg.setQuantileOrBucket("0.95,0.1|0.99,0.1|0.999,0.1"); QuantileDefinition[] quantiles = cfg.getQuantiles(); Assert.assertEquals(3, quantiles.length); Assert.assertEquals(0.95, quantiles[0].quantile,0.001); Assert.assertEquals(0.1, quantiles[0].error,0.001); Assert.assertEquals(0.99, quantiles[1].quantile,0.001); Assert.assertEquals(0.1, quantiles[1].error,0.001); Assert.assertEquals(0.999, quantiles[2].quantile,0.001); Assert.assertEquals(0.1, quantiles[2].error,0.001); } @Test public void parseMultipleQuantilesWithWindowCorrectly() { BaseCollectorConfig cfg = TestUtilities.simpleSummaryCfg(); cfg.setQuantileOrBucket("0.95,0.1|0.99,0.1|0.999,0.1;60"); QuantileDefinition[] quantiles = cfg.getQuantiles(); Assert.assertEquals(3, quantiles.length); Assert.assertEquals(0.95, quantiles[0].quantile,0.001); Assert.assertEquals(0.1, quantiles[0].error,0.001); Assert.assertEquals(0.99, quantiles[1].quantile,0.001); Assert.assertEquals(0.1, quantiles[1].error,0.001); Assert.assertEquals(0.999, quantiles[2].quantile,0.001); Assert.assertEquals(0.1, quantiles[2].error,0.001); Assert.assertEquals(60, cfg.getQuantileWindowLength()); } @Test public void parseQauntileFailsAndGivesDEFAULTs() { BaseCollectorConfig cfg = TestUtilities.simpleSummaryCfg(); cfg.setQuantileOrBucket("skdn fsdfu|,nsf"); QuantileDefinition[] quantiles = cfg.getQuantiles(); Assert.assertEquals(3, quantiles.length); Assert.assertArrayEquals(BaseCollectorConfig.DEFAULT_QUANTILES, quantiles); cfg.setQuantileOrBucket(";sdfg|other_str ing|asl dfuy"); quantiles = cfg.getQuantiles(); Assert.assertEquals(3, quantiles.length); Assert.assertArrayEquals(BaseCollectorConfig.DEFAULT_QUANTILES, quantiles); } @Test public void parseReturnsPartialForQuantiles() { BaseCollectorConfig cfg = TestUtilities.simpleSummaryCfg(); cfg.setQuantileOrBucket("skdnfs dfuns f|0.5|0.75,0.1"); //only 1 good at the end QuantileDefinition[] quantiles = cfg.getQuantiles(); Assert.assertEquals(1, quantiles.length); Assert.assertEquals(0.75, quantiles[0].quantile,0.001); Assert.assertEquals(0.1, quantiles[0].error,0.001); cfg.setQuantileOrBucket("skdnfsdfunsf|0.5,0.1|0.75,0.1|0.99"); //2 in middle are good quantiles = cfg.getQuantiles(); Assert.assertEquals(2, quantiles.length); Assert.assertEquals(0.5, quantiles[0].quantile,0.001); Assert.assertEquals(0.1, quantiles[0].error,0.001); Assert.assertEquals(0.75, quantiles[1].quantile,0.001); Assert.assertEquals(0.1, quantiles[1].error,0.001); } @Test public void parseBucketsCorrectly() { BaseCollectorConfig cfg = TestUtilities.simpleHistogramCfg(); cfg.setQuantileOrBucket("100,500,1000,2500,5000"); double[] expected = new double[] {100, 500, 1000, 2500, 5000}; double[] buckets = cfg.getBuckets(); Assert.assertEquals(5, buckets.length); Assert.assertArrayEquals(expected, buckets,0.01); } @Test public void parseBucketFailureReturnsDefaults() { BaseCollectorConfig cfg = TestUtilities.simpleHistogramCfg(); cfg.setQuantileOrBucket("akldjand| sfpoa sdnf"); double[] buckets = cfg.getBuckets(); Assert.assertEquals(4, buckets.length); Assert.assertArrayEquals(BaseCollectorConfig.DEFAULT_BUCKET_SIZES, buckets,0.01); cfg.setQuantileOrBucket("fail,otherFi al,123cantPa rse,not123 ThisEither,a3"); buckets = cfg.getBuckets(); Assert.assertEquals(4, buckets.length); Assert.assertArrayEquals(BaseCollectorConfig.DEFAULT_BUCKET_SIZES, buckets,0.01); } @Test public void parseBucketsWithPartialSuccess() { BaseCollectorConfig cfg = TestUtilities.simpleHistogramCfg(); cfg.setQuantileOrBucket("akldjand,17,lakj asdf,123no,48"); double[] buckets = cfg.getBuckets(); Assert.assertEquals(2, buckets.length); Assert.assertEquals(17, buckets[0], 0.001); Assert.assertEquals(48, buckets[1], 0.001); } @Test public void initCorrectly() { BaseCollectorConfig init = new BaseCollectorConfig(); Assert.assertEquals(init.getHelp(), BaseCollectorConfig.DEFAULT_HELP_STRING); Assert.assertArrayEquals(new String[0], init.getLabels()); Assert.assertTrue(init.getLabelsAsString().isEmpty()); Assert.assertEquals(init.getType(), JMeterCollectorType.COUNTER.toString()); Assert.assertTrue( init.getMetricName() + " does not match the expected pattern.", Pattern.matches(BaseCollectorConfig.METRIC_NAME_BASE + "\\p{Alnum}{8}", init.getMetricName())); } @Test public void createCorrectType() { BaseCollectorConfig cfg = TestUtilities.simpleCounterCfg(); Collector c = BaseCollectorConfig.fromConfig(cfg); Assert.assertTrue(c instanceof Counter); cfg = TestUtilities.simpleGaugeCfg(); c = BaseCollectorConfig.fromConfig(cfg); Assert.assertTrue(c instanceof Gauge); cfg = TestUtilities.simpleHistogramCfg(); c = BaseCollectorConfig.fromConfig(cfg); Assert.assertTrue(c instanceof Histogram); cfg = TestUtilities.simpleSummaryCfg(); c = BaseCollectorConfig.fromConfig(cfg); Assert.assertTrue(c instanceof Summary); cfg = TestUtilities.simpleSuccessRatioCfg(); c = BaseCollectorConfig.fromConfig(cfg); Assert.assertTrue(c instanceof SuccessRatioCollector); } @Test public void setOfElementsTest() { BaseCollectorConfig left = TestUtilities.simpleCounterCfg(); BaseCollectorConfig right = TestUtilities.simpleCounterCfg(); Assert.assertNotSame(left, right); Assert.assertEquals(left, right); int leftHash = left.hashCode(); int rightHash = right.hashCode(); Assert.assertEquals(leftHash, rightHash); } } ================================================ FILE: src/test/java/com/github/johrstrom/collector/JMeterCollectorRegistryTest.java ================================================ package com.github.johrstrom.collector; import com.github.johrstrom.test.TestUtilities; import io.prometheus.client.Collector; import org.junit.Assert; import org.junit.Test; public class JMeterCollectorRegistryTest { private JMeterCollectorRegistry registry = JMeterCollectorRegistry.getInstance(); @Test public void safelyGetOrCreate() { BaseCollectorConfig cfg = TestUtilities.simpleHistogramCfg(); cfg.setMetricName("register_tester"); Collector c1 = registry.getOrCreateAndRegister(cfg); Collector c2 = registry.getOrCreateAndRegister(cfg); Assert.assertSame(c1, c2); Assert.assertEquals(c1, c2); registry.unregister(cfg); registry.unregister(cfg); registry.unregister(cfg); Collector c3 = registry.getOrCreateAndRegister(cfg); Assert.assertTrue(c3 != c1 && c3 != c2); Assert.assertTrue(!c3.equals(c1) && !c3.equals(c2)); registry.unregister(cfg); registry.unregister(cfg); } } ================================================ FILE: src/test/java/com/github/johrstrom/collector/SuccessRatioCollectorTest.java ================================================ package com.github.johrstrom.collector; import com.github.johrstrom.listener.ListenerCollectorConfig; import com.github.johrstrom.test.TestUtilities; import io.prometheus.client.Collector.MetricFamilySamples; import io.prometheus.client.Collector.MetricFamilySamples.Sample; import org.junit.Assert; import org.junit.Test; import java.util.List; public class SuccessRatioCollectorTest { private static final JMeterCollectorRegistry reg = JMeterCollectorRegistry.getInstance(); private final String[] labelNames = new String[] {"foo_label","label"}; private final String[] labelValues = new String[] {"bar_value", "myLabelz"}; @Test public void testSuccess() { BaseCollectorConfig base = TestUtilities.simpleSuccessRatioCfg(); base.setLabels(labelNames); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); String baseName = "something_ratio"; cfg.setMetricName(baseName); SuccessRatioCollector c = (SuccessRatioCollector) reg.getOrCreateAndRegister(cfg); c.incrementSuccess(labelValues); List families = c.collect(); boolean foundSuccess, foundFailure, foundTotal; foundSuccess = foundFailure = foundTotal = false; for(MetricFamilySamples family : families) { switch (family.name) { case "something_ratio_success": assertOnSingleFamily(family, 1); foundSuccess = true; break; case "something_ratio_failure": assertOnSingleFamily(family, 0); foundFailure = true; break; case "something_ratio": assertOnSingleFamily(family, 1); foundTotal = true; break; default: Assert.fail(family.name + " is not an expected metric family name"); break; } } Assert.assertTrue(foundSuccess && foundFailure && foundTotal); } @Test public void testFailure() { BaseCollectorConfig base = TestUtilities.simpleSuccessRatioCfg(); base.setLabels(labelNames); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); String baseName = "otherthing_ratio"; cfg.setMetricName(baseName); SuccessRatioCollector c = (SuccessRatioCollector) reg.getOrCreateAndRegister(cfg); c.incrementFailure(labelValues); //only real diff with test above List families = c.collect(); boolean foundSuccess, foundFailure, foundTotal; foundSuccess = foundFailure = foundTotal = false; for(MetricFamilySamples family : families) { switch (family.name) { case "otherthing_ratio_success": assertOnSingleFamily(family, 0); foundSuccess = true; break; case "otherthing_ratio_failure": assertOnSingleFamily(family, 1); foundFailure = true; break; case "otherthing_ratio": assertOnSingleFamily(family, 1); foundTotal = true; break; default: Assert.fail(family.name + " is not an expected metric family name"); break; } } Assert.assertTrue(foundSuccess && foundFailure && foundTotal); } private void assertOnSingleFamily(MetricFamilySamples family, double expectedValue) { Assert.assertEquals(2, family.samples.size()); Sample sample = family.samples.get(0); Assert.assertArrayEquals(labelValues, sample.labelValues.toArray()); Assert.assertArrayEquals(labelNames, sample.labelNames.toArray()); Assert.assertEquals(expectedValue, sample.value, 0.1); } } ================================================ FILE: src/test/java/com/github/johrstrom/config/PrometheusMetricsConfigTest.java ================================================ package com.github.johrstrom.config; import com.github.johrstrom.test.TestUtilities; import org.junit.Assert; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class PrometheusMetricsConfigTest { @Test public void listenerIsSerializable() throws IOException { ByteArrayOutputStream objectBuffer = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(objectBuffer); PrometheusMetricsConfig cfg = new PrometheusMetricsConfig(); cfg.setCollectorConfigs(TestUtilities.simpleListConfig()); out.writeObject(cfg); Assert.assertNotNull(cfg); Assert.assertTrue(objectBuffer.size() > 0); } } ================================================ FILE: src/test/java/com/github/johrstrom/config/gui/ConfigGuiTest.java ================================================ package com.github.johrstrom.config.gui; import com.github.johrstrom.collector.BaseCollectorConfig; import org.apache.jmeter.util.JMeterUtils; import org.junit.Assert; import org.junit.Test; import java.util.Locale; public class ConfigGuiTest { @Test public void simpleTest() { JMeterUtils.setLocale(Locale.ENGLISH); PrometheusMetricsConfigGui gui = new PrometheusMetricsConfigGui<>(); String comment = "this should be the comment"; String name = "simple cfg name"; gui.setName(name); gui.setComment(comment); Assert.assertEquals(name, gui.getName()); Assert.assertEquals(comment, gui.getComment()); } } ================================================ FILE: src/test/java/com/github/johrstrom/listener/ListenerCollectorConfigTest.java ================================================ package com.github.johrstrom.listener; import com.github.johrstrom.test.TestUtilities; import org.junit.Assert; import org.junit.Test; public class ListenerCollectorConfigTest { @Test public void setOfElementsTest() { ListenerCollectorConfig left = new ListenerCollectorConfig(TestUtilities.simpleCounterCfg()); ListenerCollectorConfig right = new ListenerCollectorConfig(TestUtilities.simpleCounterCfg()); Assert.assertNotSame(left, right); Assert.assertEquals(left, right); int leftHash = left.hashCode(); int rightHash = right.hashCode(); Assert.assertEquals(leftHash, rightHash); } } ================================================ FILE: src/test/java/com/github/johrstrom/listener/PrometheusListenerTest.java ================================================ package com.github.johrstrom.listener; import com.github.johrstrom.collector.JMeterCollectorRegistry; import com.github.johrstrom.collector.SuccessRatioCollector; import com.github.johrstrom.test.TestUtilities; import com.github.johrstrom.test.TestUtilities.ResultAndVariables; import io.prometheus.client.Collector; import io.prometheus.client.Collector.MetricFamilySamples; import io.prometheus.client.Collector.MetricFamilySamples.Sample; import io.prometheus.client.Counter; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; import org.junit.Assert; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.List; public class PrometheusListenerTest { private static final JMeterCollectorRegistry reg = JMeterCollectorRegistry.getInstance(); static { TestUtilities.createJmeterEnv(); } @Test public void listenerIsSerializable() throws IOException { ByteArrayOutputStream objectBuffer = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(objectBuffer); PrometheusListener listener = new PrometheusListener(); out.writeObject(listener); Assert.assertNotNull(listener); Assert.assertTrue(objectBuffer.size() > 0); } @Test public void canHaveDuplicateMetrics() { PrometheusListener first = new PrometheusListener(); PrometheusListener second = new PrometheusListener(); first.setCollectorConfigs(TestUtilities.simpleListListener()); second.setCollectorConfigs(TestUtilities.simpleListListener()); first = (PrometheusListener) first.clone(); second = (PrometheusListener) second.clone(); first.testStarted(); second.testStarted(); first.testEnded(); second.testEnded(); } @Test public void lazyInizializationPrometheusServer() throws NoSuchFieldException, IllegalAccessException { PrometheusListener listener = new PrometheusListener(); // Remove the server to test lazy initialization on test start Field prometheusServerField = listener.getClass().getDeclaredField("server"); prometheusServerField.setAccessible(true); prometheusServerField.set(listener, null); listener.setCollectorConfigs(TestUtilities.simpleListListener()); listener.testStarted(); Assert.assertNotNull(prometheusServerField.get(listener)); } @Test public void lazyInizializationRegistry() throws NoSuchFieldException, IllegalAccessException { PrometheusListener listener = new PrometheusListener(); // Remove the server to test lazy initialization on test start Field registryField = listener.getClass().getSuperclass().getDeclaredField("registry"); registryField.setAccessible(true); registryField.set(listener, null); listener.setCollectorConfigs(TestUtilities.simpleListListener()); listener.testStarted(); Assert.assertNotNull(registryField.get(listener)); } @Test public void canReadJMX() throws IOException { File jmx = new File("target/test-classes/simple_prometheus_example.jmx"); HashTree tree = SaveService.loadTree(jmx); Assert.assertNotNull(tree); } @Test public void updateAllTypes() { PrometheusListener listener = new PrometheusListener(); List configs = TestUtilities.fullListListener(); for (ListenerCollectorConfig cfg : configs) { cfg.setLabels(TestUtilities.TEST_LABELS); } listener.setCollectorConfigs(configs); listener.testStarted(); long connectTime = 3123; long idleTime = 1233; long elapsedTime = 2213; long latency = 1532; int responseSize = 1342; int samplesOccurred = 0; double expectedCreated = System.currentTimeMillis() / 1000.0; ResultAndVariables res = TestUtilities.resultWithLabels(); res.result.setConnectTime(connectTime); res.result.setResponseData(new byte[responseSize]); res.result.setLatency(latency); res.result.setIdleTime(idleTime); res.result.setStampAndTime(System.currentTimeMillis(), elapsedTime); res.result.setSuccessful(true); SampleEvent event = new SampleEvent(res.result, "tg1", res.vars); listener.sampleOccurred(event); // 1st event, successful samplesOccurred++; res.result.setSuccessful(false); // 2nd event, failure event = new SampleEvent(res.result, "tg1", res.vars); listener.sampleOccurred(event); samplesOccurred++; for (ListenerCollectorConfig cfg : configs) { String name = cfg.getMetricName(); switch (name) { case "test_count_total": Counter counter = (Counter) reg.getOrCreateAndRegister(cfg); double shouldBeTwo = counter.labels(TestUtilities.EXPECTED_LABELS).get(); Assert.assertEquals(2.0, shouldBeTwo, 0.1); break; case "test_failure_total": counter = (Counter) reg.getOrCreateAndRegister(cfg); double shouldBeOne = counter.labels(TestUtilities.EXPECTED_LABELS).get(); Assert.assertEquals(1.0, shouldBeOne, 0.1); break; case "test_success_total": counter = (Counter) reg.getOrCreateAndRegister(cfg); shouldBeOne = counter.labels(TestUtilities.EXPECTED_LABELS).get(); Assert.assertEquals(1.0, shouldBeOne, 0.1); break; case "test_ratio": assertOnRatio(cfg); break; // histograms case "test_hist_rtime": assertOnHistogram(reg.getOrCreateAndRegister(cfg), elapsedTime * samplesOccurred, samplesOccurred, expectedCreated, elapsedTime); break; case "test_hist_rsize": assertOnHistogram(reg.getOrCreateAndRegister(cfg), responseSize * samplesOccurred, samplesOccurred, expectedCreated, responseSize); break; case "test_hist_latency": assertOnHistogram(reg.getOrCreateAndRegister(cfg), latency * samplesOccurred, samplesOccurred, expectedCreated, latency); break; case "test_hist_idle_time": assertOnHistogram(reg.getOrCreateAndRegister(cfg), idleTime * samplesOccurred, samplesOccurred, expectedCreated, idleTime); break; case "test_hist_connect_time": assertOnHistogram(reg.getOrCreateAndRegister(cfg), connectTime * samplesOccurred, samplesOccurred, expectedCreated, connectTime); break; // summaries case "test_summary_rtime": assertOnSummary(reg.getOrCreateAndRegister(cfg), elapsedTime * samplesOccurred, samplesOccurred, expectedCreated, elapsedTime); break; case "test_summary_rsize": assertOnSummary(reg.getOrCreateAndRegister(cfg), responseSize * samplesOccurred, samplesOccurred, expectedCreated, responseSize); break; case "test_summary_latency": assertOnSummary(reg.getOrCreateAndRegister(cfg), latency * samplesOccurred, samplesOccurred, expectedCreated, latency); break; case "test_summary_idle_time": assertOnSummary(reg.getOrCreateAndRegister(cfg), idleTime * samplesOccurred, samplesOccurred, expectedCreated, idleTime); break; case "test_summary_connect_time": assertOnSummary(reg.getOrCreateAndRegister(cfg), connectTime * samplesOccurred, samplesOccurred, expectedCreated, connectTime); break; default: Assert.fail(name + " triggered untested switch case"); break; } } listener.testEnded(); } private void assertOnRatio(ListenerCollectorConfig cfg) { SuccessRatioCollector ratio = (SuccessRatioCollector) reg.getOrCreateAndRegister(cfg); double shouldBeOne = ratio.getSuccess(TestUtilities.EXPECTED_LABELS); Assert.assertEquals(1.0, shouldBeOne, 0.1); shouldBeOne = ratio.getFailure(TestUtilities.EXPECTED_LABELS); Assert.assertEquals(1.0, shouldBeOne, 0.1); double shouldBeTwo = ratio.getTotal(TestUtilities.EXPECTED_LABELS); Assert.assertEquals(2.0, shouldBeTwo, 0.1); } protected static void assertOnHistogram(Collector collector, double expectedSum, double expectedCount, double expectedCreated, double boundary) { List metrics = collector.collect(); Assert.assertEquals(1, metrics.size()); MetricFamilySamples family = metrics.get(0); // labels + Inf + count + sum Assert.assertEquals(TestUtilities.EXPECTED_LABELS.length + 5, family.samples.size()); for (Sample sample : family.samples) { List values = sample.labelValues; List names = sample.labelNames; // correct labels without 'le' (bin size) for (int i = 0; i < TestUtilities.TEST_LABELS.length; i++) { Assert.assertEquals(TestUtilities.TEST_LABELS[i], names.get(i)); Assert.assertEquals(TestUtilities.EXPECTED_LABELS[i], values.get(i)); } int labelSize = TestUtilities.EXPECTED_LABELS.length; // _sum and _count don't have an 'le' label if (sample.name.endsWith("count")) { Assert.assertTrue(values.size() == labelSize && names.size() == labelSize); Assert.assertEquals(expectedCount, sample.value, 0.1); } else if (sample.name.endsWith("sum")) { Assert.assertTrue(values.size() == labelSize && names.size() == labelSize); Assert.assertEquals(expectedSum, sample.value, 0.1); } else if (sample.name.endsWith("created")) { Assert.assertTrue(values.size() == labelSize && names.size() == labelSize); Assert.assertEquals(expectedCreated, sample.value, 0.1); } else { Assert.assertEquals(values.size(), labelSize + 1); Assert.assertEquals(names.size(), labelSize + 1); String leString = values.get(labelSize); double le = (!leString.isEmpty() && !leString.equals("+Inf")) ? Double.parseDouble(leString) : Double.MAX_VALUE; if (le == Double.MAX_VALUE) { Assert.assertEquals(expectedCount, sample.value, 0.1); } else if (le < boundary) { Assert.assertEquals(0, sample.value, 0.1); } else if (le > boundary) { Assert.assertEquals(expectedCount, sample.value, 0.1); } } } } protected void assertOnSummary(Collector collector, double expectedSum, double expectedCount, double expectedCreated, double expectedValue) { List metrics = collector.collect(); Assert.assertEquals(1, metrics.size()); MetricFamilySamples family = metrics.get(0); Assert.assertEquals(6, family.samples.size()); // 3 quantiles + count + sum for (Sample sample : family.samples) { List values = sample.labelValues; List names = sample.labelNames; for (int i = 0; i < TestUtilities.TEST_LABELS.length; i++) { Assert.assertEquals(TestUtilities.TEST_LABELS[i], names.get(i)); Assert.assertEquals(TestUtilities.EXPECTED_LABELS[i], values.get(i)); } // _sum and _count don't have an 'le' label if (sample.name.endsWith("count")) { Assert.assertEquals(values.size(), TestUtilities.EXPECTED_LABELS.length); Assert.assertEquals(names.size(), TestUtilities.TEST_LABELS.length); Assert.assertEquals(expectedCount, sample.value, 0.1); } else if (sample.name.endsWith("sum")) { Assert.assertEquals(expectedSum, sample.value, 0.1); } else if (sample.name.endsWith("created")) { Assert.assertEquals(expectedCreated, sample.value, 0.1); } else { Assert.assertEquals(values.size(), TestUtilities.EXPECTED_LABELS.length + 1); Assert.assertEquals(values.size(), TestUtilities.EXPECTED_LABELS.length + 1); Assert.assertEquals(expectedValue, sample.value, 0.1); } } } } ================================================ FILE: src/test/java/com/github/johrstrom/listener/PrometheusServerTest.java ================================================ package com.github.johrstrom.listener; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import org.junit.Assert; import org.junit.Test; public class PrometheusServerTest { @Test public void ensureCleanStartStop() throws Exception { PrometheusServer server = PrometheusServer.getInstance(); Assert.assertNotNull(server); server.start(); Thread.currentThread(); Thread.sleep(1000); pingAPI(); // also be sure that you can hit the api server.stop(); server.start(); Thread.currentThread(); Thread.sleep(1000); pingAPI(); // you can still hit it after stopping server.stop(); } private String pingAPI() throws IOException { URL url = new URL("http://localhost:9270/metrics"); URLConnection conn = url.openConnection(); conn.setReadTimeout(3000); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuilder sb = new StringBuilder(); String line = ""; while ((line = in.readLine()) != null) { sb.append(line); } return sb.toString(); } } ================================================ FILE: src/test/java/com/github/johrstrom/listener/gui/ListenerGuiTest.java ================================================ package com.github.johrstrom.listener.gui; import org.apache.jmeter.util.JMeterUtils; import org.junit.Assert; import org.junit.Test; import java.util.Locale; public class ListenerGuiTest { @Test public void simpleTest() { JMeterUtils.setLocale(Locale.ENGLISH); PrometheusListenerGui gui = new PrometheusListenerGui(); String comment = "this should be the comment"; String name = "simple listener name"; gui.setName(name); gui.setComment(comment); Assert.assertEquals(name, gui.getName()); Assert.assertEquals(comment, gui.getComment()); } } ================================================ FILE: src/test/java/com/github/johrstrom/listener/updater/AbstractUpdaterTest.java ================================================ package com.github.johrstrom.listener.updater; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.listener.ListenerCollectorConfig; import com.github.johrstrom.test.TestUtilities; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterVariables; import org.junit.Assert; import org.junit.Test; public class AbstractUpdaterTest { public static class TestUpdater extends AbstractUpdater { public TestUpdater(ListenerCollectorConfig cfg) { super(cfg); } @Override public void update(SampleEvent e) { // do nothing } } @Test public void testKeywords() { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(new String[] {"label","code", "thread_group"}); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); TestUpdater u = new TestUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel("test_label"); res.setResponseCode("204"); SampleEvent event = new SampleEvent(res,"test_tg", new JMeterVariables()); String[] labels = u.labelValues(event); Assert.assertTrue(labels.length == 3); Assert.assertArrayEquals(new String[] {"test_label", "204", "test_tg"}, labels); } @Test public void testVariables() { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(new String[] {"foo", "bar"}); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); TestUpdater u = new TestUpdater(cfg); JMeterVariables vars = new JMeterVariables(); vars.put("foo", "funny"); vars.put("bar", "banal"); JMeterContextService.getContext().setVariables(vars); SampleEvent event = new SampleEvent(new SampleResult(),"tg1", vars); String[] labels = u.labelValues(event); Assert.assertTrue(labels.length == 2); Assert.assertArrayEquals(new String[] {"funny", "banal"}, labels); } @Test public void testCombo() { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(new String[] {"foo", "code", "bar", "label"}); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); TestUpdater u = new TestUpdater(cfg); JMeterVariables vars = new JMeterVariables(); vars.put("foo", "funnier"); vars.put("bar", "more banal"); JMeterContextService.getContext().setVariables(vars); SampleResult res = new SampleResult(); res.setSampleLabel("one after the"); res.setResponseCode("909"); SampleEvent event = new SampleEvent(res,"tg1", vars); String[] labels = u.labelValues(event); Assert.assertTrue(labels.length == 4); Assert.assertArrayEquals(new String[] {"funnier", "909", "more banal", "one after the"}, labels); } @Test public void testNulls() { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(new String[] {"be_null_one", "be_null_two", "code"}); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); TestUpdater u = new TestUpdater(cfg); SampleResult res = new SampleResult(); res.setResponseCode("304"); SampleEvent event = new SampleEvent(res ,"tg1", new JMeterVariables()); String[] labels = u.labelValues(event); Assert.assertTrue(labels.length == 3); Assert.assertArrayEquals(new String[] {"null", "null", "304"}, labels); } } ================================================ FILE: src/test/java/com/github/johrstrom/listener/updater/AggregatedTypeUpdaterTest.java ================================================ package com.github.johrstrom.listener.updater; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.JMeterCollectorRegistry; import com.github.johrstrom.listener.ListenerCollectorConfig; import com.github.johrstrom.listener.ListenerCollectorConfig.Measurable; import com.github.johrstrom.test.TestUtilities; import io.prometheus.client.Collector.MetricFamilySamples; import io.prometheus.client.Collector.MetricFamilySamples.Sample; import io.prometheus.client.Histogram; import io.prometheus.client.Summary; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterVariables; import org.junit.Assert; import org.junit.Test; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class AggregatedTypeUpdaterTest { private static final JMeterCollectorRegistry reg = JMeterCollectorRegistry.getInstance(); private static final String[] labels = new String[] {"foo_label","label","code"}; private static final String name = "super_cool_sampler"; private static final String code = "super_cool_sampler"; private static final String var_value = "bar_value"; private static final String[] expectedLabels = new String[] {var_value,name,code}; @Test public void testHistogramResponseTime() { BaseCollectorConfig base = TestUtilities.simpleHistogramCfg(); base.setLabels(labels); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMeasuring(Measurable.ResponseTime.toString()); cfg.setMetricName("ct_updater_test_hist_rt"); Histogram collector = (Histogram) reg.getOrCreateAndRegister(cfg); AggregatedTypeUpdater u = new AggregatedTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel(name); int responseTime = 650; res.setStampAndTime(System.currentTimeMillis(), 650); res.setResponseCode(code); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", var_value); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] actualLabels = u.labelValues(e); Assert.assertArrayEquals(expectedLabels, actualLabels); u.update(e); List metrics = collector.collect(); assertEquals(1, metrics.size()); MetricFamilySamples family = metrics.get(0); assertEquals(8, family.samples.size()); // 4 buckets + Inf + count + sum for(Sample sample : family.samples) { List values = sample.labelValues; List names = sample.labelNames; //correct labels without 'le' (bin size) boolean correctLabels = names.get(0).equals(labels[0]) && names.get(1).equals(labels[1]) && names.get(2).equals(labels[2]) && values.get(0).equals(expectedLabels[0]) && values.get(1).equals(expectedLabels[1]) && values.get(2).equals(expectedLabels[2]); assertTrue(correctLabels); // _sum and _count don't have an 'le' label if(sample.name.endsWith("count") || sample.name.endsWith("sum") || sample.name.endsWith("created")) { assertTrue(values.size() == 3 && names.size() == 3); if(sample.name.endsWith("count")) { Assert.assertEquals(1, sample.value, 0.1); } else if (sample.name.endsWith("created")) { Assert.assertEquals(System.currentTimeMillis() / 1000.0, sample.value, 0.1); } else { Assert.assertEquals(responseTime, sample.value, 0.1); } }else { assertTrue(values.size() == 4 && names.size() == 4); String leString = values.get(3); double le = (!leString.isEmpty() && !leString.equals("+Inf")) ? Double.parseDouble(leString) : Double.MAX_VALUE; if(le == Double.MAX_VALUE) { Assert.assertEquals(1, sample.value, 0.1); } else if(le < responseTime) { Assert.assertEquals(0, sample.value, 0.1); }else if(le > responseTime) { Assert.assertEquals(1, sample.value, 0.1); } } } } @Test public void testSummaryResponseTime() { BaseCollectorConfig base = TestUtilities.simpleSummaryCfg(); base.setLabels(labels); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMeasuring(Measurable.ResponseTime.toString()); cfg.setMetricName("ct_updater_test_summary_rt"); Summary collector = (Summary) reg.getOrCreateAndRegister(cfg); AggregatedTypeUpdater u = new AggregatedTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel(name); int responseTime = 650; res.setStampAndTime(System.currentTimeMillis(), 650); res.setResponseCode(code); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", var_value); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] actualLabels = u.labelValues(e); Assert.assertArrayEquals(expectedLabels, actualLabels); u.update(e); List metrics = collector.collect(); assertEquals(1, metrics.size()); MetricFamilySamples family = metrics.get(0); assertEquals(6, family.samples.size()); // 3 quantiles + count + sum for(Sample sample : family.samples) { List values = sample.labelValues; List names = sample.labelNames; //correct labels without quantile boolean correctLabels = names.get(0).equals(labels[0]) && names.get(1).equals(labels[1]) && names.get(2).equals(labels[2]) && values.get(0).equals(expectedLabels[0]) && values.get(1).equals(expectedLabels[1]) && values.get(2).equals(expectedLabels[2]); assertTrue(correctLabels); // _sum and _count don't have an 'le' label if(sample.name.endsWith("count") || sample.name.endsWith("sum") || sample.name.endsWith("created")) { assertTrue(values.size() == 3 && names.size() == 3); if(sample.name.endsWith("count")) { Assert.assertEquals(1, sample.value, 0.1); }else if (sample.name.endsWith("created")) { Assert.assertEquals(System.currentTimeMillis() / 1000.0, sample.value, 0.1); } else { Assert.assertEquals(responseTime, sample.value, 0.1); } }else { assertTrue(values.size() == 4 && names.size() == 4); Assert.assertEquals(responseTime, sample.value, 0.1); } } } @Test public void testHistogramResponseSize() { BaseCollectorConfig base = TestUtilities.simpleHistogramCfg(); base.setLabels(labels); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMetricName("ct_updater_test_histogram_rsize"); cfg.setMeasuring(Measurable.ResponseSize.toString()); Histogram collector = (Histogram) reg.getOrCreateAndRegister(cfg); AggregatedTypeUpdater u = new AggregatedTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel(name); int responseSize = 650; res.setResponseData(new byte[responseSize]); res.setResponseCode(code); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", var_value); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] actualLabels = u.labelValues(e); Assert.assertArrayEquals(expectedLabels, actualLabels); u.update(e); List metrics = collector.collect(); Assert.assertEquals(1, metrics.size()); MetricFamilySamples family = metrics.get(0); Assert.assertEquals(8, family.samples.size()); // 4 buckets + Inf + count + sum for(Sample sample : family.samples) { List values = sample.labelValues; List names = sample.labelNames; this.correctLabels(names, values); // _sum and _count don't have an 'le' label if(sample.name.endsWith("count") || sample.name.endsWith("sum") || sample.name.endsWith("created")) { assertTrue(values.size() == 3 && names.size() == 3); if(sample.name.endsWith("count")) { Assert.assertEquals(1, sample.value, 0.1); } else if (sample.name.endsWith("created")) { Assert.assertEquals(System.currentTimeMillis() / 1000.0, sample.value, 0.1); } else { Assert.assertEquals(responseSize, sample.value, 0.1); } }else { assertTrue(values.size() == 4 && names.size() == 4); String leString = values.get(3); double le = (!leString.isEmpty() && !leString.equals("+Inf")) ? Double.parseDouble(leString) : Double.MAX_VALUE; if(le == Double.MAX_VALUE) { Assert.assertEquals(1, sample.value, 0.1); } else if(le < responseSize) { Assert.assertEquals(0, sample.value, 0.1); }else if(le > responseSize) { Assert.assertEquals(1, sample.value, 0.1); } } } } @Test public void testSummaryResponseSize() { BaseCollectorConfig base = TestUtilities.simpleSummaryCfg(); base.setLabels(labels); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMetricName("ct_updater_test_summary_rsize"); cfg.setMeasuring(Measurable.ResponseSize.toString()); Summary collector = (Summary) reg.getOrCreateAndRegister(cfg); AggregatedTypeUpdater u = new AggregatedTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel(name); int responseSize = 650; res.setResponseData(new byte[responseSize]); res.setResponseCode(code); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", var_value); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] actualLabels = u.labelValues(e); Assert.assertArrayEquals(expectedLabels, actualLabels); u.update(e); List metrics = collector.collect(); Assert.assertEquals(1, metrics.size()); MetricFamilySamples family = metrics.get(0); Assert.assertEquals(6, family.samples.size()); // 3 quantiles + count + sum for(Sample sample : family.samples) { List values = sample.labelValues; List names = sample.labelNames; this.correctLabels(names, values); // _sum and _count don't have an 'le' label if(sample.name.endsWith("count") || sample.name.endsWith("sum") || sample.name.endsWith("created")) { assertTrue(values.size() == 3 && names.size() == 3); if(sample.name.endsWith("count")) { Assert.assertEquals(1, sample.value, 0.1); } else if (sample.name.endsWith("created")) { Assert.assertEquals(System.currentTimeMillis() / 1000.0, sample.value, 0.1); } else { Assert.assertEquals(responseSize, sample.value, 0.1); } }else { assertTrue(values.size() == 4 && names.size() == 4); Assert.assertEquals(responseSize, sample.value, 0.1); } } } private void correctLabels(List names, List values) { boolean correctLabels = names.get(0).equals(labels[0]) && names.get(1).equals(labels[1]) && names.get(2).equals(labels[2]) && values.get(0).equals(expectedLabels[0]) && values.get(1).equals(expectedLabels[1]) && values.get(2).equals(expectedLabels[2]); assertTrue(correctLabels); } } ================================================ FILE: src/test/java/com/github/johrstrom/listener/updater/CountTypeUpdaterTest.java ================================================ package com.github.johrstrom.listener.updater; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.JMeterCollectorRegistry; import com.github.johrstrom.collector.SuccessRatioCollector; import com.github.johrstrom.listener.ListenerCollectorConfig; import com.github.johrstrom.listener.ListenerCollectorConfig.Measurable; import com.github.johrstrom.test.TestUtilities; import io.prometheus.client.Counter; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterVariables; import org.junit.Assert; import org.junit.Test; public class CountTypeUpdaterTest { private static final JMeterCollectorRegistry reg = JMeterCollectorRegistry.getInstance(); private final String[] labelNames = new String[] {"foo_label","label", "code"}; private final String[] expectedLabels = new String[] {"bar_value", "myLabelz", "909"}; @Test public void successCountOnSamplesTest() { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(labelNames); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMeasuring(Measurable.SuccessTotal.toString()); cfg.setMetricName("ct_updater_test_success_only"); Counter c = (Counter) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel("myLabelz"); res.setStampAndTime(System.currentTimeMillis(), 10000); res.setSuccessful(true); // #1 res.setResponseCode("909"); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", "bar_value"); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] labels = u.labelValues(e); u.update(e); Assert.assertArrayEquals(expectedLabels, labels); double shouldBeOne = c.labels(expectedLabels).get(); Assert.assertEquals(1, shouldBeOne, 0.1); u.update(e); // #2 res.setSuccessful(false); e = new SampleEvent(res,"tg1", vars); u.update(e); // could be #3, but shouldn't update double shouldBeTwo = c.labels(expectedLabels).get(); Assert.assertEquals(2, shouldBeTwo, 0.1); } @Test public void failureCountOnSamplesTest() { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(labelNames); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMetricName("ct_updater_test_failure_only"); cfg.setMeasuring(Measurable.FailureTotal.toString()); Counter c = (Counter) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel("myLabelz"); res.setStampAndTime(System.currentTimeMillis(), 10000); res.setSuccessful(false); // #1 res.setResponseCode("909"); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", "bar_value"); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] labels = u.labelValues(e); u.update(e); Assert.assertArrayEquals(expectedLabels, labels); double shouldBeOne = c.labels(expectedLabels).get(); Assert.assertEquals(1, shouldBeOne, 0.1); u.update(e); // #2 res.setSuccessful(true); e = new SampleEvent(res,"tg1", vars); u.update(e); // could be #3, but shouldn't update double shouldBeTwo = c.labels(expectedLabels).get(); Assert.assertEquals(2, shouldBeTwo, 0.1); } @Test public void countSamplesTotalTest() { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(this.labelNames); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMetricName("count_sample_total_test"); cfg.setMeasuring(Measurable.CountTotal.toString()); Counter c = (Counter) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel("myLabelz"); res.setStampAndTime(System.currentTimeMillis(), 10000); res.setResponseCode("909"); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", "bar_value"); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] labels = u.labelValues(e); u.update(e); // #1 Assert.assertArrayEquals(this.expectedLabels, labels); double shouldBeOne = c.labels(this.expectedLabels).get(); Assert.assertEquals(1, shouldBeOne, 0.1); u.update(e); // #2 res.setSuccessful(false); // should be 3 no matter what, even if last e = new SampleEvent(res,"tg1", vars); // 2 where success u.update(e); double shouldBeThree = c.labels(this.expectedLabels).get(); Assert.assertEquals(3, shouldBeThree, 0.1); } @Test public void testSROnSamples() { BaseCollectorConfig base = TestUtilities.simpleSuccessRatioCfg(); base.setLabels(labelNames); String baseName = "ct_updater_test_success_ratio_samples"; base.setMetricName(baseName); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); SuccessRatioCollector c = (SuccessRatioCollector) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult res = new SampleResult(); res.setSampleLabel("myLabelz"); res.setStampAndTime(System.currentTimeMillis(), 10000); res.setSuccessful(true); res.setResponseCode("909"); JMeterVariables vars = new JMeterVariables(); vars.put("foo_label", "bar_value"); JMeterContextService.getContext().setVariables(vars); SampleEvent e = new SampleEvent(res,"tg1", vars); String[] actualLabels = u.labelValues(e); u.update(e); // first success Assert.assertArrayEquals(expectedLabels, actualLabels); double successShouldBeOne = c.getSuccess(expectedLabels); double failureShouldBeZero = c.getFailure(expectedLabels); double totalShouldBeOne = c.getTotal(expectedLabels); Assert.assertEquals(1, successShouldBeOne, 0.1); Assert.assertEquals(0, failureShouldBeZero, 0.1); Assert.assertEquals(1, totalShouldBeOne, 0.1); u.update(e); // the 2nd success res.setSuccessful(false); e = new SampleEvent(res,"tg1", vars); u.update(e); // now failure = 1, success = 2 and total = 3 double successShouldBeTwo = c.getSuccess(expectedLabels); double failureShouldBeOne = c.getFailure(expectedLabels); double totalShouldBeThree = c.getTotal(expectedLabels); Assert.assertEquals(2, successShouldBeTwo, 0.1); Assert.assertEquals(1, failureShouldBeOne, 0.1); Assert.assertEquals(3, totalShouldBeThree, 0.1); } @Test public void testRatioOnAssertions() { ListenerCollectorConfig cfg = TestUtilities.listenerSuccessRatioCfg( "ratio_on_assertions", ListenerCollectorConfig.ASSERTIONS); SuccessRatioCollector ratio = (SuccessRatioCollector) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult result = newSampleResultWithAssertion(true); u.update(new SampleEvent(result,"tg1", vars())); // #1 success double actualSuccess = ratio.getSuccess(TestUtilities.EXPECTED_ASSERTION_LABELS); double actualFailure = ratio.getFailure(TestUtilities.EXPECTED_ASSERTION_LABELS); double actualTotal = ratio.getTotal(TestUtilities.EXPECTED_ASSERTION_LABELS); Assert.assertEquals(1.0, actualSuccess, 0.1); Assert.assertEquals(0.0, actualFailure, 0.1); Assert.assertEquals(1.0, actualTotal, 0.1); result = newSampleResultWithAssertion(false); u.update(new SampleEvent(result,"tg1", vars())); // #1 failure actualSuccess = ratio.getSuccess(TestUtilities.EXPECTED_ASSERTION_LABELS); actualFailure = ratio.getFailure(TestUtilities.EXPECTED_ASSERTION_LABELS); actualTotal = ratio.getTotal(TestUtilities.EXPECTED_ASSERTION_LABELS); Assert.assertEquals(1.0, actualSuccess, 0.1); Assert.assertEquals(1.0, actualFailure, 0.1); Assert.assertEquals(2.0, actualTotal, 0.1); result = newSampleResultWithAssertion(true); result.addAssertionResult(altAssertion(true)); u.update(new SampleEvent(result,"tg1", vars())); // #now update alt as well actualSuccess = ratio.getSuccess(TestUtilities.EXPECTED_ASSERTION_LABELS); actualFailure = ratio.getFailure(TestUtilities.EXPECTED_ASSERTION_LABELS); actualTotal = ratio.getTotal(TestUtilities.EXPECTED_ASSERTION_LABELS); Assert.assertEquals(2.0, actualSuccess, 0.1); Assert.assertEquals(1.0, actualFailure, 0.1); Assert.assertEquals(3.0, actualTotal, 0.1); actualSuccess = ratio.getSuccess(TestUtilities.EXPECTED_ASSERTION_LABELS_ALT); actualFailure = ratio.getFailure(TestUtilities.EXPECTED_ASSERTION_LABELS_ALT); actualTotal = ratio.getTotal(TestUtilities.EXPECTED_ASSERTION_LABELS_ALT); Assert.assertEquals(1.0, actualSuccess, 0.1); Assert.assertEquals(0.0, actualFailure, 0.1); Assert.assertEquals(1.0, actualTotal, 0.1); } @Test public void testSuccessAssertions() { ListenerCollectorConfig cfg = TestUtilities.listenerCounterCfg( "count_assertion_success_test", Measurable.SuccessTotal, ListenerCollectorConfig.ASSERTIONS); Counter c = (Counter) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult result = newSampleResultWithAssertion(true); u.update(new SampleEvent(result,"tg1", vars())); // #1 double shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(1.0, shouldBeOne, 0.1); result = newSampleResultWithAssertion(false); u.update(new SampleEvent(result,"tg1", vars())); // #could be 2, but should be 1 shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(1.0, shouldBeOne, 0.1); // now update 2 assertions result = newSampleResultWithAssertion(true); result.addAssertionResult(altAssertion(true)); u.update(new SampleEvent(result,"tg1", vars())); // #now should be 2 double shouldBeTwo = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(2.0, shouldBeTwo, 0.1); shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS_ALT).get(); //but alt is just 1 Assert.assertEquals(1.0, shouldBeOne, 0.1); } @Test public void testFailureAssertions() { ListenerCollectorConfig cfg = TestUtilities.listenerCounterCfg( "count_assertion_failure_test", Measurable.FailureTotal, ListenerCollectorConfig.ASSERTIONS); Counter c = (Counter) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult result = newSampleResultWithAssertion(false); u.update(new SampleEvent(result,"tg1", vars())); // #1 double shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(1.0, shouldBeOne, 0.1); result = newSampleResultWithAssertion(true); u.update(new SampleEvent(result,"tg1", vars())); // #could be 2, but should be 1 shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(1.0, shouldBeOne, 0.1); // now update 2 assertions result = newSampleResultWithAssertion(false); result.addAssertionResult(altAssertion(false)); u.update(new SampleEvent(result,"tg1", vars())); // #now should be 2 double shouldBeTwo = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(2.0, shouldBeTwo, 0.1); shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS_ALT).get(); //but alt is just 1 Assert.assertEquals(1.0, shouldBeOne, 0.1); } @Test public void testTotalAssertions() { ListenerCollectorConfig cfg = TestUtilities.listenerCounterCfg( "count_assertion_total_test", Measurable.CountTotal, ListenerCollectorConfig.ASSERTIONS); Counter c = (Counter) reg.getOrCreateAndRegister(cfg); CountTypeUpdater u = new CountTypeUpdater(cfg); SampleResult result = newSampleResultWithAssertion(false); u.update(new SampleEvent(result,"tg1", vars())); // #1 double shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(1.0, shouldBeOne, 0.1); result = newSampleResultWithAssertion(true); u.update(new SampleEvent(result,"tg1", vars())); // #2 double shouldBeTwo = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(2.0, shouldBeTwo, 0.1); // now update 2 assertions result = newSampleResultWithAssertion(false); result.addAssertionResult(altAssertion(false)); u.update(new SampleEvent(result,"tg1", vars())); // #3 double shouldBeThree = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS).get(); Assert.assertEquals(3.0, shouldBeThree, 0.1); shouldBeOne = c.labels(TestUtilities.EXPECTED_ASSERTION_LABELS_ALT).get(); //but alt is just 1 Assert.assertEquals(1.0, shouldBeOne, 0.1); } public static SampleResult newSampleResult(boolean success) { SampleResult res = new SampleResult(); res.setSampleLabel(TestUtilities.TEST_SAMPLER_NAME); res.setSuccessful(success); return res; } public static SampleResult newSampleResultWithAssertion(boolean success) { SampleResult res = newSampleResult(success); AssertionResult assertion = new AssertionResult(TestUtilities.TEST_ASSERTION_NAME); assertion.setFailure(!success); res.addAssertionResult(assertion); return res; } public static AssertionResult altAssertion(boolean success) { AssertionResult assertion = new AssertionResult(TestUtilities.TEST_ASSERTION_NAME_ALT); assertion.setFailure(!success); return assertion; } public static JMeterVariables vars() { JMeterVariables vars = new JMeterVariables(); vars.put(TestUtilities.TEST_VAR_NAME, TestUtilities.TEST_VAR_VALUE); JMeterContextService.getContext().setVariables(vars); return vars; } } ================================================ FILE: src/test/java/com/github/johrstrom/test/NOOPThreadMonitor.java ================================================ package com.github.johrstrom.test; import org.apache.jmeter.threads.JMeterThread; import org.apache.jmeter.threads.JMeterThreadMonitor; public class NOOPThreadMonitor implements JMeterThreadMonitor { @Override public void threadFinished(JMeterThread jmt) { // NOOP } } ================================================ FILE: src/test/java/com/github/johrstrom/test/TestUtilities.java ================================================ package com.github.johrstrom.test; import com.github.johrstrom.collector.BaseCollectorConfig; import com.github.johrstrom.collector.BaseCollectorConfig.JMeterCollectorType; import com.github.johrstrom.listener.ListenerCollectorConfig; import com.github.johrstrom.listener.ListenerCollectorConfig.Measurable; import org.apache.jmeter.control.LoopController; import org.apache.jmeter.engine.StandardJMeterEngine; import org.apache.jmeter.gui.tree.JMeterTreeListener; import org.apache.jmeter.gui.tree.JMeterTreeModel; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.save.SaveService; import org.apache.jmeter.threads.ThreadGroup; import org.apache.jmeter.threads.*; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.collections.HashTree; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; public class TestUtilities { public static final String TEST_VAR_NAME = "arbitrary_var"; public static final String TEST_SAMPLER_NAME = "super_cool_sampler"; public static final String TEST_ASSERTION_NAME = "super_cool_assertion"; public static final String TEST_ASSERTION_NAME_ALT = "other_super_cool_assertion"; public static final String TEST_SAMPLER_CODE = "909"; public static final String TEST_VAR_VALUE = "bar_value"; public static final String[] TEST_LABELS = new String[] {TEST_VAR_NAME,"label","code"}; public static final String[] EXPECTED_LABELS = new String[] {TEST_VAR_VALUE, TEST_SAMPLER_NAME, TEST_SAMPLER_CODE}; public static final String[] TEST_ASSERTION_LABELS = new String[] {TEST_VAR_NAME,"label"}; public static final String[] EXPECTED_ASSERTION_LABELS = new String[] {TEST_VAR_VALUE, TEST_ASSERTION_NAME}; public static final String[] EXPECTED_ASSERTION_LABELS_ALT = new String[] {TEST_VAR_VALUE, TEST_ASSERTION_NAME_ALT}; public static BaseCollectorConfig simpleCounterCfg() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setMetricName("simple_counter"); cfg.setType(JMeterCollectorType.COUNTER.toString()); cfg.setHelp("some helpe message"); return cfg; } public static ListenerCollectorConfig listenerCounterCfg(String name, Measurable measurable, String listenTo) { BaseCollectorConfig base = TestUtilities.simpleCounterCfg(); base.setLabels(TestUtilities.TEST_ASSERTION_LABELS); ListenerCollectorConfig cfg = new ListenerCollectorConfig(base); cfg.setMetricName(name); cfg.setMeasuring(measurable.toString()); cfg.setListenTo(listenTo); return cfg; } public static ListenerCollectorConfig listenerSuccessRatioCfg(String name, String listenTo) { ListenerCollectorConfig cfg = new ListenerCollectorConfig(); cfg.setLabels(TestUtilities.TEST_ASSERTION_LABELS); cfg.setMetricName(name); cfg.setType(JMeterCollectorType.SUCCESS_RATIO.toString()); cfg.setHelp("some helpe message"); cfg.setListenTo(listenTo); return cfg; } public static BaseCollectorConfig simpleSummaryCfg() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setMetricName("simple_summary"); cfg.setType(JMeterCollectorType.SUMMARY.toString()); cfg.setHelp("some helpe message"); return cfg; } public static BaseCollectorConfig simpleHistogramCfg() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setMetricName("simple_histogram"); cfg.setType(JMeterCollectorType.HISTOGRAM.toString()); cfg.setHelp("some helpe message"); return cfg; } public static BaseCollectorConfig simpleGaugeCfg() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setMetricName("simple_gauge"); cfg.setType(JMeterCollectorType.GAUGE.toString()); cfg.setHelp("some helpe message"); return cfg; } public static BaseCollectorConfig simpleSuccessRatioCfg() { BaseCollectorConfig cfg = new BaseCollectorConfig(); cfg.setMetricName("simple_ratio"); cfg.setType(JMeterCollectorType.SUCCESS_RATIO.toString()); cfg.setHelp("some helpe message"); return cfg; } public static void createJmeterEnv() { JMeterUtils.setJMeterHome("src/test/resources"); JMeterUtils.setLocale(Locale.ENGLISH); JMeterUtils.loadJMeterProperties("src/test/resources/bin/jmeter.properties"); try { SaveService.loadProperties(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } JMeterTreeModel jMeterTreeModel = new JMeterTreeModel(); JMeterTreeListener jMeterTreeListener = new JMeterTreeListener(); jMeterTreeListener.setModel(jMeterTreeModel); JMeterContextService.getContext().setVariables(new JMeterVariables()); StandardJMeterEngine engine = new StandardJMeterEngine(); JMeterContextService.getContext().setEngine(engine); JMeterThreadMonitor monitor = new NOOPThreadMonitor(); HashTree hashtree = new HashTree(); hashtree.add(new LoopController()); JMeterThread thread = new JMeterThread(hashtree, monitor, null); thread.setThreadName("test thread"); JMeterContextService.getContext().setThread(thread); ThreadGroup tg1 = new ThreadGroup(); tg1.setName("tg1"); JMeterContextService.getContext().setThreadGroup(tg1); } public static List simpleListConfig() { List list = new ArrayList(); list.add(simpleGaugeCfg()); list.add(simpleHistogramCfg()); list.add(simpleSummaryCfg()); list.add(simpleCounterCfg()); list.add(simpleSuccessRatioCfg()); return list; } public static List simpleListListener() { List list = new ArrayList(); list.add(new ListenerCollectorConfig(simpleGaugeCfg())); list.add(new ListenerCollectorConfig(simpleHistogramCfg())); list.add(new ListenerCollectorConfig(simpleSummaryCfg())); list.add(new ListenerCollectorConfig(simpleCounterCfg())); list.add(new ListenerCollectorConfig(simpleSuccessRatioCfg())); return list; } public static List fullListListener(){ List list = new ArrayList(); // ---------- counters and success ratio ListenerCollectorConfig cfg = new ListenerCollectorConfig(simpleCounterCfg()); cfg = redoNameAndMeasuring(cfg, "test_count_total", Measurable.CountTotal); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_failure_total", Measurable.FailureTotal); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_success_total", Measurable.SuccessTotal); list.add(cfg); cfg = new ListenerCollectorConfig(simpleSuccessRatioCfg()); cfg = redoNameAndMeasuring(cfg, "test_ratio", Measurable.SuccessRatio); list.add(cfg); // ------- histograms cfg = new ListenerCollectorConfig(simpleHistogramCfg()); cfg = redoNameAndMeasuring(cfg, "test_hist_rtime", Measurable.ResponseTime); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_hist_rsize", Measurable.ResponseSize); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_hist_latency", Measurable.Latency); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_hist_idle_time", Measurable.IdleTime); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_hist_connect_time", Measurable.ConnectTime); list.add(cfg); // -------- summaries cfg = new ListenerCollectorConfig(simpleSummaryCfg()); cfg = redoNameAndMeasuring(cfg, "test_summary_rtime", Measurable.ResponseTime); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_summary_rsize", Measurable.ResponseSize); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_summary_latency", Measurable.Latency); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_summary_idle_time", Measurable.IdleTime); list.add(cfg); cfg = redoNameAndMeasuring(cfg, "test_summary_connect_time", Measurable.ConnectTime); list.add(cfg); return list; } public static ListenerCollectorConfig redoNameAndMeasuring(ListenerCollectorConfig cfg, String name, Measurable m) { ListenerCollectorConfig clone = (ListenerCollectorConfig) cfg.clone(); clone.setMetricName(name); clone.setMeasuring(m.toString()); return clone; } public static ResultAndVariables resultWithLabels() { SampleResult result = new SampleResult(); result.setSampleLabel(TEST_SAMPLER_NAME); result.setResponseCode(TEST_SAMPLER_CODE); JMeterVariables vars = new JMeterVariables(); vars.put(TEST_VAR_NAME, TEST_VAR_VALUE); JMeterContextService.getContext().setVariables(vars); return new ResultAndVariables(result, vars); } public static class ResultAndVariables { public SampleResult result; public JMeterVariables vars; public ResultAndVariables(SampleResult result, JMeterVariables vars) { this.result = result; this.vars = vars; } } } ================================================ FILE: src/test/resources/bin/jmeter.properties ================================================ ################################################################################ # Apache JMeter Property file ################################################################################ ## Licensed to the Apache Software Foundation (ASF) under one or more ## contributor license agreements. See the NOTICE file distributed with ## this work for additional information regarding copyright ownership. ## The ASF licenses this file to You under the Apache License, Version 2.0 ## (the "License"); you may not use this file except in compliance with ## the License. You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. ################################################################################ # # THIS FILE SHOULD NOT BE MODIFIED # # This avoids having to re-apply the modifications when upgrading JMeter # Instead only user.properties should be modified: # 1/ copy the property you want to modify to user.properties from jmeter.properties # 2/ Change its value there # ################################################################################ # JMeter properties are described in the file # http://jmeter.apache.org/usermanual/properties_reference.html # A local copy can be found in # printable_docs/usermanual/properties_reference.html #Preferred GUI language. Comment out to use the JVM default locale's language. #language=en # Additional locale(s) to add to the displayed list. # The current default list is: en, fr, de, no, es, tr, ja, zh_CN, zh_TW, pl, pt_BR # [see JMeterMenuBar#makeLanguageMenu()] # The entries are a comma-separated list of language names #locales.add=zu #--------------------------------------------------------------------------- # XML Parser #--------------------------------------------------------------------------- # Path to a Properties file containing Namespace mapping in the form # prefix=Namespace # Example: # ns=http://biz.aol.com/schema/2006-12-18 #xpath.namespace.config= #--------------------------------------------------------------------------- # SSL configuration #--------------------------------------------------------------------------- ## SSL System properties are now in system.properties # JMeter no longer converts javax.xxx property entries in this file into System properties. # These must now be defined in the system.properties file or on the command-line. # The system.properties file gives more flexibility. # By default, SSL session contexts are now created per-thread, rather than being shared. # The original behaviour can be enabled by setting the JMeter property to true #https.sessioncontext.shared=false # Be aware that https default protocol may vary depending on the version of JVM # See https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https # See https://bz.apache.org/bugzilla/show_bug.cgi?id=58236 # Default HTTPS protocol level: #https.default.protocol=TLS # This may need to be changed here (or in user.properties) to: #https.default.protocol=SSLv3 # List of protocols to enable. You may have to select only a subset if you find issues with target server. # This is needed when server does not support Socket version negotiation, this can lead to: # javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated # java.net.SocketException: Connection reset # see https://bz.apache.org/bugzilla/show_bug.cgi?id=54759 #https.socket.protocols=SSLv2Hello SSLv3 TLSv1 # Control if we allow reuse of cached SSL context between iterations # set the value to 'false' to reset the SSL context each iteration #https.use.cached.ssl.context=true # Start and end index to be used with keystores with many entries # The default is to use entry 0, i.e. the first #https.keyStoreStartIndex=0 #https.keyStoreEndIndex=0 #--------------------------------------------------------------------------- # Look and Feel configuration #--------------------------------------------------------------------------- #Classname of the Swing default UI # # The LAF classnames that are available are now displayed as ToolTip text # when hovering over the Options/Look and Feel selection list. # # You can either use a full class name, as shown below, # or one of the strings "System" or "CrossPlatform" which means # JMeter will use the corresponding string returned by UIManager.getLookAndFeelClassName() # LAF can be overridden by os.name (lowercased, spaces replaced by '_') # Sample os.name LAF: #jmeter.laf.windows_xp=javax.swing.plaf.metal.MetalLookAndFeel # Failing that, the OS family = os.name, but only up to first space: # Sample OS family LAF: #jmeter.laf.windows=com.sun.java.swing.plaf.windows.WindowsLookAndFeel # Custom settings for Mac using System LAF if you don't want to use Darcula #jmeter.laf.mac=System # Failing that, the JMeter default laf can be defined: #jmeter.laf=System # If none of the above jmeter.laf properties are defined, JMeter uses the CrossPlatform LAF. # This is because the CrossPlatform LAF generally looks better than the System LAF. # See https://bz.apache.org/bugzilla/show_bug.cgi?id=52026 for details # N.B. the laf can be defined in user.properties. # LoggerPanel display # default to false #jmeter.loggerpanel.display=false # Enable LogViewer Panel to receive log event even if closed # Enabled since 2.12 # Note this has some impact on performances, but as GUI mode must # not be used for Load Test it is acceptable #jmeter.loggerpanel.enable_when_closed=true # Max lines kept in LoggerPanel, default to 1000 chars # 0 means no limit #jmeter.loggerpanel.maxlength=1000 # Interval period in ms to process the queue of events of the listeners #jmeter.gui.refresh_period=500 # HiDPI mode (default: false) # Activate a 'pseudo'-hidpi mode. Allows to increase size of some UI elements # which are not correctly managed by JVM with high resolution screens in Linux or Windows #jmeter.hidpi.mode=false # To enable pseudo-hidpi mode change to true #jmeter.hidpi.mode=true # HiDPI scale factor #jmeter.hidpi.scale.factor=1.0 # Suggested value for HiDPI #jmeter.hidpi.scale.factor=2.0 # Toolbar display # Toolbar icon definitions #jmeter.toolbar.icons=org/apache/jmeter/images/toolbar/icons-toolbar.properties # Toolbar list #jmeter.toolbar=new,open,close,save,save_as_testplan,|,cut,copy,paste,|,expand,collapse,toggle,|,test_start,test_stop,test_shutdown,|,test_start_remote_all,test_stop_remote_all,test_shutdown_remote_all,|,test_clear,test_clear_all,|,search,search_reset,|,function_helper,help # Toolbar icons default size: 22x22. Available sizes are: 22x22, 32x32, 48x48 #jmeter.toolbar.icons.size=22x22 # Suggested value for HiDPI #jmeter.toolbar.icons.size=48x48 # Icon definitions # default: #jmeter.icons=org/apache/jmeter/images/icon.properties # alternate: #jmeter.icons=org/apache/jmeter/images/icon_1.properties # Historical icon set (deprecated) #jmeter.icons=org/apache/jmeter/images/icon_old.properties # Tree icons default size: 19x19. Available sizes are: 19x19, 24x24, 32x32, 48x48 # Useful for HiDPI display (see below) #jmeter.tree.icons.size=19x19 # Suggested value for HiDPI screen like 3200x1800: #jmeter.tree.icons.size=32x32 #Components to not display in JMeter GUI (GUI class name or static label) # These elements are deprecated and will be removed in next version: # MongoDB Script, MongoDB Source Config, Monitor Results # BSF Elements not_in_menu=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler,org.apache.jmeter.protocol.mongodb.config.MongoSourceElement,\ org.apache.jmeter.timers.BSFTimer,org.apache.jmeter.modifiers.BSFPreProcessor,org.apache.jmeter.extractor.BSFPostProcessor,org.apache.jmeter.assertions.BSFAssertion,\ org.apache.jmeter.visualizers.BSFListener,org.apache.jmeter.protocol.java.sampler.BSFSampler,\ org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui # Number of items in undo history # Feature is disabled by default (0) due to known and not fixed bugs: # https://bz.apache.org/bugzilla/show_bug.cgi?id=57043 # https://bz.apache.org/bugzilla/show_bug.cgi?id=57039 # https://bz.apache.org/bugzilla/show_bug.cgi?id=57040 # Set it to a number > 0 (25 can be a good default) # The bigger it is, the more it consumes memory #undo.history.size=0 # Hotkeys to add JMeter components, will add elements when you press Ctrl+0 .. Ctrl+9 (Command+0 .. Command+9 on Mac) gui.quick_0=ThreadGroupGui gui.quick_1=HttpTestSampleGui gui.quick_2=RegexExtractorGui gui.quick_3=AssertionGui gui.quick_4=ConstantTimerGui gui.quick_5=TestActionGui gui.quick_6=JSR223PostProcessor gui.quick_7=JSR223PreProcessor gui.quick_8=DebugSampler gui.quick_9=ViewResultsFullVisualizer #--------------------------------------------------------------------------- # JMX Backup configuration #--------------------------------------------------------------------------- #Enable auto backups of the .jmx file when a test plan is saved. #When enabled, before the .jmx is saved, it will be backed up to the directory pointed #by the jmeter.gui.action.save.backup_directory property (see below). Backup file names are built #after the jmx file being saved. For example, saving test-plan.jmx will create a test-plan-000012.jmx #in the backup directory provided that the last created backup file is test-plan-000011.jmx. #Default value is true indicating that auto backups are enabled #jmeter.gui.action.save.backup_on_save=true #Set the backup directory path where JMX backups will be created upon save in the GUI. #If not set (what it defaults to) then backup files will be created in #a sub-directory of the JMeter base installation. The default directory is ${JMETER_HOME}/backups #If set and the directory does not exist, it will be created. #jmeter.gui.action.save.backup_directory= #Set the maximum time (in hours) that backup files should be preserved since the save time. #By default no expiration time is set which means we keep backups for ever. #jmeter.gui.action.save.keep_backup_max_hours=0 #Set the maximum number of backup files that should be preserved. By default 10 backups will be preserved. #Setting this to zero will cause the backups to not being deleted (unless keep_backup_max_hours is set to a non zero value) #jmeter.gui.action.save.keep_backup_max_count=10 #Enable auto saving of the .jmx file before start run a test plan #When enabled, before the run, the .jmx will be saved and also backed up to the directory pointed #save_automatically_before_run=true #--------------------------------------------------------------------------- # Remote hosts and RMI configuration #--------------------------------------------------------------------------- # Remote Hosts - comma delimited remote_hosts=127.0.0.1 #remote_hosts=localhost:1099,localhost:2010 # RMI port to be used by the server (must start rmiregistry with same port) #server_port=1099 # To change the port to (say) 1234: # On the server(s) # - set server_port=1234 # - start rmiregistry with port 1234 # On Windows this can be done by: # SET SERVER_PORT=1234 # JMETER-SERVER # # On Unix: # SERVER_PORT=1234 jmeter-server # # On the client: # - set remote_hosts=server:1234 # Parameter that controls the RMI port used by RemoteSampleListenerImpl (The Controller) # Default value is 0 which means port is randomly assigned # You may need to open Firewall port on the Controller machine #client.rmi.localport=0 # When distributed test is starting, there may be several attempts to initialize # remote engines. By default, only single try is made. Increase following property # to make it retry for additional times #client.tries=1 # If there is initialization retries, following property sets delay between attempts #client.retries_delay=5000 # When all initialization tries was made, test will fail if some remote engines are failed # Set following property to true to ignore failed nodes and proceed with test #client.continue_on_fail=false # To change the default port (1099) used to access the server: #server.rmi.port=1234 # To use a specific port for the JMeter server engine, define # the following property before starting the server: #server.rmi.localport=4000 # The jmeter server creates by default the RMI registry as part of the server process. # To stop the server creating the RMI registry: #server.rmi.create=false # Define the following property to cause JMeter to exit after the first test #server.exitaftertest=true # # Configuration of Secure RMI connection # # Type of keystore : JKS #server.rmi.ssl.keystore.type=JKS # # Keystore file that contains private key #server.rmi.ssl.keystore.file=rmi_keystore.jks # # Password of Keystore #server.rmi.ssl.keystore.password=changeit # # Key alias #server.rmi.ssl.keystore.alias=rmi # # Type of truststore : JKS #server.rmi.ssl.truststore.type=JKS # # Keystore file that contains certificate #server.rmi.ssl.truststore.file=rmi_keystore.jks # # Password of Trust store #server.rmi.ssl.truststore.password=changeit # # Set this if you don't want to use SSL for RMI #server.rmi.ssl.disable=false #--------------------------------------------------------------------------- # Include Controller #--------------------------------------------------------------------------- # Prefix used by IncludeController when building file name #includecontroller.prefix= #--------------------------------------------------------------------------- # HTTP Java configuration #--------------------------------------------------------------------------- # Number of connection retries performed by HTTP Java sampler before giving up # 0 means no retry since version 3.0 #http.java.sampler.retries=0 #--------------------------------------------------------------------------- # Following properties apply to Apache HttpClient #--------------------------------------------------------------------------- # set the socket timeout (or use the parameter http.socket.timeout) # for AJP Sampler implementation. # Value is in milliseconds #httpclient.timeout=0 # 0 == no timeout # Set the http version (defaults to 1.1) #httpclient.version=1.1 (or use the parameter http.protocol.version) # Define characters per second > 0 to emulate slow connections #httpclient.socket.http.cps=0 #httpclient.socket.https.cps=0 #Enable loopback protocol #httpclient.loopback=true # Define the local host address to be used for multi-homed hosts #httpclient.localaddress=1.2.3.4 #--------------------------------------------------------------------------- # AuthManager Kerberos configuration #--------------------------------------------------------------------------- # AuthManager Kerberos configuration # Name of application module used in jaas.conf #kerberos_jaas_application=JMeter # Should ports be stripped from urls before constructing SPNs # for SPNEGO authentication #kerberos.spnego.strip_port=true #--------------------------------------------------------------------------- # Apache HttpComponents HTTPClient configuration (HTTPClient4) #--------------------------------------------------------------------------- # define a properties file for overriding Apache HttpClient parameters # Uncomment this line if you put anything in hc.parameters file #hc.parameters.file=hc.parameters # Preemptively send Authorization Header when BASIC auth is used #httpclient4.auth.preemptive=true # Number of retries to attempt (default 0) #httpclient4.retrycount=0 # true if it's OK to retry requests that have been sent # This will retry Idempotent and non Idempotent requests # This should usually be false, but it can be useful # when testing against some Load Balancers like Amazon ELB #httpclient4.request_sent_retry_enabled=false # Idle connection timeout (Milliseconds) to apply if the server does not send # Keep-Alive headers (default 0) # Set this > 0 to compensate for servers that don't send a Keep-Alive header # If <= 0, idle timeout will only apply if the server sends a Keep-Alive header #httpclient4.idletimeout=0 # Check connections if the elapsed time (Milliseconds) since the last # use of the connection exceed this value #httpclient4.validate_after_inactivity=1700 # TTL (in Milliseconds) represents an absolute value. # No matter what, the connection will not be re-used beyond its TTL. #httpclient4.time_to_live=2000 # Max size in bytes of PUT body to retain in result sampler. # Bigger results will be clipped. #httpclient4.max_body_retain_size=32768 #--------------------------------------------------------------------------- # HTTP Cache Manager configuration #--------------------------------------------------------------------------- # # Space or comma separated list of methods that can be cached #cacheable_methods=GET # N.B. This property is currently a temporary solution for Bug 56162 # Since 2.12, JMeter does not create anymore a Sample Result with 204 response # code for a resource found in cache which is inline with what browser do. #cache_manager.cached_resource_mode=RETURN_NO_SAMPLE # You can choose between 3 modes: # RETURN_NO_SAMPLE (default) # RETURN_200_CACHE # RETURN_CUSTOM_STATUS # Those mode have the following behaviours: # RETURN_NO_SAMPLE: # this mode returns no Sample Result, it has no additional configuration # RETURN_200_CACHE: # this mode will return Sample Result with response code to 200 and # response message to "(ex cache)", you can modify response message by setting # RETURN_200_CACHE.message=(ex cache) # RETURN_CUSTOM_STATUS: # This mode lets you select what response code and message you want to return, # if you use this mode you need to set those properties # RETURN_CUSTOM_STATUS.code= # RETURN_CUSTOM_STATUS.message= #--------------------------------------------------------------------------- # Results file configuration #--------------------------------------------------------------------------- # This section helps determine how result data will be saved. # The commented out values are the defaults. # legitimate values: xml, csv, db. Only xml and csv are currently supported. #jmeter.save.saveservice.output_format=csv # The below properties are true when field should be saved; false otherwise # # assertion_results_failure_message only affects CSV output #jmeter.save.saveservice.assertion_results_failure_message=true # # legitimate values: none, first, all #jmeter.save.saveservice.assertion_results=none # #jmeter.save.saveservice.data_type=true #jmeter.save.saveservice.label=true #jmeter.save.saveservice.response_code=true # response_data is not currently supported for CSV output #jmeter.save.saveservice.response_data=false # Save ResponseData for failed samples #jmeter.save.saveservice.response_data.on_error=false #jmeter.save.saveservice.response_message=true #jmeter.save.saveservice.successful=true #jmeter.save.saveservice.thread_name=true #jmeter.save.saveservice.time=true #jmeter.save.saveservice.subresults=true #jmeter.save.saveservice.assertions=true #jmeter.save.saveservice.latency=true # Only available with HttpClient4 #jmeter.save.saveservice.connect_time=true #jmeter.save.saveservice.samplerData=false #jmeter.save.saveservice.responseHeaders=false #jmeter.save.saveservice.requestHeaders=false #jmeter.save.saveservice.encoding=false #jmeter.save.saveservice.bytes=true # Only available with HttpClient4 #jmeter.save.saveservice.sent_bytes=true #jmeter.save.saveservice.url=false #jmeter.save.saveservice.filename=false #jmeter.save.saveservice.hostname=false #jmeter.save.saveservice.thread_counts=true #jmeter.save.saveservice.sample_count=false #jmeter.save.saveservice.idle_time=true # Timestamp format - this only affects CSV output files # legitimate values: none, ms, or a format suitable for SimpleDateFormat #jmeter.save.saveservice.timestamp_format=ms #jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS # For use with Comma-separated value (CSV) files or other formats # where the fields' values are separated by specified delimiters. # Default: #jmeter.save.saveservice.default_delimiter=, # For TAB, one can use: #jmeter.save.saveservice.default_delimiter=\t # Only applies to CSV format files: # Print field names as first line in CSV #jmeter.save.saveservice.print_field_names=true # Optional list of JMeter variable names whose values are to be saved in the result data files. # Use commas to separate the names. For example: #sample_variables=SESSION_ID,REFERENCE # N.B. The current implementation saves the values in XML as attributes, # so the names must be valid XML names. # By default JMeter sends the variable to all servers # to ensure that the correct data is available at the client. # Optional xml processing instruction for line 2 of the file: # Example: #jmeter.save.saveservice.xml_pi= # Default value: #jmeter.save.saveservice.xml_pi= # Prefix used to identify filenames that are relative to the current base #jmeter.save.saveservice.base_prefix=~/ # AutoFlush on each line written in XML or CSV output # Setting this to true will result in less test results data loss in case of Crash # but with impact on performances, particularly for intensive tests (low or no pauses) # Since JMeter 2.10, this is false by default #jmeter.save.saveservice.autoflush=false #--------------------------------------------------------------------------- # Settings that affect SampleResults #--------------------------------------------------------------------------- # Save the start time stamp instead of the end # This also affects the timestamp stored in result files sampleresult.timestamp.start=true # Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis() #sampleresult.useNanoTime=true # Use a background thread to calculate the nanoTime offset # Set this to <= 0 to disable the background thread #sampleresult.nanoThreadSleep=5000 #--------------------------------------------------------------------------- # Upgrade property #--------------------------------------------------------------------------- # File that holds a record of name changes for backward compatibility issues upgrade_properties=/bin/upgrade.properties #--------------------------------------------------------------------------- # JMeter Test Script recorder configuration # # N.B. The element was originally called the Proxy recorder, which is why the # properties have the prefix "proxy". #--------------------------------------------------------------------------- # If the recorder detects a gap of at least 5s (default) between HTTP requests, # it assumes that the user has clicked a new URL #proxy.pause=5000 # Add numeric prefix to Sampler names (default true) #proxy.number.requests=true # List of URL patterns that will be added to URL Patterns to exclude # Separate multiple lines with ; #proxy.excludes.suggested=.*\\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff|woff2) # Change the default HTTP Sampler (currently HttpClient4) # Java: #jmeter.httpsampler=HTTPSampler #or #jmeter.httpsampler=Java # # HttpClient4.x #jmeter.httpsampler=HttpClient4 # By default JMeter tries to be more lenient with RFC2616 redirects and allows # relative paths. # If you want to test strict conformance, set this value to true # When the property is true, JMeter follows http://tools.ietf.org/html/rfc3986#section-5.2 #jmeter.httpclient.strict_rfc2616=false # Default content-type include filter to use #proxy.content_type_include=text/html|text/plain|text/xml # Default content-type exclude filter to use #proxy.content_type_exclude=image/.*|text/css|application/.* # Default headers to remove from Header Manager elements # (Cookie and Authorization are always removed) #proxy.headers.remove=If-Modified-Since,If-None-Match,Host # Binary content-type handling # These content-types will be handled by saving the request in a file: #proxy.binary.types=application/x-amf,application/x-java-serialized-object # The files will be saved in this directory: #proxy.binary.directory=user.dir # The files will be created with this file filesuffix: #proxy.binary.filesuffix=.binary #--------------------------------------------------------------------------- # Test Script Recorder certificate configuration #--------------------------------------------------------------------------- #proxy.cert.directory= #proxy.cert.file=proxyserver.jks #proxy.cert.type=JKS #proxy.cert.keystorepass=password #proxy.cert.keypassword=password #proxy.cert.factory=SunX509 # define this property if you wish to use your own keystore #proxy.cert.alias= # The default validity for certificates created by JMeter #proxy.cert.validity=7 # Use dynamic key generation (if supported by JMeter/JVM) # If false, will revert to using a single key with no certificate #proxy.cert.dynamic_keys=true #--------------------------------------------------------------------------- # Test Script Recorder miscellaneous configuration #--------------------------------------------------------------------------- # Whether to attempt disabling of samples that resulted from redirects # where the generated samples use auto-redirection #proxy.redirect.disabling=true # SSL configuration #proxy.ssl.protocol=TLS #--------------------------------------------------------------------------- # JMeter Proxy configuration #--------------------------------------------------------------------------- # use command-line flags for user-name and password #http.proxyDomain=NTLM domain, if required by HTTPClient sampler #--------------------------------------------------------------------------- # HTTPSampleResponse Parser configuration #--------------------------------------------------------------------------- # Space-separated list of parser groups HTTPResponse.parsers=htmlParser wmlParser cssParser # for each parser, there should be a parser.types and a parser.className property # CSS Parser based on ph-css cssParser.className=org.apache.jmeter.protocol.http.parser.CssParser cssParser.types=text/css # CSS parser LRU cache size # This cache stores the URLs found in a CSS to avoid continuously parsing the CSS # By default the cache size is 400 # It can be disabled by setting its value to 0 #css.parser.cache.size=400 # Let the CSS Parser ignore all css errors #css.parser.ignore_all_css_errors=true #--------------------------------------------------------------------------- # HTML Parser configuration #--------------------------------------------------------------------------- # Define the HTML parser to be used. # Default parser: # This new parser (since 2.10) should perform better than all others # see https://bz.apache.org/bugzilla/show_bug.cgi?id=55632 # Do not comment this property htmlParser.className=org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser # Other parsers: # Default parser before 2.10 #htmlParser.className=org.apache.jmeter.protocol.http.parser.JTidyHTMLParser # Note that Regexp extractor may detect references that have been commented out. # In many cases it will work OK, but you should be aware that it may generate # additional references. #htmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser # This parser is based on JSoup, it should be the most accurate but less # performant than LagartoBasedHtmlParser #htmlParser.className=org.apache.jmeter.protocol.http.parser.JsoupBasedHtmlParser #Used by HTTPSamplerBase to associate htmlParser with content types below htmlParser.types=text/html application/xhtml+xml application/xml text/xml #--------------------------------------------------------------------------- # WML Parser configuration #--------------------------------------------------------------------------- wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser #Used by HTTPSamplerBase to associate wmlParser with content types below wmlParser.types=text/vnd.wap.wml #--------------------------------------------------------------------------- # Remote batching configuration #--------------------------------------------------------------------------- # How is Sample sender implementations configured: # - true (default) means client configuration will be used # - false means server configuration will be used #sample_sender_client_configured=true # By default when Stripping modes are used JMeter since 3.1 will strip # response even for SampleResults in error. # If you want to revert to previous behaviour (no stripping of Responses in error) # set this property to false #sample_sender_strip_also_on_error=true # Remote batching support # Since JMeter 2.9, default is MODE_STRIPPED_BATCH, which returns samples in # batch mode (every 100 samples or every minute by default) # Note also that MODE_STRIPPED_BATCH strips response data from SampleResult, so if you need it change to # another mode # Batch returns samples in batches # Statistical returns sample summary statistics # mode can also be the class name of an implementation of org.apache.jmeter.samplers.SampleSender #mode=Standard #mode=Batch #mode=Statistical #Set to true to key statistical samples on threadName rather than threadGroup #key_on_threadname=false #mode=Stripped #mode=StrippedBatch #mode=org.example.load.MySampleSender # #num_sample_threshold=100 # Value is in milliseconds #time_threshold=60000 # # Asynchronous sender; uses a queue and background worker process to return the samples #mode=Asynch # default queue size #asynch.batch.queue.size=100 # Same as Asynch but strips response data from SampleResult #mode=StrippedAsynch # # DiskStore: Serialises the samples to disk, rather than saving in memory #mode=DiskStore # Same as DiskStore but strips response data from SampleResult #mode=StrippedDiskStore # Note: the mode is currently resolved on the client; # other properties (e.g. time_threshold) are resolved on the server. #--------------------------------------------------------------------------- # JDBC Request configuration #--------------------------------------------------------------------------- # String used to indicate a null value #jdbcsampler.nullmarker=]NULL[ # # Max size of BLOBs and CLOBs to store in JDBC sampler. Result will be cut off #jdbcsampler.max_retain_result_size=65536 # Database validation query # based in https://stackoverflow.com/questions/10684244/dbcp-validationquery-for-different-databases list jdbc.config.check.query=select 1 from INFORMATION_SCHEMA.SYSTEM_USERS|select 1 from dual|select 1 from sysibm.sysdummy1|select 1|select 1 from rdb$database jdbc.config.jdbc.driver.class=com.mysql.jdbc.Driver|org.postgresql.Driver|oracle.jdbc.OracleDriver|com.ingres.jdbc.IngresDriver|com.microsoft.sqlserver.jdbc.SQLServerDriver|com.microsoft.jdbc.sqlserver.SQLServerDriver|org.apache.derby.jdbc.ClientDriver|org.hsqldb.jdbc.JDBCDriver|com.ibm.db2.jcc.DB2Driver|org.apache.derby.jdbc.ClientDriver|org.h2.Driver|org.firebirdsql.jdbc.FBDrivery|org.mariadb.jdbc.Driver|org.sqlite.JDBC|net.sourceforge.jtds.jdbc.Driver #--------------------------------------------------------------------------- # OS Process Sampler configuration #--------------------------------------------------------------------------- # Polling to see if process has finished its work, used when a timeout is configured on sampler #os_sampler.poll_for_timeout=100 #--------------------------------------------------------------------------- # TCP Sampler configuration #--------------------------------------------------------------------------- # The default handler class #tcp.handler=TCPClientImpl # # eolByte = byte value for end of line # set this to a value outside the range -128 to +127 to skip eol checking #tcp.eolByte=1000 # # TCP Charset, used by org.apache.jmeter.protocol.tcp.sampler.TCPClientImpl # default to Platform defaults charset as returned by Charset.defaultCharset().name() #tcp.charset= # # status.prefix and suffix = strings that enclose the status response code #tcp.status.prefix=Status= #tcp.status.suffix=. # # status.properties = property file to convert codes to messages #tcp.status.properties=mytestfiles/tcpstatus.properties # The length prefix used by LengthPrefixedBinaryTCPClientImpl implementation # defaults to 2 bytes. #tcp.binarylength.prefix.length=2 #--------------------------------------------------------------------------- # Summariser - Generate Summary Results - configuration (mainly applies to non-GUI mode) #--------------------------------------------------------------------------- # # Comment the following property to disable the default non-GUI summariser # [or change the value to rename it] # (applies to non-GUI mode only) summariser.name=summary # # interval between summaries (in seconds) default 30 seconds #summariser.interval=30 # # Write messages to log file #summariser.log=true # # Write messages to System.out #summariser.out=true # Ignore SampleResults generated by TransactionControllers # defaults to true #summariser.ignore_transaction_controller_sample_result=true #--------------------------------------------------------------------------- # Aggregate Report and Aggregate Graph - configuration #--------------------------------------------------------------------------- # # Percentiles to display in reports # Can be float value between 0 and 100 # First percentile to display, defaults to 90% #aggregate_rpt_pct1=90 # Second percentile to display, defaults to 95% #aggregate_rpt_pct2=95 # Second percentile to display, defaults to 99% #aggregate_rpt_pct3=99 #--------------------------------------------------------------------------- # BackendListener - configuration #--------------------------------------------------------------------------- # # Backend metrics window mode (fixed=fixed-size window, timed=time boxed) #backend_metrics_window_mode=fixed # Backend metrics sliding window size for Percentiles, Min, Max #backend_metrics_window=100 # Backend metrics sliding window size for Percentiles, Min, Max # when backend_metrics_window_mode is timed # Setting this value too high can lead to OOM #backend_metrics_large_window=5000 ######################## # Graphite Backend ######################## # Send interval in second # Defaults to 1 second #backend_graphite.send_interval=1 ######################## # Influx Backend ######################## # Send interval in second # Defaults to 5 seconds #backend_influxdb.send_interval=5 #Influxdb timeouts #backend_influxdb.connection_timeout=1000 #backend_influxdb.socket_timeout=3000 #backend_influxdb.connection_request_timeout=100 #--------------------------------------------------------------------------- # BeanShell configuration #--------------------------------------------------------------------------- # BeanShell Server properties # # Define the port number as non-zero to start the http server on that port #beanshell.server.port=9000 # The telnet server will be started on the next port # # Define the server initialisation file beanshell.server.file=../extras/startup.bsh # # Define a file to be processed at startup # This is processed using its own interpreter. #beanshell.init.file= # # Define the intialisation files for BeanShell Sampler, Function and other BeanShell elements # N.B. Beanshell test elements do not share interpreters. # Each element in each thread has its own interpreter. # This is retained between samples. #beanshell.sampler.init=BeanShellSampler.bshrc #beanshell.function.init=BeanShellFunction.bshrc #beanshell.assertion.init=BeanShellAssertion.bshrc #beanshell.listener.init=etc #beanshell.postprocessor.init=etc #beanshell.preprocessor.init=etc #beanshell.timer.init=etc # The file BeanShellListeners.bshrc contains sample definitions # of Test and Thread Listeners. #--------------------------------------------------------------------------- # Groovy function #--------------------------------------------------------------------------- #Path to Groovy file containing utility functions to make available to __groovy function #groovy.utilities= # Example #groovy.utilities=bin/utility.groovy #--------------------------------------------------------------------------- # MailerModel configuration #--------------------------------------------------------------------------- # Number of successful samples before a message is sent #mailer.successlimit=2 # # Number of failed samples before a message is sent #mailer.failurelimit=2 #--------------------------------------------------------------------------- # CSVRead configuration #--------------------------------------------------------------------------- # CSVRead delimiter setting (default ",") # Make sure that there are no trailing spaces or tabs after the delimiter # characters, or these will be included in the list of valid delimiters #csvread.delimiter=, #csvread.delimiter=; #csvread.delimiter=! #csvread.delimiter=~ # The following line has a tab after the = #csvread.delimiter= #--------------------------------------------------------------------------- # __time() function configuration # # The properties below can be used to redefine the default formats #--------------------------------------------------------------------------- #time.YMD=yyyyMMdd #time.HMS=HHmmss #time.YMDHMS=yyyyMMdd-HHmmss #time.USER1= #time.USER2= #--------------------------------------------------------------------------- # CSV DataSet configuration #--------------------------------------------------------------------------- # String to return at EOF (if recycle not used) #csvdataset.eofstring= #list in https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html csvdataset.file.encoding_list=UTF-8|UTF-16|ISO-8859-15|US-ASCII #--------------------------------------------------------------------------- # LDAP Sampler configuration #--------------------------------------------------------------------------- # Maximum number of search results returned by a search that will be sorted # to guarantee a stable ordering (if more results then this limit are returned # then no sorting is done). Set to 0 to turn off all sorting, in which case # "Equals" response assertions will be very likely to fail against search results. # #ldapsampler.max_sorted_results=1000 # Number of characters to log for each of three sections (starting matching section, diff section, # ending matching section where not all sections will appear for all diffs) diff display when an Equals # assertion fails. So a value of 100 means a maximum of 300 characters of diff text will be displayed # (+ a number of extra characters like "..." and "[[["/"]]]" which are used to decorate it). #assertion.equals_section_diff_len=100 # test written out to log to signify start/end of diff delta #assertion.equals_diff_delta_start=[[[ #assertion.equals_diff_delta_end=]]] #--------------------------------------------------------------------------- # Miscellaneous configuration #--------------------------------------------------------------------------- # Used to control what happens when you start a test and # have listeners that could overwrite existing result files # Possible values: # ASK : Ask user # APPEND : Append results to existing file # DELETE : Delete existing file and start a new file #resultcollector.action_if_file_exists=ASK # If defined, then start the mirror server on the port #mirror.server.port=8081 # ORO PatternCacheLRU size #oro.patterncache.size=1000 #TestBeanGui # #propertyEditorSearchPath=null # Turn expert mode on/off: expert mode will show expert-mode beans and properties #jmeter.expertMode=true # Max size of bytes stored in memory per SampleResult # Ensure you don't exceed max capacity of a Java Array and remember # that the higher it is, the higher JMeter will consume heap # Defaults to 0, which means no truncation #httpsampler.max_bytes_to_store_per_request=0 # Max size of buffer in bytes used when reading responses # Defaults to 64k #httpsampler.max_buffer_size=66560 # Maximum redirects to follow in a single sequence (default 20) #httpsampler.max_redirects=20 # Maximum frame/iframe nesting depth (default 5) #httpsampler.max_frame_depth=5 # Revert to BUG 51939 behaviour (no separate container for embedded resources) by setting the following false: #httpsampler.separate.container=true # If embedded resources download fails due to missing resources or other reasons, if this property is true # Parent sample will not be marked as failed #httpsampler.ignore_failed_embedded_resources=false #keep alive time for the parallel download threads (in seconds) #httpsampler.parallel_download_thread_keepalive_inseconds=60 # Don't keep the embedded resources response data : just keep the size and the md5 # default to false #httpsampler.embedded_resources_use_md5=false # List of extra HTTP methods that should be available in select box #httpsampler.user_defined_methods=VERSION-CONTROL,REPORT,CHECKOUT,CHECKIN,UNCHECKOUT,MKWORKSPACE,UPDATE,LABEL,MERGE,BASELINE-CONTROL,MKACTIVITY # The encoding to be used if none is provided (default ISO-8859-1) #sampleresult.default.encoding=ISO-8859-1 # CookieManager behaviour - should cookies with null/empty values be deleted? # Default is true. Use false to revert to original behaviour #CookieManager.delete_null_cookies=true # CookieManager behaviour - should variable cookies be allowed? # Default is true. Use false to revert to original behaviour #CookieManager.allow_variable_cookies=true # CookieManager behaviour - should Cookies be stored as variables? # Default is false #CookieManager.save.cookies=false # CookieManager behaviour - prefix to add to cookie name before storing it as a variable # Default is COOKIE_; to remove the prefix, define it as one or more spaces #CookieManager.name.prefix= # CookieManager behaviour - check received cookies are valid before storing them? # Default is true. Use false to revert to previous behaviour #CookieManager.check.cookies=true # Netscape HTTP Cookie file cookies=cookies # Ability to switch to Nashorn as default Javascript Engine used by IfController and __javaScript function # JMeter works as following: # - JDK >= 8 and javascript.use_rhino=false or not set : Nashorn # - JDK >= 8 and javascript.use_rhino=true: Rhino # If you want to use Rhino on JDK8, set this property to true #javascript.use_rhino=false # Number of milliseconds to wait for a thread to stop #jmeterengine.threadstop.wait=5000 #Whether to invoke System.exit(0) in server exit code after stopping RMI #jmeterengine.remote.system.exit=false # Whether to call System.exit(1) on failure to stop threads in non-GUI mode. # This only takes effect if the test was explicitly requested to stop. # If this is disabled, it may be necessary to kill the JVM externally #jmeterengine.stopfail.system.exit=true # Whether to force call System.exit(0) at end of test in non-GUI mode, even if # there were no failures and the test was not explicitly asked to stop. # Without this, the JVM may never exit if there are other threads spawned by # the test which never exit. #jmeterengine.force.system.exit=false # How long to pause (in ms) in the daemon thread before reporting that the JVM has failed to exit. # If the value is <= 0, the JMeter does not start the daemon thread #jmeter.exit.check.pause=2000 # If running non-GUI, then JMeter listens on the following port for a shutdown message. # To disable, set the port to 1000 or less. #jmeterengine.nongui.port=4445 # # If the initial port is busy, keep trying until this port is reached # (to disable searching, set the value less than or equal to the .port property) #jmeterengine.nongui.maxport=4455 # How often to check for shutdown during ramp-up (milliseconds) #jmeterthread.rampup.granularity=1000 #Should JMeter expand the tree when loading a test plan? # default value is false since JMeter 2.7 #onload.expandtree=false #JSyntaxTextArea configuration #jsyntaxtextarea.wrapstyleword=true #jsyntaxtextarea.linewrap=true #jsyntaxtextarea.codefolding=true # Set 0 to disable undo feature in JSyntaxTextArea #jsyntaxtextarea.maxundos=50 # Change the font on the (JSyntax) Text Areas. (Useful for HiDPI screens) #jsyntaxtextarea.font.family=Hack #jsyntaxtextarea.font.size=14 # Set this to false to disable the use of JSyntaxTextArea for the Console Logger panel #loggerpanel.usejsyntaxtext=true # Maximum size of HTML page that can be displayed; default=10 mbytes # Set to 0 to disable the size check and display the whole response #view.results.tree.max_size=10485760 # Order of Renderers in View Results Tree # Note full class names should be used for non jmeter core renderers # For JMeter core renderers, class names start with . and are automatically # prefixed with org.apache.jmeter.visualizers view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundaryExtractor,.RenderAsCssJQuery,.RenderAsXPath,org.apache.jmeter.extractor.json.render.RenderAsJsonRenderer,.RenderAsHTML,.RenderAsHTMLFormatted,.RenderAsHTMLWithEmbedded,.RenderAsDocument,.RenderAsJSON,.RenderAsXML # Maximum number of results in the results tree # Set to 0 to store all results (might consume a lot of memory) #view.results.tree.max_results=500 # Maximum size of Document that can be parsed by Tika engine; defaut=10 * 1024 * 1024 (10MB) # Set to 0 to disable the size check #document.max_size=0 #JMS options # Enable the following property to stop JMS Point-to-Point Sampler from using # the properties java.naming.security.[principal|credentials] when creating the queue connection #JMSSampler.useSecurity.properties=false # Set the following value to true in order to skip the delete confirmation dialogue #confirm.delete.skip=false # Used by JSR223 elements # Size of compiled scripts cache #jsr223.compiled_scripts_cache_size=100 #--------------------------------------------------------------------------- # Classpath configuration #--------------------------------------------------------------------------- # List of directories (separated by ;) to search for additional JMeter plugin classes, # for example new GUI elements and samplers. # Any jar file in such a directory will be automatically included, # jar files in sub directories are ignored. # The given value is in addition to any jars found in the lib/ext directory. # Do not use this for utility or plugin dependency jars. #search_paths=/app1/lib;/app2/lib # List of directories that JMeter will search for utility and plugin dependency classes. # Use your platform path separator to separate multiple paths. # Any jar file in such a directory will be automatically included, # jar files in sub directories are ignored. # The given value is in addition to any jars found in the lib directory. # All entries will be added to the class path of the system class loader # and also to the path of the JMeter internal loader. # Paths with spaces may cause problems for the JVM #user.classpath=../classes;../lib # List of directories (separated by ;) that JMeter will search for utility # and plugin dependency classes. # Any jar file in such a directory will be automatically included, # jar files in sub directories are ignored. # The given value is in addition to any jars found in the lib directory # or given by the user.classpath property. # All entries will be added to the path of the JMeter internal loader only. # For plugin dependencies this property should be used instead of user.classpath. #plugin_dependency_paths=../dependencies/lib;../app1/;../app2/ # Classpath finder # ================ # The classpath finder currently needs to load every single JMeter class to find # the classes it needs. # For non-GUI mode, it's only necessary to scan for Function classes, but all classes # are still loaded. # All current Function classes include ".function." in their name, # and none include ".gui." in the name, so the number of unwanted classes loaded can be # reduced by checking for these. However, if a valid function class name does not match # these restrictions, it will not be loaded. If problems are encountered, then comment # or change the following properties: classfinder.functions.contain=.functions. classfinder.functions.notContain=.gui. #--------------------------------------------------------------------------- # Additional property files to load #--------------------------------------------------------------------------- # Should JMeter automatically load additional JMeter properties? # File name to look for (comment to disable) user.properties=user.properties # Should JMeter automatically load additional system properties? # File name to look for (comment to disable) system.properties=system.properties # Comma separated list of files that contain reference to templates and their description # Path must be relative to JMeter root folder #template.files=/bin/templates/templates.xml #--------------------------------------------------------------------------- # Thread Group Validation feature #--------------------------------------------------------------------------- # Validation is the name of the feature used to rapidly validate a Thread Group runs fine # Default implementation is org.apache.jmeter.gui.action.validation.TreeClonerForValidation # It runs validation without timers, with 1 thread, 1 iteration and Startup Delay set to 0 # You can implement your own policy that must extend org.apache.jmeter.engine.TreeCloner # JMeter will instantiate it and use it to create the Tree used to run validation on Thread Group #testplan_validation.tree_cloner_class=org.apache.jmeter.validation.ComponentTreeClonerForValidation # Number of threads to use to validate a Thread Group #testplan_validation.nb_threads_per_thread_group=1 # Ignore BackendListener when validating the thread group of plan #testplan_validation.ignore_backends=true # Ignore timers when validating the thread group of plan #testplan_validation.ignore_timers=true # Number of iterations to use to validate a Thread Group #testplan_validation.number_iterations=1 # Force throuput controllers that work in percentage mode to be a 100% # Disabled by default #testplan_validation.tpc_force_100_pct=false #--------------------------------------------------------------------------- # Think Time configuration #--------------------------------------------------------------------------- # # Apply a factor on computed pauses by the following Timers: # - Gaussian Random Timer # - Uniform Random Timer # - Poisson Random Timer # #timer.factor=1.0f # Default implementation that create the Timer structure to add to Test Plan # Implementation of interface org.apache.jmeter.gui.action.thinktime.ThinkTimeCreator #think_time_creator.impl=org.apache.jmeter.thinktime.DefaultThinkTimeCreator # Default Timer GUI class added to Test Plan by DefaultThinkTimeCreator #think_time_creator.default_timer_implementation=org.apache.jmeter.timers.gui.UniformRandomTimerGui # Default constant pause of Timer #think_time_creator.default_constant_pause=1000 # Default range pause of Timer #think_time_creator.default_range=100 # Change this parameter if you want to override the APDEX satisfaction threshold. jmeter.reportgenerator.apdex_satisfied_threshold=500 # Change this parameter if you want to override the APDEX tolerance threshold. jmeter.reportgenerator.apdex_tolerated_threshold=1500 #--------------------------------------------------------------------------- # Naming Policy configuration #--------------------------------------------------------------------------- # Prefix used when naming elements #naming_policy.prefix= # Suffix used when naming elements #naming_policy.suffix= # Implementation of interface org.apache.jmeter.gui.action.TreeNodeNamingPolicy #naming_policy.impl=org.apache.jmeter.gui.action.impl.DefaultTreeNodeNamingPolicy #--------------------------------------------------------------------------- # Help Documentation #--------------------------------------------------------------------------- # Switch that allows using Local documentation opened in JMeter GUI # By default we use Online documentation opened in Browser #help.local=false ================================================ FILE: src/test/resources/bin/saveservice.properties ================================================ #--------------------------------------------------------- # SAVESERVICE PROPERTIES - JMETER INTERNAL USE ONLY #--------------------------------------------------------- ## Licensed to the Apache Software Foundation (ASF) under one or more ## contributor license agreements. See the NOTICE file distributed with ## this work for additional information regarding copyright ownership. ## The ASF licenses this file to You under the Apache License, Version 2.0 ## (the "License"); you may not use this file except in compliance with ## the License. You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. # This file is used to define how XStream (de-)serializes classnames # in JMX test plan files. # FOR JMETER INTERNAL USE ONLY #--------------------------------------------------------- # N.B. To ensure backward compatibility, please do NOT change or delete any entries # New entries can be added as necessary. # # Note that keys starting with an underscore are special, # and are not used as aliases. # # Please keep the entries in alphabetical order within the sections # to reduce the likelihood of duplicates # # version number of this file is now computed by a sha1 sum, so no need for # an explicit _file_version property anymore. # # For this sha1 sum we ignore every newline character. It can be computed # by the following command: # # cat bin/saveservice.properties | perl -ne 'chomp; print' | sha1sum # # Be aware, that every change in this file will change the sha1 sum! # # Conversion version (for JMX output files) # Must be updated if the file has been changed since the previous release # Format is: # Save service version=JMeter version at which change occurred # 1.7 = 2.1.1 # 1.8 = 2.1.2 # (Some version updates were missed here...) # 2.0 = 2.3.1 # 2.1 = 2.3.2 # (Some version updates were missed here...) # 2.2 = 2.6 # 2.3 = 2.7 # 2.4 = 2.9 # 2.5 = 2.10 # 2.6 = 2.11 # 2.7 = 2.12 # 2.8 = 2.13 # 2.9 = 2.14 # 3.1 = 3.1 # 3.2 = 3.2 # 3.4 = 3.4 _version=4.0 # # # Character set encoding used to read and write JMeter XML files and CSV results # _file_encoding=UTF-8 # #--------------------------------------------------------- # # The following properties are used to create aliases # [Must all start with capital letter] # AccessLogSampler=org.apache.jmeter.protocol.http.sampler.AccessLogSampler AjpSampler=org.apache.jmeter.protocol.http.sampler.AjpSampler AjpSamplerGui=org.apache.jmeter.protocol.http.control.gui.AjpSamplerGui AnchorModifier=org.apache.jmeter.protocol.http.modifier.AnchorModifier AnchorModifierGui=org.apache.jmeter.protocol.http.modifier.gui.AnchorModifierGui Argument=org.apache.jmeter.config.Argument Arguments=org.apache.jmeter.config.Arguments ArgumentsPanel=org.apache.jmeter.config.gui.ArgumentsPanel AssertionGui=org.apache.jmeter.assertions.gui.AssertionGui AssertionVisualizer=org.apache.jmeter.visualizers.AssertionVisualizer AuthManager=org.apache.jmeter.protocol.http.control.AuthManager Authorization=org.apache.jmeter.protocol.http.control.Authorization AuthPanel=org.apache.jmeter.protocol.http.gui.AuthPanel BackendListener=org.apache.jmeter.visualizers.backend.BackendListener BackendListenerGui=org.apache.jmeter.visualizers.backend.BackendListenerGui BeanShellAssertion=org.apache.jmeter.assertions.BeanShellAssertion BeanShellAssertionGui=org.apache.jmeter.assertions.gui.BeanShellAssertionGui BeanShellListener=org.apache.jmeter.visualizers.BeanShellListener BeanShellPostProcessor=org.apache.jmeter.extractor.BeanShellPostProcessor BeanShellPreProcessor=org.apache.jmeter.modifiers.BeanShellPreProcessor BeanShellSampler=org.apache.jmeter.protocol.java.sampler.BeanShellSampler BeanShellSamplerGui=org.apache.jmeter.protocol.java.control.gui.BeanShellSamplerGui BeanShellTimer=org.apache.jmeter.timers.BeanShellTimer BoundaryExtractor=org.apache.jmeter.extractor.BoundaryExtractor BoundaryExtractorGui=org.apache.jmeter.extractor.gui.BoundaryExtractorGui BSFAssertion=org.apache.jmeter.assertions.BSFAssertion BSFListener=org.apache.jmeter.visualizers.BSFListener BSFPreProcessor=org.apache.jmeter.modifiers.BSFPreProcessor BSFPostProcessor=org.apache.jmeter.extractor.BSFPostProcessor BSFSampler=org.apache.jmeter.protocol.java.sampler.BSFSampler BSFSamplerGui=org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui BSFTimer=org.apache.jmeter.timers.BSFTimer CacheManager=org.apache.jmeter.protocol.http.control.CacheManager CacheManagerGui=org.apache.jmeter.protocol.http.gui.CacheManagerGui CompareAssertion=org.apache.jmeter.assertions.CompareAssertion ComparisonVisualizer=org.apache.jmeter.visualizers.ComparisonVisualizer ConfigTestElement=org.apache.jmeter.config.ConfigTestElement ConstantThroughputTimer=org.apache.jmeter.timers.ConstantThroughputTimer ConstantTimer=org.apache.jmeter.timers.ConstantTimer ConstantTimerGui=org.apache.jmeter.timers.gui.ConstantTimerGui Cookie=org.apache.jmeter.protocol.http.control.Cookie CookieManager=org.apache.jmeter.protocol.http.control.CookieManager CookiePanel=org.apache.jmeter.protocol.http.gui.CookiePanel CounterConfig=org.apache.jmeter.modifiers.CounterConfig CriticalSectionController=org.apache.jmeter.control.CriticalSectionController CriticalSectionControllerGui=org.apache.jmeter.control.gui.CriticalSectionControllerGui CounterConfigGui=org.apache.jmeter.modifiers.gui.CounterConfigGui CSVDataSet=org.apache.jmeter.config.CSVDataSet DebugPostProcessor=org.apache.jmeter.extractor.DebugPostProcessor DebugSampler=org.apache.jmeter.sampler.DebugSampler # removed in 3.1, class was deleted in r1763837 DistributionGraphVisualizer=org.apache.jmeter.visualizers.DistributionGraphVisualizer DNSCacheManager=org.apache.jmeter.protocol.http.control.DNSCacheManager DNSCachePanel=org.apache.jmeter.protocol.http.gui.DNSCachePanel DurationAssertion=org.apache.jmeter.assertions.DurationAssertion DurationAssertionGui=org.apache.jmeter.assertions.gui.DurationAssertionGui PreciseThroughputTimer=org.apache.jmeter.timers.poissonarrivals.PreciseThroughputTimer # Should really have been defined as floatProp to agree with other properties # No point changing this now FloatProperty=org.apache.jmeter.testelement.property.FloatProperty ForeachController=org.apache.jmeter.control.ForeachController ForeachControlPanel=org.apache.jmeter.control.gui.ForeachControlPanel FtpConfigGui=org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui FTPSampler=org.apache.jmeter.protocol.ftp.sampler.FTPSampler FtpTestSamplerGui=org.apache.jmeter.protocol.ftp.control.gui.FtpTestSamplerGui GaussianRandomTimer=org.apache.jmeter.timers.GaussianRandomTimer GaussianRandomTimerGui=org.apache.jmeter.timers.gui.GaussianRandomTimerGui GenericController=org.apache.jmeter.control.GenericController GraphAccumVisualizer=org.apache.jmeter.visualizers.GraphAccumVisualizer GraphVisualizer=org.apache.jmeter.visualizers.GraphVisualizer Header=org.apache.jmeter.protocol.http.control.Header HeaderManager=org.apache.jmeter.protocol.http.control.HeaderManager HeaderPanel=org.apache.jmeter.protocol.http.gui.HeaderPanel HTMLAssertion=org.apache.jmeter.assertions.HTMLAssertion HTMLAssertionGui=org.apache.jmeter.assertions.gui.HTMLAssertionGui HTTPArgument=org.apache.jmeter.protocol.http.util.HTTPArgument HTTPArgumentsPanel=org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel HTTPFileArg=org.apache.jmeter.protocol.http.util.HTTPFileArg HTTPFileArgs=org.apache.jmeter.protocol.http.util.HTTPFileArgs HttpDefaultsGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui HtmlExtractor=org.apache.jmeter.extractor.HtmlExtractor HtmlExtractorGui=org.apache.jmeter.extractor.gui.HtmlExtractorGui # removed in r1039684, probably not released. Not present in r322831 or since. #HttpGenericSampler=org.apache.jmeter.protocol.http.sampler.HttpGenericSampler # removed in r1039684, probably not released. Not present in r322831 or since. #HttpGenericSamplerGui=org.apache.jmeter.protocol.http.control.gui.HttpGenericSamplerGui HttpMirrorControl=org.apache.jmeter.protocol.http.control.HttpMirrorControl HttpMirrorControlGui=org.apache.jmeter.protocol.http.control.gui.HttpMirrorControlGui # r397955 - removed test class. Keep as commented entry for info only. #HTTPNullSampler=org.apache.jmeter.protocol.http.sampler.HTTPNullSampler # Merge previous 2 HTTP samplers into one HTTPSampler_=org.apache.jmeter.protocol.http.sampler.HTTPSampler HTTPSampler2_=org.apache.jmeter.protocol.http.sampler.HTTPSampler2 HTTPSamplerProxy,HTTPSampler,HTTPSampler2=org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy # Merge GUIs HttpTestSampleGui,HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui #HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui2 IfController=org.apache.jmeter.control.IfController IfControllerPanel=org.apache.jmeter.control.gui.IfControllerPanel IncludeController=org.apache.jmeter.control.IncludeController IncludeControllerGui=org.apache.jmeter.control.gui.IncludeControllerGui InterleaveControl=org.apache.jmeter.control.InterleaveControl InterleaveControlGui=org.apache.jmeter.control.gui.InterleaveControlGui JavaConfig=org.apache.jmeter.protocol.java.config.JavaConfig JavaConfigGui=org.apache.jmeter.protocol.java.config.gui.JavaConfigGui JavaSampler=org.apache.jmeter.protocol.java.sampler.JavaSampler JavaTest=org.apache.jmeter.protocol.java.test.JavaTest JavaTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JavaTestSamplerGui JDBCDataSource=org.apache.jmeter.protocol.jdbc.config.DataSourceElement JDBCPostProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPostProcessor JDBCPreProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPreProcessor JDBCSampler=org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler # Renamed to JMSSamplerGui; keep original entry for backwards compatibility JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui JMSProperties=org.apache.jmeter.protocol.jms.sampler.JMSProperties JMSProperty=org.apache.jmeter.protocol.jms.sampler.JMSProperty JMSPublisherGui=org.apache.jmeter.protocol.jms.control.gui.JMSPublisherGui JMSSampler=org.apache.jmeter.protocol.jms.sampler.JMSSampler JMSSamplerGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui JMSSubscriberGui=org.apache.jmeter.protocol.jms.control.gui.JMSSubscriberGui JSONPathAssertion=org.apache.jmeter.assertions.JSONPathAssertion JSONPathAssertionGui=org.apache.jmeter.assertions.gui.JSONPathAssertionGui JSONPostProcessor=org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor JSONPostProcessorGui=org.apache.jmeter.extractor.json.jsonpath.gui.JSONPostProcessorGui # Removed in r545311 as Jndi no longer present; keep for compat. JndiDefaultsGui=org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui JSR223Assertion=org.apache.jmeter.assertions.JSR223Assertion JSR223Listener=org.apache.jmeter.visualizers.JSR223Listener JSR223PostProcessor=org.apache.jmeter.extractor.JSR223PostProcessor JSR223PreProcessor=org.apache.jmeter.modifiers.JSR223PreProcessor JSR223Sampler=org.apache.jmeter.protocol.java.sampler.JSR223Sampler JSR223Timer=org.apache.jmeter.timers.JSR223Timer JUnitSampler=org.apache.jmeter.protocol.java.sampler.JUnitSampler JUnitTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JUnitTestSamplerGui KeystoreConfig=org.apache.jmeter.config.KeystoreConfig LDAPArgument=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgument LDAPArguments=org.apache.jmeter.protocol.ldap.config.gui.LDAPArguments LDAPArgumentsPanel=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel LdapConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui LdapExtConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui LDAPExtSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler LdapExtTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapExtTestSamplerGui LDAPSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPSampler LdapTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapTestSamplerGui LogicControllerGui=org.apache.jmeter.control.gui.LogicControllerGui LoginConfig=org.apache.jmeter.config.LoginConfig LoginConfigGui=org.apache.jmeter.config.gui.LoginConfigGui LoopController=org.apache.jmeter.control.LoopController LoopControlPanel=org.apache.jmeter.control.gui.LoopControlPanel MailerModel=org.apache.jmeter.reporters.MailerModel MailerResultCollector=org.apache.jmeter.reporters.MailerResultCollector MailerVisualizer=org.apache.jmeter.visualizers.MailerVisualizer MailReaderSampler=org.apache.jmeter.protocol.mail.sampler.MailReaderSampler MailReaderSamplerGui=org.apache.jmeter.protocol.mail.sampler.gui.MailReaderSamplerGui MD5HexAssertion=org.apache.jmeter.assertions.MD5HexAssertion MD5HexAssertionGUI=org.apache.jmeter.assertions.gui.MD5HexAssertionGUI ModuleController=org.apache.jmeter.control.ModuleController ModuleControllerGui=org.apache.jmeter.control.gui.ModuleControllerGui MongoScriptSampler=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler MongoSourceElement=org.apache.jmeter.protocol.mongodb.config.MongoSourceElement # removed in 3.2, class was deleted in r MonitorHealthVisualizer=org.apache.jmeter.visualizers.MonitorHealthVisualizer NamePanel=org.apache.jmeter.gui.NamePanel ObsoleteGui=org.apache.jmeter.config.gui.ObsoleteGui OnceOnlyController=org.apache.jmeter.control.OnceOnlyController OnceOnlyControllerGui=org.apache.jmeter.control.gui.OnceOnlyControllerGui # removed in 3.0, class was deleted in r1722962 ParamMask=org.apache.jmeter.protocol.http.modifier.ParamMask # removed in 3.0, class was deleted in r1722757 ParamModifier=org.apache.jmeter.protocol.http.modifier.ParamModifier # removed in 3.0, class was deleted in r1722757 ParamModifierGui=org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui PoissonRandomTimer=org.apache.jmeter.timers.PoissonRandomTimer PoissonRandomTimerGui=org.apache.jmeter.timers.gui.PoissonRandomTimerGui PropertyControlGui=org.apache.jmeter.visualizers.PropertyControlGui ProxyControl=org.apache.jmeter.protocol.http.proxy.ProxyControl ProxyControlGui=org.apache.jmeter.protocol.http.proxy.gui.ProxyControlGui PublisherSampler=org.apache.jmeter.protocol.jms.sampler.PublisherSampler RandomControlGui=org.apache.jmeter.control.gui.RandomControlGui RandomController=org.apache.jmeter.control.RandomController RandomOrderController=org.apache.jmeter.control.RandomOrderController RandomOrderControllerGui=org.apache.jmeter.control.gui.RandomOrderControllerGui RandomVariableConfig=org.apache.jmeter.config.RandomVariableConfig RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController RecordingController=org.apache.jmeter.protocol.http.control.RecordingController # removed in r1039684, class was deleted in r580452 ReflectionThreadGroup=org.apache.jmeter.threads.ReflectionThreadGroup RegexExtractor=org.apache.jmeter.extractor.RegexExtractor RegexExtractorGui=org.apache.jmeter.extractor.gui.RegexExtractorGui RegExUserParameters=org.apache.jmeter.protocol.http.modifier.RegExUserParameters RegExUserParametersGui=org.apache.jmeter.protocol.http.modifier.gui.RegExUserParametersGui RemoteListenerWrapper=org.apache.jmeter.samplers.RemoteListenerWrapper RemoteSampleListenerWrapper=org.apache.jmeter.samplers.RemoteSampleListenerWrapper RemoteTestListenerWrapper=org.apache.jmeter.samplers.RemoteTestListenerWrapper RemoteThreadsListenerWrapper=org.apache.jmeter.threads.RemoteThreadsListenerWrapper ResponseAssertion=org.apache.jmeter.assertions.ResponseAssertion RespTimeGraphVisualizer=org.apache.jmeter.visualizers.RespTimeGraphVisualizer ResultAction=org.apache.jmeter.reporters.ResultAction ResultActionGui=org.apache.jmeter.reporters.gui.ResultActionGui ResultCollector=org.apache.jmeter.reporters.ResultCollector ResultSaver=org.apache.jmeter.reporters.ResultSaver ResultSaverGui=org.apache.jmeter.reporters.gui.ResultSaverGui RunTime=org.apache.jmeter.control.RunTime RunTimeGui=org.apache.jmeter.control.gui.RunTimeGui SampleSaveConfiguration=org.apache.jmeter.samplers.SampleSaveConfiguration SampleTimeout=org.apache.jmeter.modifiers.SampleTimeout SampleTimeoutGui=org.apache.jmeter.modifiers.gui.SampleTimeoutGui SimpleConfigGui=org.apache.jmeter.config.gui.SimpleConfigGui SimpleDataWriter=org.apache.jmeter.visualizers.SimpleDataWriter SizeAssertion=org.apache.jmeter.assertions.SizeAssertion SizeAssertionGui=org.apache.jmeter.assertions.gui.SizeAssertionGui SMIMEAssertion=org.apache.jmeter.assertions.SMIMEAssertionTestElement SMIMEAssertionGui=org.apache.jmeter.assertions.gui.SMIMEAssertionGui SmtpSampler=org.apache.jmeter.protocol.smtp.sampler.SmtpSampler SmtpSamplerGui=org.apache.jmeter.protocol.smtp.sampler.gui.SmtpSamplerGui # removed in 3.2, class was deleted in r SoapSampler=org.apache.jmeter.protocol.http.sampler.SoapSampler # removed in 3.2, class was deleted in r SoapSamplerGui=org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui # removed in 3.1, class was deleted in r1763837 SplineVisualizer=org.apache.jmeter.visualizers.SplineVisualizer # Originally deleted in r397955 as class is obsolete; needed for compat. SqlConfigGui=org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui StaticHost=org.apache.jmeter.protocol.http.control.StaticHost StatGraphVisualizer=org.apache.jmeter.visualizers.StatGraphVisualizer StatVisualizer=org.apache.jmeter.visualizers.StatVisualizer SubscriberSampler=org.apache.jmeter.protocol.jms.sampler.SubscriberSampler SubstitutionElement=org.apache.jmeter.assertions.SubstitutionElement Summariser=org.apache.jmeter.reporters.Summariser SummariserGui=org.apache.jmeter.reporters.gui.SummariserGui SummaryReport=org.apache.jmeter.visualizers.SummaryReport SwitchController=org.apache.jmeter.control.SwitchController SwitchControllerGui=org.apache.jmeter.control.gui.SwitchControllerGui SyncTimer=org.apache.jmeter.timers.SyncTimer SystemSampler=org.apache.jmeter.protocol.system.SystemSampler SystemSamplerGui=org.apache.jmeter.protocol.system.gui.SystemSamplerGui TableVisualizer=org.apache.jmeter.visualizers.TableVisualizer TCPConfigGui=org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui TCPSampler=org.apache.jmeter.protocol.tcp.sampler.TCPSampler TCPSamplerGui=org.apache.jmeter.protocol.tcp.control.gui.TCPSamplerGui TestAction=org.apache.jmeter.sampler.TestAction TestActionGui=org.apache.jmeter.sampler.gui.TestActionGui TestBeanGUI=org.apache.jmeter.testbeans.gui.TestBeanGUI TestFragmentController=org.apache.jmeter.control.TestFragmentController TestFragmentControllerGui=org.apache.jmeter.control.gui.TestFragmentControllerGui TestPlan=org.apache.jmeter.testelement.TestPlan TestPlanGui=org.apache.jmeter.control.gui.TestPlanGui ThreadGroup=org.apache.jmeter.threads.ThreadGroup ThreadGroupGui=org.apache.jmeter.threads.gui.ThreadGroupGui PostThreadGroup=org.apache.jmeter.threads.PostThreadGroup PostThreadGroupGui=org.apache.jmeter.threads.gui.PostThreadGroupGui SetupThreadGroup=org.apache.jmeter.threads.SetupThreadGroup SetupThreadGroupGui=org.apache.jmeter.threads.gui.SetupThreadGroupGui ThroughputController=org.apache.jmeter.control.ThroughputController ThroughputControllerGui=org.apache.jmeter.control.gui.ThroughputControllerGui TransactionController=org.apache.jmeter.control.TransactionController TransactionControllerGui=org.apache.jmeter.control.gui.TransactionControllerGui TransactionSampler=org.apache.jmeter.control.TransactionSampler UniformRandomTimer=org.apache.jmeter.timers.UniformRandomTimer UniformRandomTimerGui=org.apache.jmeter.timers.gui.UniformRandomTimerGui URLRewritingModifier=org.apache.jmeter.protocol.http.modifier.URLRewritingModifier URLRewritingModifierGui=org.apache.jmeter.protocol.http.modifier.gui.URLRewritingModifierGui UserParameterModifier=org.apache.jmeter.protocol.http.modifier.UserParameterModifier UserParameterModifierGui=org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui UserParameters=org.apache.jmeter.modifiers.UserParameters UserParametersGui=org.apache.jmeter.modifiers.gui.UserParametersGui ViewResultsFullVisualizer=org.apache.jmeter.visualizers.ViewResultsFullVisualizer # removed in 3.0, class was deleted in r1722757 WebServiceSampler=org.apache.jmeter.protocol.http.sampler.WebServiceSampler # removed in 3.0, class was deleted in r1722757 WebServiceSamplerGui=org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui WhileController=org.apache.jmeter.control.WhileController WhileControllerGui=org.apache.jmeter.control.gui.WhileControllerGui WorkBench=org.apache.jmeter.testelement.WorkBench WorkBenchGui=org.apache.jmeter.control.gui.WorkBenchGui XMLAssertion=org.apache.jmeter.assertions.XMLAssertion XMLAssertionGui=org.apache.jmeter.assertions.gui.XMLAssertionGui XMLSchemaAssertion=org.apache.jmeter.assertions.XMLSchemaAssertion XMLSchemaAssertionGUI=org.apache.jmeter.assertions.gui.XMLSchemaAssertionGUI XPathAssertion=org.apache.jmeter.assertions.XPathAssertion XPathAssertionGui=org.apache.jmeter.assertions.gui.XPathAssertionGui XPathExtractor=org.apache.jmeter.extractor.XPathExtractor XPathExtractorGui=org.apache.jmeter.extractor.gui.XPathExtractorGui # # Properties - all start with lower case letter and end with Prop # boolProp=org.apache.jmeter.testelement.property.BooleanProperty collectionProp=org.apache.jmeter.testelement.property.CollectionProperty doubleProp=org.apache.jmeter.testelement.property.DoubleProperty elementProp=org.apache.jmeter.testelement.property.TestElementProperty # see above - already defined as FloatProperty #floatProp=org.apache.jmeter.testelement.property.FloatProperty intProp=org.apache.jmeter.testelement.property.IntegerProperty longProp=org.apache.jmeter.testelement.property.LongProperty mapProp=org.apache.jmeter.testelement.property.MapProperty objProp=org.apache.jmeter.testelement.property.ObjectProperty stringProp=org.apache.jmeter.testelement.property.StringProperty # # Other - must start with a lower case letter (and not end with Prop) # (otherwise they could clash with the initial set of aliases) # hashTree=org.apache.jorphan.collections.ListedHashTree jmeterTestPlan=org.apache.jmeter.save.ScriptWrapper sample=org.apache.jmeter.samplers.SampleResult httpSample=org.apache.jmeter.protocol.http.sampler.HTTPSampleResult statSample=org.apache.jmeter.samplers.StatisticalSampleResult testResults=org.apache.jmeter.save.TestResultWrapper assertionResult=org.apache.jmeter.assertions.AssertionResult # removed in 3.2, class was deleted in r monitorStats=org.apache.jmeter.visualizers.MonitorStats sampleEvent=org.apache.jmeter.samplers.SampleEvent # # Converters to register. Must start line with '_' # If the converter is a collection of subitems, set equal to "collection" # If the converter needs to know the class mappings but is not a collection of # subitems, set it equal to "mapping" #_org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseConverter=collection #_org.apache.jmeter.protocol.http.util.HTTPResultConverter=collection _org.apache.jmeter.save.converters.BooleanPropertyConverter= _org.apache.jmeter.save.converters.IntegerPropertyConverter= _org.apache.jmeter.save.converters.LongPropertyConverter= _org.apache.jmeter.save.converters.MultiPropertyConverter=collection _org.apache.jmeter.save.converters.SampleEventConverter= _org.apache.jmeter.save.converters.SampleResultConverter=collection _org.apache.jmeter.save.converters.SampleSaveConfigurationConverter=collection _org.apache.jmeter.save.converters.StringPropertyConverter= _org.apache.jmeter.save.converters.HashTreeConverter=collection _org.apache.jmeter.save.converters.TestElementConverter=collection _org.apache.jmeter.save.converters.TestElementPropertyConverter=collection _org.apache.jmeter.save.converters.TestResultWrapperConverter=collection _org.apache.jmeter.save.ScriptWrapperConverter=mapping # # Remember to update the _version entry # ================================================ FILE: src/test/resources/bin/upgrade.properties ================================================ # Class, property and value upgrade equivalences. ## Licensed to the Apache Software Foundation (ASF) under one or more ## contributor license agreements. See the NOTICE file distributed with ## this work for additional information regarding copyright ownership. ## The ASF licenses this file to You under the Apache License, Version 2.0 ## (the "License"); you may not use this file except in compliance with ## the License. You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. # # Format is as follows -- # for renamed test element & GUI classes: # old.class.Name=new.class.Name # old.class.Name|guiClassName=new.class.Name # (e.g. for ConfigTestElement) # # for renamed / deleted properties: # class.Name/Old.propertyName=newPropertyName # if newPropertyName is omitted, then property is deleted # # for renamed values: # old.class.Name.old.propertyName/oldValue=newValue # org.apache.jmeter.protocol.http.config.gui.UrlConfigGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui org.apache.jmeter.assertions.Assertion=org.apache.jmeter.assertions.ResponseAssertion org.apache.jmeter.protocol.http.sampler.HTTPSamplerFull=org.apache.jmeter.protocol.http.sampler.HTTPSampler org.apache.jmeter.control.gui.RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController org.apache.jmeter.timers.gui.ConstantThroughputTimerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI org.apache.jmeter.timers.ConstantThroughputTimer/ConstantThroughputTimer.throughput=throughput org.apache.jmeter.protocol.jdbc.control.gui.JdbcTestSampleGui=org.apache.jmeter.testbeans.gui.TestBeanGUI org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler/JDBCSampler.query=query #org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler.JDBCSampler.dataSource/NULL= # Convert DBconfig org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.url=dbUrl org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.driver=driver org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.query=query org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.username=username org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.password=password # Convert PoolConfig org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connections= org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connPoolClass= org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.maxuse=poolMax # SQL Config org.apache.jmeter.config.ConfigTestElement/JDBCSampler.query=query org.apache.jmeter.protocol.http.control.Header/TestElement.name=Header.name # Upgrade AccessLogSampler org.apache.jmeter.protocol.http.control.gui.AccessLogSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.log_file=logFile org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.port=portString #Is the following used now? #org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.generator_class_name= #Looks to be a new field #filterClassName org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.domain=domain org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.parser_class_name=parserClassName org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.image_parser=imageParsing # Renamed class org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui # These classes have been deleted; there's no defined replacement org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui=org.apache.jmeter.config.gui.ObsoleteGui org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui=org.apache.jmeter.config.gui.ObsoleteGui # Should probably map to something other than ObsoleteGui... org.apache.jmeter.threads.ReflectionThreadGroup=org.apache.jmeter.config.gui.ObsoleteGui # Convert BSFSamplerGui org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.filename=filename org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.language=scriptLanguage org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.parameters=parameters org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.query=script # Obsolete Http user Parameters modifier test element # Note: ConfigTestElement is the test element associated with ObsoleteGui org.apache.jmeter.protocol.http.modifier.UserParameterModifier=org.apache.jmeter.config.ConfigTestElement org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui=org.apache.jmeter.config.gui.ObsoleteGui # Obsolete Graph Full Results listener org.apache.jmeter.visualizers.GraphAccumVisualizer=org.apache.jmeter.config.gui.ObsoleteGui # removed in 3.0, class was deleted in r1722757 org.apache.jmeter.protocol.http.sampler.WebServiceSampler=org.apache.jmeter.config.ConfigTestElement # removed in 3.0, class was deleted in r1722757 org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui # removed in 3.0, class was deleted in r1722757 org.apache.jmeter.protocol.http.modifier.ParamModifier=org.apache.jmeter.config.ConfigTestElement # removed in 3.0, class was deleted in r1722962 org.apache.jmeter.protocol.http.modifier.ParamMask=org.apache.jmeter.config.ConfigTestElement # removed in 3.0, class was deleted in r1722757 org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui=org.apache.jmeter.config.gui.ObsoleteGui # removed in 3.1, class was deleted in r1774947 org.apache.jmeter.visualizers.SplineVisualizer=org.apache.jmeter.config.gui.ObsoleteGui # removed in 3.1 class was deleted in r1763837 org.apache.jmeter.visualizers.DistributionGraphVisualizer=org.apache.jmeter.config.gui.ObsoleteGui # removed in 3.2 class was deleted in r1771608 org.apache.jmeter.visualizers.MonitorStats=org.apache.jmeter.config.ConfigTestElement org.apache.jmeter.visualizers.MonitorHealthVisualizer=org.apache.jmeter.config.gui.ObsoleteGui # removed in 3.2 class was deleted in r1783280 org.apache.jmeter.protocol.http.sampler.HTTPSampler2=org.apache.jmeter.config.ConfigTestElement org.apache.jmeter.protocol.http.sampler.SoapSampler=org.apache.jmeter.config.ConfigTestElement org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui ================================================ FILE: src/test/resources/bin/user.properties ================================================ ================================================ FILE: src/test/resources/log4j2.xml ================================================