├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-MPL-RabbitMQ ├── Makefile ├── README.md ├── erlang.mk ├── include ├── rabbit_mgmt_metrics.hrl └── rabbit_mgmt_records.hrl ├── priv └── schema │ └── rabbitmq_management_agent.schema ├── rabbitmq-components.mk ├── src ├── Elixir.RabbitMQ.CLI.Ctl.Commands.ResetStatsDbCommand.erl ├── exometer_slide.erl ├── rabbit_mgmt_agent_app.erl ├── rabbit_mgmt_agent_config.erl ├── rabbit_mgmt_agent_sup.erl ├── rabbit_mgmt_agent_sup_sup.erl ├── rabbit_mgmt_data.erl ├── rabbit_mgmt_data_compat.erl ├── rabbit_mgmt_db_handler.erl ├── rabbit_mgmt_external_stats.erl ├── rabbit_mgmt_ff.erl ├── rabbit_mgmt_format.erl ├── rabbit_mgmt_gc.erl ├── rabbit_mgmt_metrics_collector.erl ├── rabbit_mgmt_metrics_gc.erl └── rabbit_mgmt_storage.erl └── test ├── exometer_slide_SUITE.erl ├── metrics_SUITE.erl ├── rabbit_mgmt_gc_SUITE.erl └── rabbit_mgmt_slide_SUITE.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .sw? 2 | .*.sw? 3 | *.beam 4 | *.plt 5 | /.erlang.mk/ 6 | /cover/ 7 | /deps/ 8 | /doc/ 9 | /ebin/ 10 | /escript/ 11 | /escript.lock 12 | /logs/ 13 | /plugins/ 14 | /plugins.lock 15 | /sbin/ 16 | /sbin.lock 17 | 18 | rabbitmq_management_agent.d 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # vim:sw=2:et: 2 | 3 | os: linux 4 | dist: xenial 5 | language: elixir 6 | notifications: 7 | email: 8 | recipients: 9 | - alerts@rabbitmq.com 10 | on_success: never 11 | on_failure: always 12 | addons: 13 | apt: 14 | packages: 15 | - awscli 16 | cache: 17 | apt: true 18 | env: 19 | global: 20 | - secure: SvMls+ca5j34cUph3yOaOxjOXHGtzfK00HCZgOgKQVkX5MI835MZkRn/HpmNrmuO++J6h8Rqet5v7ejVS0K41IhpRzJqRzRCUmI+/k0+MfgDrLP1KpszJQP05D22Wx46Bumyx7WKoUqr10jn75VhfeJQzsCf/2gn/+aAcs/2pRE= 21 | - secure: OxHYDgMTq4dtLKl4hh7evQeb5cZM6Cce5i/akV5ci14c4pmx2P22oyAuDXqDi84959hfspuLx3IW808vnJL9LNQzLGs7jjWX2Yzisz3u5/429efgEQh50liZczmK+c3L1wPQ+RFwY9dysm9U6/+gX/o82qH0yFkVYXHCpIgjAhc= 22 | 23 | # $base_rmq_ref is used by rabbitmq-components.mk to select the 24 | # appropriate branch for dependencies. 25 | - base_rmq_ref=master 26 | 27 | elixir: 28 | - '1.9' 29 | otp_release: 30 | - '21.3' 31 | - '22.2' 32 | 33 | install: 34 | # This project being an Erlang one (we just set language to Elixir 35 | # to ensure it is installed), we don't want Travis to run mix(1) 36 | # automatically as it will break. 37 | skip 38 | 39 | script: 40 | # $current_rmq_ref is also used by rabbitmq-components.mk to select 41 | # the appropriate branch for dependencies. 42 | - make check-rabbitmq-components.mk 43 | current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}" 44 | - make xref 45 | current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}" 46 | - make tests 47 | current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}" 48 | 49 | after_failure: 50 | - | 51 | cd "$TRAVIS_BUILD_DIR" 52 | if test -d logs && test "$AWS_ACCESS_KEY_ID" && test "$AWS_SECRET_ACCESS_KEY"; then 53 | archive_name="$(basename "$TRAVIS_REPO_SLUG")-$TRAVIS_JOB_NUMBER" 54 | 55 | tar -c --transform "s/^logs/${archive_name}/" -f - logs | \ 56 | xz > "${archive_name}.tar.xz" 57 | 58 | aws s3 cp "${archive_name}.tar.xz" s3://server-release-pipeline/travis-ci-logs/ \ 59 | --region eu-west-1 \ 60 | --acl public-read 61 | fi 62 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open 4 | and welcoming community, we pledge to respect all people who contribute through reporting 5 | issues, posting feature requests, updating documentation, submitting pull requests or 6 | patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for 9 | everyone, regardless of level of experience, gender, gender identity and expression, 10 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, 11 | religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic addresses, 20 | without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 24 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 25 | Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors 26 | that they deem inappropriate, threatening, offensive, or harmful. 27 | 28 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and 29 | consistently applying these principles to every aspect of managing this project. Project 30 | maintainers who do not follow or enforce the Code of Conduct may be permanently removed 31 | from the project team. 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an 34 | individual is representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 37 | contacting a project maintainer at [info@rabbitmq.com](mailto:info@rabbitmq.com). All complaints will 38 | be reviewed and investigated and will result in a response that is deemed necessary and 39 | appropriate to the circumstances. Maintainers are obligated to maintain confidentiality 40 | with regard to the reporter of an incident. 41 | 42 | This Code of Conduct is adapted from the 43 | [Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at 44 | [contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/) 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions. 4 | Pull requests is the primary place of discussing code changes. 5 | 6 | ## How to Contribute 7 | 8 | The process is fairly standard: 9 | 10 | * Fork the repository or repositories you plan on contributing to 11 | * Clone [RabbitMQ umbrella repository](https://github.com/rabbitmq/rabbitmq-public-umbrella) 12 | * `cd umbrella`, `make co` 13 | * Create a branch with a descriptive name in the relevant repositories 14 | * Make your changes, run tests, commit with a [descriptive message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), push to your fork 15 | * Submit pull requests with an explanation what has been changed and **why** 16 | * Submit a filled out and signed [Contributor Agreement](https://github.com/rabbitmq/ca#how-to-submit) if needed (see below) 17 | * Be patient. We will get to your pull request eventually 18 | 19 | If what you are going to work on is a substantial change, please first ask the core team 20 | of their opinion on [RabbitMQ mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). 21 | 22 | 23 | ## Code of Conduct 24 | 25 | See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). 26 | 27 | 28 | ## Contributor Agreement 29 | 30 | If you want to contribute a non-trivial change, please submit a signed copy of our 31 | [Contributor Agreement](https://github.com/rabbitmq/ca#how-to-submit) around the time 32 | you submit your pull request. This will make it much easier (in some cases, possible) 33 | for the RabbitMQ team at Pivotal to merge your contribution. 34 | 35 | 36 | ## Where to Ask Questions 37 | 38 | If something isn't clear, feel free to ask on our [mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This package is licensed under the MPL 2.0. For the MPL 2.0, please see LICENSE-MPL-RabbitMQ. 2 | 3 | If you have any questions regarding licensing, please contact us at 4 | info@rabbitmq.com. 5 | -------------------------------------------------------------------------------- /LICENSE-MPL-RabbitMQ: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = rabbitmq_management_agent 2 | PROJECT_DESCRIPTION = RabbitMQ Management Agent 3 | PROJECT_MOD = rabbit_mgmt_agent_app 4 | 5 | define PROJECT_ENV 6 | [ 7 | {rates_mode, basic}, 8 | {sample_retention_policies, 9 | %% List of {MaxAgeInSeconds, SampleEveryNSeconds} 10 | [{global, [{605, 5}, {3660, 60}, {29400, 600}, {86400, 1800}]}, 11 | {basic, [{605, 5}, {3600, 60}]}, 12 | {detailed, [{605, 5}]}]} 13 | ] 14 | endef 15 | 16 | define PROJECT_APP_EXTRA_KEYS 17 | {broker_version_requirements, []} 18 | endef 19 | 20 | DEPS = rabbit_common rabbit 21 | TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers 22 | LOCAL_DEPS += xmerl mnesia ranch ssl crypto public_key 23 | DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk 24 | DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk 25 | 26 | # FIXME: Use erlang.mk patched for RabbitMQ, while waiting for PRs to be 27 | # reviewed and merged. 28 | 29 | ERLANG_MK_REPO = https://github.com/rabbitmq/erlang.mk.git 30 | ERLANG_MK_COMMIT = rabbitmq-tmp 31 | 32 | include rabbitmq-components.mk 33 | TEST_DEPS := $(filter-out rabbitmq_test,$(TEST_DEPS)) 34 | include erlang.mk 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ Management Agent 2 | 3 | ## This was migrated to https://github.com/rabbitmq/rabbitmq-server 4 | 5 | This repository has been moved to the main unified RabbitMQ "monorepo", including all open issues. You can find the source under [/deps/rabbitmq_management_agent](https://github.com/rabbitmq/rabbitmq-server/tree/master/deps/rabbitmq_management_agent). 6 | All issues have been transferred. 7 | 8 | -------------------------------------------------------------------------------- /include/rabbit_mgmt_metrics.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% https://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is Pivotal Software, Inc. 14 | %% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved. 15 | %% 16 | 17 | -type(event_type() :: queue_stats | queue_exchange_stats | vhost_stats 18 | | channel_queue_stats | channel_stats 19 | | channel_exchange_stats | exchange_stats 20 | | node_stats | node_node_stats | connection_stats). 21 | -type(type() :: deliver_get | fine_stats | queue_msg_rates | queue_msg_counts 22 | | coarse_node_stats | coarse_node_node_stats | coarse_conn_stats 23 | | process_stats). 24 | 25 | -type(table_name() :: atom()). 26 | 27 | -define(TABLES, [{connection_stats_coarse_conn_stats, set}, 28 | {vhost_stats_coarse_conn_stats, set}, 29 | {connection_created_stats, set}, 30 | {connection_stats, set}, 31 | {channel_created_stats, set}, 32 | {channel_stats, set}, 33 | {channel_stats_fine_stats, set}, 34 | {channel_exchange_stats_fine_stats, set}, 35 | {channel_queue_stats_deliver_stats, set}, 36 | {vhost_stats_fine_stats, set}, 37 | {queue_stats_deliver_stats, set}, 38 | {vhost_stats_deliver_stats, set}, 39 | {channel_stats_deliver_stats, set}, 40 | {channel_process_stats, set}, 41 | {queue_stats_publish, set}, 42 | {queue_exchange_stats_publish, set}, 43 | {exchange_stats_publish_out, set}, 44 | {exchange_stats_publish_in, set}, 45 | {consumer_stats, set}, 46 | {queue_stats, set}, 47 | {queue_msg_stats, set}, 48 | {vhost_msg_stats, set}, 49 | {queue_process_stats, set}, 50 | {node_stats, set}, 51 | {node_coarse_stats, set}, 52 | {node_persister_stats, set}, 53 | {node_node_stats, set}, 54 | {node_node_coarse_stats, set}, 55 | {queue_msg_rates, set}, 56 | {vhost_msg_rates, set}, 57 | {connection_churn_rates, set}]). 58 | 59 | -define(INDEX_TABLES, [consumer_stats_queue_index, 60 | consumer_stats_channel_index, 61 | channel_exchange_stats_fine_stats_exchange_index, 62 | channel_exchange_stats_fine_stats_channel_index, 63 | channel_queue_stats_deliver_stats_queue_index, 64 | channel_queue_stats_deliver_stats_channel_index, 65 | queue_exchange_stats_publish_queue_index, 66 | queue_exchange_stats_publish_exchange_index, 67 | node_node_coarse_stats_node_index]). 68 | 69 | -define(GC_EVENTS, [connection_closed, channel_closed, consumer_deleted, 70 | exchange_deleted, queue_deleted, vhost_deleted, 71 | node_node_deleted, channel_consumer_deleted]). 72 | 73 | -define(DELEGATE_PREFIX, "delegate_management_"). 74 | 75 | %%------------------------------------------------------------------------------ 76 | %% Only for documentation and testing purposes, so we keep track of the number and 77 | %% order of the metrics 78 | -define(connection_stats_coarse_conn_stats(Recv_oct, Send_oct, Reductions), 79 | {Recv_oct, Send_oct, Reductions}). 80 | -define(vhost_stats_coarse_conn_stats(Recv_oct, Send_oct), {Recv_oct, Send_oct}). 81 | -define(connection_created_stats(Id, Name, Props), {Id, Name, Props}). 82 | -define(connection_stats(Id, Props), {Id, Props}). 83 | -define(channel_created_stats(Id, Name, Props), {Id, Name, Props}). 84 | -define(channel_consumer_created_stats(Queue, ChPid, ConsumerTag), 85 | {Queue, {ChPid, ConsumerTag}}). 86 | -define(channel_stats(Id, Props), {Id, Props}). 87 | -define(channel_stats_fine_stats(Publish, Confirm, ReturnUnroutable, DropUnroutable), 88 | {Publish, Confirm, ReturnUnroutable, DropUnroutable}). 89 | -define(channel_exchange_stats_fine_stats(Publish, Confirm, ReturnUnroutable, DropUnroutable), 90 | {Publish, Confirm, ReturnUnroutable, DropUnroutable}). 91 | -define(channel_queue_stats_deliver_stats(Get, Get_no_ack, Deliver, Deliver_no_ack, 92 | Redeliver, Ack, Deliver_get, Get_empty), 93 | {Get, Get_no_ack, Deliver, Deliver_no_ack, Redeliver, Ack, Deliver_get, 94 | Get_empty}). 95 | -define(vhost_stats_fine_stats(Publish, Confirm, ReturnUnroutable, DropUnroutable), 96 | {Publish, Confirm, ReturnUnroutable, DropUnroutable}). 97 | -define(queue_stats_deliver_stats(Get, Get_no_ack, Deliver, Deliver_no_ack, 98 | Redeliver, Ack, Deliver_get, Get_empty), 99 | {Get, Get_no_ack, Deliver, Deliver_no_ack, Redeliver, Ack, Deliver_get, 100 | Get_empty}). 101 | -define(vhost_stats_deliver_stats(Get, Get_no_ack, Deliver, Deliver_no_ack, 102 | Redeliver, Ack, Deliver_get, Get_empty), 103 | {Get, Get_no_ack, Deliver, Deliver_no_ack, Redeliver, Ack, Deliver_get, 104 | Get_empty}). 105 | -define(channel_stats_deliver_stats(Get, Get_no_ack, Deliver, Deliver_no_ack, 106 | Redeliver, Ack, Deliver_get, Get_empty), 107 | {Get, Get_no_ack, Deliver, Deliver_no_ack, Redeliver, Ack, Deliver_get, 108 | Get_empty}). 109 | -define(channel_process_stats(Reductions), {Reductions}). 110 | -define(queue_stats_publish(Publish), {Publish}). 111 | -define(queue_exchange_stats_publish(Publish), {Publish}). 112 | -define(exchange_stats_publish_out(Publish_out), {Publish_out}). 113 | -define(exchange_stats_publish_in(Publish_in), {Publish_in}). 114 | -define(consumer_stats(Id, Props), {Id, Props}). 115 | -define(queue_stats(Id, Props), {Id, Props}). 116 | -define(queue_msg_stats(Messages_ready, Messages_unacknowledged, Messages), 117 | {Messages_ready, Messages_unacknowledged, Messages}). 118 | -define(vhost_msg_stats(Messages_ready, Messages_unacknowledged, Messages), 119 | {Messages_ready, Messages_unacknowledged, Messages}). 120 | -define(queue_process_stats(Reductions), {Reductions}). 121 | -define(node_stats(Id, Props), {Id, Props}). 122 | -define(node_coarse_stats(Fd_used, Sockets_used, Mem_used, Disk_free, Proc_used, 123 | Gc_num, Gc_bytes_reclaimed, Context_switches), 124 | {Fd_used, Sockets_used, Mem_used, Disk_free, Proc_used, Gc_num, 125 | Gc_bytes_reclaimed, Context_switches}). 126 | -define(node_persister_stats(Io_read_count, Io_read_bytes, Io_read_avg_time, Io_write_count, 127 | Io_write_bytes, Io_write_avg_time, Io_sync_count, Io_sync_avg_time, 128 | Io_seek_count, Io_seek_avg_time, Io_reopen_count, Mnesia_ram_tx_count, 129 | Mnesia_disk_tx_count, Msg_store_read_count, Msg_store_write_count, 130 | Queue_index_journal_write_count, Queue_index_write_count, 131 | Queue_index_read_count, Io_file_handle_open_attempt_count, 132 | Io_file_handle_open_attempt_avg_time), 133 | {Io_read_count, Io_read_bytes, Io_read_avg_time, Io_write_count, Io_write_bytes, 134 | Io_write_avg_time, Io_sync_count, Io_sync_avg_time, Io_seek_count, Io_seek_avg_time, 135 | Io_reopen_count, Mnesia_ram_tx_count, Mnesia_disk_tx_count, Msg_store_read_count, 136 | Msg_store_write_count, Queue_index_journal_write_count, Queue_index_write_count, 137 | Queue_index_read_count, Io_file_handle_open_attempt_count, 138 | Io_file_handle_open_attempt_avg_time}). 139 | -define(node_node_stats(Send_bytes, Recv_bytes), {Send_bytes, Recv_bytes}). 140 | -define(node_node_coarse_stats(Send_bytes, Recv_bytes), {Send_bytes, Recv_bytes}). 141 | -define(queue_msg_rates(Disk_reads, Disk_writes), {Disk_reads, Disk_writes}). 142 | -define(vhost_msg_rates(Disk_reads, Disk_writes), {Disk_reads, Disk_writes}). 143 | -define(old_aggr_stats(Id, Stats), {Id, Stats}). 144 | -define(connection_churn_rates(Connection_created, Connection_closed, Channel_created, 145 | Channel_closed, Queue_declared, Queue_created, 146 | Queue_deleted), 147 | {Connection_created, Connection_closed, Channel_created, Channel_closed, 148 | Queue_declared, Queue_created, Queue_deleted}). 149 | 150 | -define(stats_per_table(Table), 151 | case Table of 152 | connection_stats_coarse_conn_stats -> 153 | [recv_oct, send_oct, reductions]; 154 | vhost_stats_coarse_conn_stats -> 155 | [recv_oct, send_oct]; 156 | T when T =:= channel_stats_fine_stats; 157 | T =:= channel_exchange_stats_fine_stats; 158 | T =:= vhost_stats_fine_stats -> 159 | [publish, confirm, return_unroutable, drop_unroutable]; 160 | T when T =:= channel_queue_stats_deliver_stats; 161 | T =:= queue_stats_deliver_stats; 162 | T =:= vhost_stats_deliver_stats; 163 | T =:= channel_stats_deliver_stats -> 164 | [get, get_no_ack, deliver, deliver_no_ack, redeliver, ack, deliver_get, 165 | get_empty]; 166 | T when T =:= channel_process_stats; 167 | T =:= queue_process_stats -> 168 | [reductions]; 169 | T when T =:= queue_stats_publish; 170 | T =:= queue_exchange_stats_publish -> 171 | [publish]; 172 | exchange_stats_publish_out -> 173 | [publish_out]; 174 | exchange_stats_publish_in -> 175 | [publish_in]; 176 | T when T =:= queue_msg_stats; 177 | T =:= vhost_msg_stats -> 178 | [messages_ready, messages_unacknowledged, messages]; 179 | node_coarse_stats -> 180 | [fd_used, sockets_used, mem_used, disk_free, proc_used, gc_num, 181 | gc_bytes_reclaimed, context_switches]; 182 | node_persister_stats -> 183 | [io_read_count, io_read_bytes, io_read_avg_time, io_write_count, 184 | io_write_bytes, io_write_avg_time, io_sync_count, io_sync_avg_time, 185 | io_seek_count, io_seek_avg_time, io_reopen_count, mnesia_ram_tx_count, 186 | mnesia_disk_tx_count, msg_store_read_count, msg_store_write_count, 187 | queue_index_journal_write_count, queue_index_write_count, 188 | queue_index_read_count, io_file_handle_open_attempt_count, 189 | io_file_handle_open_attempt_avg_time]; 190 | node_node_coarse_stats -> 191 | [send_bytes, recv_bytes]; 192 | T when T =:= queue_msg_rates; 193 | T =:= vhost_msg_rates -> 194 | [disk_reads, disk_writes]; 195 | T when T =:= connection_churn_rates -> 196 | [connection_created, connection_closed, channel_created, channel_closed, queue_declared, queue_created, queue_deleted] 197 | end). 198 | %%------------------------------------------------------------------------------ 199 | -------------------------------------------------------------------------------- /include/rabbit_mgmt_records.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% https://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ Management Console. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 15 | %% 16 | 17 | -record(context, {user, 18 | password = none, 19 | impl}). % storage for a context of the resource handler 20 | 21 | -record(range, {first :: integer(), 22 | last :: integer(), 23 | incr :: integer()}). 24 | 25 | 26 | -------------------------------------------------------------------------------- /priv/schema/rabbitmq_management_agent.schema: -------------------------------------------------------------------------------- 1 | %% Agent collectors won't start if metrics collection is disabled, only external stats are enabled. 2 | %% Also the management application will refuse to start if metrics collection is disabled 3 | {mapping, "management_agent.disable_metrics_collector", "rabbitmq_management_agent.disable_metrics_collector", 4 | [{datatype, {enum, [true, false]}}]}. 5 | -------------------------------------------------------------------------------- /rabbitmq-components.mk: -------------------------------------------------------------------------------- 1 | ifeq ($(.DEFAULT_GOAL),) 2 | # Define default goal to `all` because this file defines some targets 3 | # before the inclusion of erlang.mk leading to the wrong target becoming 4 | # the default. 5 | .DEFAULT_GOAL = all 6 | endif 7 | 8 | # PROJECT_VERSION defaults to: 9 | # 1. the version exported by rabbitmq-server-release; 10 | # 2. the version stored in `git-revisions.txt`, if it exists; 11 | # 3. a version based on git-describe(1), if it is a Git clone; 12 | # 4. 0.0.0 13 | 14 | PROJECT_VERSION := $(RABBITMQ_VERSION) 15 | 16 | ifeq ($(PROJECT_VERSION),) 17 | PROJECT_VERSION := $(shell \ 18 | if test -f git-revisions.txt; then \ 19 | head -n1 git-revisions.txt | \ 20 | awk '{print $$$(words $(PROJECT_DESCRIPTION) version);}'; \ 21 | else \ 22 | (git describe --dirty --abbrev=7 --tags --always --first-parent \ 23 | 2>/dev/null || echo rabbitmq_v0_0_0) | \ 24 | sed -e 's/^rabbitmq_v//' -e 's/^v//' -e 's/_/./g' -e 's/-/+/' \ 25 | -e 's/-/./g'; \ 26 | fi) 27 | endif 28 | 29 | # -------------------------------------------------------------------- 30 | # RabbitMQ components. 31 | # -------------------------------------------------------------------- 32 | 33 | # For RabbitMQ repositories, we want to checkout branches which match 34 | # the parent project. For instance, if the parent project is on a 35 | # release tag, dependencies must be on the same release tag. If the 36 | # parent project is on a topic branch, dependencies must be on the same 37 | # topic branch or fallback to `stable` or `master` whichever was the 38 | # base of the topic branch. 39 | 40 | dep_amqp_client = git_rmq rabbitmq-erlang-client $(current_rmq_ref) $(base_rmq_ref) master 41 | dep_amqp10_client = git_rmq rabbitmq-amqp1.0-client $(current_rmq_ref) $(base_rmq_ref) master 42 | dep_amqp10_common = git_rmq rabbitmq-amqp1.0-common $(current_rmq_ref) $(base_rmq_ref) master 43 | dep_rabbit = git_rmq rabbitmq-server $(current_rmq_ref) $(base_rmq_ref) master 44 | dep_rabbit_common = git_rmq rabbitmq-common $(current_rmq_ref) $(base_rmq_ref) master 45 | dep_rabbitmq_amqp1_0 = git_rmq rabbitmq-amqp1.0 $(current_rmq_ref) $(base_rmq_ref) master 46 | dep_rabbitmq_auth_backend_amqp = git_rmq rabbitmq-auth-backend-amqp $(current_rmq_ref) $(base_rmq_ref) master 47 | dep_rabbitmq_auth_backend_cache = git_rmq rabbitmq-auth-backend-cache $(current_rmq_ref) $(base_rmq_ref) master 48 | dep_rabbitmq_auth_backend_http = git_rmq rabbitmq-auth-backend-http $(current_rmq_ref) $(base_rmq_ref) master 49 | dep_rabbitmq_auth_backend_ldap = git_rmq rabbitmq-auth-backend-ldap $(current_rmq_ref) $(base_rmq_ref) master 50 | dep_rabbitmq_auth_backend_oauth2 = git_rmq rabbitmq-auth-backend-oauth2 $(current_rmq_ref) $(base_rmq_ref) master 51 | dep_rabbitmq_auth_mechanism_ssl = git_rmq rabbitmq-auth-mechanism-ssl $(current_rmq_ref) $(base_rmq_ref) master 52 | dep_rabbitmq_aws = git_rmq rabbitmq-aws $(current_rmq_ref) $(base_rmq_ref) master 53 | dep_rabbitmq_boot_steps_visualiser = git_rmq rabbitmq-boot-steps-visualiser $(current_rmq_ref) $(base_rmq_ref) master 54 | dep_rabbitmq_cli = git_rmq rabbitmq-cli $(current_rmq_ref) $(base_rmq_ref) master 55 | dep_rabbitmq_codegen = git_rmq rabbitmq-codegen $(current_rmq_ref) $(base_rmq_ref) master 56 | dep_rabbitmq_consistent_hash_exchange = git_rmq rabbitmq-consistent-hash-exchange $(current_rmq_ref) $(base_rmq_ref) master 57 | dep_rabbitmq_ct_client_helpers = git_rmq rabbitmq-ct-client-helpers $(current_rmq_ref) $(base_rmq_ref) master 58 | dep_rabbitmq_ct_helpers = git_rmq rabbitmq-ct-helpers $(current_rmq_ref) $(base_rmq_ref) master 59 | dep_rabbitmq_delayed_message_exchange = git_rmq rabbitmq-delayed-message-exchange $(current_rmq_ref) $(base_rmq_ref) master 60 | dep_rabbitmq_dotnet_client = git_rmq rabbitmq-dotnet-client $(current_rmq_ref) $(base_rmq_ref) master 61 | dep_rabbitmq_event_exchange = git_rmq rabbitmq-event-exchange $(current_rmq_ref) $(base_rmq_ref) master 62 | dep_rabbitmq_federation = git_rmq rabbitmq-federation $(current_rmq_ref) $(base_rmq_ref) master 63 | dep_rabbitmq_federation_management = git_rmq rabbitmq-federation-management $(current_rmq_ref) $(base_rmq_ref) master 64 | dep_rabbitmq_java_client = git_rmq rabbitmq-java-client $(current_rmq_ref) $(base_rmq_ref) master 65 | dep_rabbitmq_jms_client = git_rmq rabbitmq-jms-client $(current_rmq_ref) $(base_rmq_ref) master 66 | dep_rabbitmq_jms_cts = git_rmq rabbitmq-jms-cts $(current_rmq_ref) $(base_rmq_ref) master 67 | dep_rabbitmq_jms_topic_exchange = git_rmq rabbitmq-jms-topic-exchange $(current_rmq_ref) $(base_rmq_ref) master 68 | dep_rabbitmq_lvc_exchange = git_rmq rabbitmq-lvc-exchange $(current_rmq_ref) $(base_rmq_ref) master 69 | dep_rabbitmq_management = git_rmq rabbitmq-management $(current_rmq_ref) $(base_rmq_ref) master 70 | dep_rabbitmq_management_agent = git_rmq rabbitmq-management-agent $(current_rmq_ref) $(base_rmq_ref) master 71 | dep_rabbitmq_management_exchange = git_rmq rabbitmq-management-exchange $(current_rmq_ref) $(base_rmq_ref) master 72 | dep_rabbitmq_management_themes = git_rmq rabbitmq-management-themes $(current_rmq_ref) $(base_rmq_ref) master 73 | dep_rabbitmq_message_timestamp = git_rmq rabbitmq-message-timestamp $(current_rmq_ref) $(base_rmq_ref) master 74 | dep_rabbitmq_metronome = git_rmq rabbitmq-metronome $(current_rmq_ref) $(base_rmq_ref) master 75 | dep_rabbitmq_mqtt = git_rmq rabbitmq-mqtt $(current_rmq_ref) $(base_rmq_ref) master 76 | dep_rabbitmq_objc_client = git_rmq rabbitmq-objc-client $(current_rmq_ref) $(base_rmq_ref) master 77 | dep_rabbitmq_peer_discovery_aws = git_rmq rabbitmq-peer-discovery-aws $(current_rmq_ref) $(base_rmq_ref) master 78 | dep_rabbitmq_peer_discovery_common = git_rmq rabbitmq-peer-discovery-common $(current_rmq_ref) $(base_rmq_ref) master 79 | dep_rabbitmq_peer_discovery_consul = git_rmq rabbitmq-peer-discovery-consul $(current_rmq_ref) $(base_rmq_ref) master 80 | dep_rabbitmq_peer_discovery_etcd = git_rmq rabbitmq-peer-discovery-etcd $(current_rmq_ref) $(base_rmq_ref) master 81 | dep_rabbitmq_peer_discovery_k8s = git_rmq rabbitmq-peer-discovery-k8s $(current_rmq_ref) $(base_rmq_ref) master 82 | dep_rabbitmq_prometheus = git_rmq rabbitmq-prometheus $(current_rmq_ref) $(base_rmq_ref) master 83 | dep_rabbitmq_random_exchange = git_rmq rabbitmq-random-exchange $(current_rmq_ref) $(base_rmq_ref) master 84 | dep_rabbitmq_recent_history_exchange = git_rmq rabbitmq-recent-history-exchange $(current_rmq_ref) $(base_rmq_ref) master 85 | dep_rabbitmq_routing_node_stamp = git_rmq rabbitmq-routing-node-stamp $(current_rmq_ref) $(base_rmq_ref) master 86 | dep_rabbitmq_rtopic_exchange = git_rmq rabbitmq-rtopic-exchange $(current_rmq_ref) $(base_rmq_ref) master 87 | dep_rabbitmq_server_release = git_rmq rabbitmq-server-release $(current_rmq_ref) $(base_rmq_ref) master 88 | dep_rabbitmq_sharding = git_rmq rabbitmq-sharding $(current_rmq_ref) $(base_rmq_ref) master 89 | dep_rabbitmq_shovel = git_rmq rabbitmq-shovel $(current_rmq_ref) $(base_rmq_ref) master 90 | dep_rabbitmq_shovel_management = git_rmq rabbitmq-shovel-management $(current_rmq_ref) $(base_rmq_ref) master 91 | dep_rabbitmq_stomp = git_rmq rabbitmq-stomp $(current_rmq_ref) $(base_rmq_ref) master 92 | dep_rabbitmq_stream = git_rmq rabbitmq-stream $(current_rmq_ref) $(base_rmq_ref) master 93 | dep_rabbitmq_toke = git_rmq rabbitmq-toke $(current_rmq_ref) $(base_rmq_ref) master 94 | dep_rabbitmq_top = git_rmq rabbitmq-top $(current_rmq_ref) $(base_rmq_ref) master 95 | dep_rabbitmq_tracing = git_rmq rabbitmq-tracing $(current_rmq_ref) $(base_rmq_ref) master 96 | dep_rabbitmq_trust_store = git_rmq rabbitmq-trust-store $(current_rmq_ref) $(base_rmq_ref) master 97 | dep_rabbitmq_test = git_rmq rabbitmq-test $(current_rmq_ref) $(base_rmq_ref) master 98 | dep_rabbitmq_web_dispatch = git_rmq rabbitmq-web-dispatch $(current_rmq_ref) $(base_rmq_ref) master 99 | dep_rabbitmq_web_stomp = git_rmq rabbitmq-web-stomp $(current_rmq_ref) $(base_rmq_ref) master 100 | dep_rabbitmq_web_stomp_examples = git_rmq rabbitmq-web-stomp-examples $(current_rmq_ref) $(base_rmq_ref) master 101 | dep_rabbitmq_web_mqtt = git_rmq rabbitmq-web-mqtt $(current_rmq_ref) $(base_rmq_ref) master 102 | dep_rabbitmq_web_mqtt_examples = git_rmq rabbitmq-web-mqtt-examples $(current_rmq_ref) $(base_rmq_ref) master 103 | dep_rabbitmq_website = git_rmq rabbitmq-website $(current_rmq_ref) $(base_rmq_ref) live master 104 | dep_toke = git_rmq toke $(current_rmq_ref) $(base_rmq_ref) master 105 | 106 | dep_rabbitmq_public_umbrella = git_rmq rabbitmq-public-umbrella $(current_rmq_ref) $(base_rmq_ref) master 107 | 108 | # Third-party dependencies version pinning. 109 | # 110 | # We do that in this file, which is copied in all projects, to ensure 111 | # all projects use the same versions. It avoids conflicts and makes it 112 | # possible to work with rabbitmq-public-umbrella. 113 | 114 | dep_accept = hex 0.3.5 115 | dep_cowboy = hex 2.8.0 116 | dep_cowlib = hex 2.9.1 117 | dep_jsx = hex 2.11.0 118 | dep_lager = hex 3.8.0 119 | dep_prometheus = git https://github.com/deadtrickster/prometheus.erl.git master 120 | dep_ra = git https://github.com/rabbitmq/ra.git master 121 | dep_ranch = hex 1.7.1 122 | dep_recon = hex 2.5.1 123 | dep_observer_cli = hex 1.5.4 124 | dep_stdout_formatter = hex 0.2.4 125 | dep_sysmon_handler = hex 1.3.0 126 | 127 | RABBITMQ_COMPONENTS = amqp_client \ 128 | amqp10_common \ 129 | amqp10_client \ 130 | rabbit \ 131 | rabbit_common \ 132 | rabbitmq_amqp1_0 \ 133 | rabbitmq_auth_backend_amqp \ 134 | rabbitmq_auth_backend_cache \ 135 | rabbitmq_auth_backend_http \ 136 | rabbitmq_auth_backend_ldap \ 137 | rabbitmq_auth_backend_oauth2 \ 138 | rabbitmq_auth_mechanism_ssl \ 139 | rabbitmq_aws \ 140 | rabbitmq_boot_steps_visualiser \ 141 | rabbitmq_cli \ 142 | rabbitmq_codegen \ 143 | rabbitmq_consistent_hash_exchange \ 144 | rabbitmq_ct_client_helpers \ 145 | rabbitmq_ct_helpers \ 146 | rabbitmq_delayed_message_exchange \ 147 | rabbitmq_dotnet_client \ 148 | rabbitmq_event_exchange \ 149 | rabbitmq_federation \ 150 | rabbitmq_federation_management \ 151 | rabbitmq_java_client \ 152 | rabbitmq_jms_client \ 153 | rabbitmq_jms_cts \ 154 | rabbitmq_jms_topic_exchange \ 155 | rabbitmq_lvc_exchange \ 156 | rabbitmq_management \ 157 | rabbitmq_management_agent \ 158 | rabbitmq_management_exchange \ 159 | rabbitmq_management_themes \ 160 | rabbitmq_message_timestamp \ 161 | rabbitmq_metronome \ 162 | rabbitmq_mqtt \ 163 | rabbitmq_objc_client \ 164 | rabbitmq_peer_discovery_aws \ 165 | rabbitmq_peer_discovery_common \ 166 | rabbitmq_peer_discovery_consul \ 167 | rabbitmq_peer_discovery_etcd \ 168 | rabbitmq_peer_discovery_k8s \ 169 | rabbitmq_prometheus \ 170 | rabbitmq_random_exchange \ 171 | rabbitmq_recent_history_exchange \ 172 | rabbitmq_routing_node_stamp \ 173 | rabbitmq_rtopic_exchange \ 174 | rabbitmq_server_release \ 175 | rabbitmq_sharding \ 176 | rabbitmq_shovel \ 177 | rabbitmq_shovel_management \ 178 | rabbitmq_stomp \ 179 | rabbitmq_stream \ 180 | rabbitmq_toke \ 181 | rabbitmq_top \ 182 | rabbitmq_tracing \ 183 | rabbitmq_trust_store \ 184 | rabbitmq_web_dispatch \ 185 | rabbitmq_web_mqtt \ 186 | rabbitmq_web_mqtt_examples \ 187 | rabbitmq_web_stomp \ 188 | rabbitmq_web_stomp_examples \ 189 | rabbitmq_website 190 | 191 | # Erlang.mk does not rebuild dependencies by default, once they were 192 | # compiled once, except for those listed in the `$(FORCE_REBUILD)` 193 | # variable. 194 | # 195 | # We want all RabbitMQ components to always be rebuilt: this eases 196 | # the work on several components at the same time. 197 | 198 | FORCE_REBUILD = $(RABBITMQ_COMPONENTS) 199 | 200 | # Several components have a custom erlang.mk/build.config, mainly 201 | # to disable eunit. Therefore, we can't use the top-level project's 202 | # erlang.mk copy. 203 | NO_AUTOPATCH += $(RABBITMQ_COMPONENTS) 204 | 205 | ifeq ($(origin current_rmq_ref),undefined) 206 | ifneq ($(wildcard .git),) 207 | current_rmq_ref := $(shell (\ 208 | ref=$$(LANG=C git branch --list | awk '/^\* \(.*detached / {ref=$$0; sub(/.*detached [^ ]+ /, "", ref); sub(/\)$$/, "", ref); print ref; exit;} /^\* / {ref=$$0; sub(/^\* /, "", ref); print ref; exit}');\ 209 | if test "$$(git rev-parse --short HEAD)" != "$$ref"; then echo "$$ref"; fi)) 210 | else 211 | current_rmq_ref := master 212 | endif 213 | endif 214 | export current_rmq_ref 215 | 216 | ifeq ($(origin base_rmq_ref),undefined) 217 | ifneq ($(wildcard .git),) 218 | possible_base_rmq_ref := master 219 | ifeq ($(possible_base_rmq_ref),$(current_rmq_ref)) 220 | base_rmq_ref := $(current_rmq_ref) 221 | else 222 | base_rmq_ref := $(shell \ 223 | (git rev-parse --verify -q master >/dev/null && \ 224 | git rev-parse --verify -q $(possible_base_rmq_ref) >/dev/null && \ 225 | git merge-base --is-ancestor $$(git merge-base master HEAD) $(possible_base_rmq_ref) && \ 226 | echo $(possible_base_rmq_ref)) || \ 227 | echo master) 228 | endif 229 | else 230 | base_rmq_ref := master 231 | endif 232 | endif 233 | export base_rmq_ref 234 | 235 | # Repository URL selection. 236 | # 237 | # First, we infer other components' location from the current project 238 | # repository URL, if it's a Git repository: 239 | # - We take the "origin" remote URL as the base 240 | # - The current project name and repository name is replaced by the 241 | # target's properties: 242 | # eg. rabbitmq-common is replaced by rabbitmq-codegen 243 | # eg. rabbit_common is replaced by rabbitmq_codegen 244 | # 245 | # If cloning from this computed location fails, we fallback to RabbitMQ 246 | # upstream which is GitHub. 247 | 248 | # Macro to transform eg. "rabbit_common" to "rabbitmq-common". 249 | rmq_cmp_repo_name = $(word 2,$(dep_$(1))) 250 | 251 | # Upstream URL for the current project. 252 | RABBITMQ_COMPONENT_REPO_NAME := $(call rmq_cmp_repo_name,$(PROJECT)) 253 | RABBITMQ_UPSTREAM_FETCH_URL ?= https://github.com/rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 254 | RABBITMQ_UPSTREAM_PUSH_URL ?= git@github.com:rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 255 | 256 | # Current URL for the current project. If this is not a Git clone, 257 | # default to the upstream Git repository. 258 | ifneq ($(wildcard .git),) 259 | git_origin_fetch_url := $(shell git config remote.origin.url) 260 | git_origin_push_url := $(shell git config remote.origin.pushurl || git config remote.origin.url) 261 | RABBITMQ_CURRENT_FETCH_URL ?= $(git_origin_fetch_url) 262 | RABBITMQ_CURRENT_PUSH_URL ?= $(git_origin_push_url) 263 | else 264 | RABBITMQ_CURRENT_FETCH_URL ?= $(RABBITMQ_UPSTREAM_FETCH_URL) 265 | RABBITMQ_CURRENT_PUSH_URL ?= $(RABBITMQ_UPSTREAM_PUSH_URL) 266 | endif 267 | 268 | # Macro to replace the following pattern: 269 | # 1. /foo.git -> /bar.git 270 | # 2. /foo -> /bar 271 | # 3. /foo/ -> /bar/ 272 | subst_repo_name = $(patsubst %/$(1)/%,%/$(2)/%,$(patsubst %/$(1),%/$(2),$(patsubst %/$(1).git,%/$(2).git,$(3)))) 273 | 274 | # Macro to replace both the project's name (eg. "rabbit_common") and 275 | # repository name (eg. "rabbitmq-common") by the target's equivalent. 276 | # 277 | # This macro is kept on one line because we don't want whitespaces in 278 | # the returned value, as it's used in $(dep_fetch_git_rmq) in a shell 279 | # single-quoted string. 280 | dep_rmq_repo = $(if $(dep_$(2)),$(call subst_repo_name,$(PROJECT),$(2),$(call subst_repo_name,$(RABBITMQ_COMPONENT_REPO_NAME),$(call rmq_cmp_repo_name,$(2)),$(1))),$(pkg_$(1)_repo)) 281 | 282 | dep_rmq_commits = $(if $(dep_$(1)), \ 283 | $(wordlist 3,$(words $(dep_$(1))),$(dep_$(1))), \ 284 | $(pkg_$(1)_commit)) 285 | 286 | define dep_fetch_git_rmq 287 | fetch_url1='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_FETCH_URL),$(1))'; \ 288 | fetch_url2='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_FETCH_URL),$(1))'; \ 289 | if test "$$$$fetch_url1" != '$(RABBITMQ_CURRENT_FETCH_URL)' && \ 290 | git clone -q -n -- "$$$$fetch_url1" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 291 | fetch_url="$$$$fetch_url1"; \ 292 | push_url='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_PUSH_URL),$(1))'; \ 293 | elif git clone -q -n -- "$$$$fetch_url2" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 294 | fetch_url="$$$$fetch_url2"; \ 295 | push_url='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_PUSH_URL),$(1))'; \ 296 | fi; \ 297 | cd $(DEPS_DIR)/$(call dep_name,$(1)) && ( \ 298 | $(foreach ref,$(call dep_rmq_commits,$(1)), \ 299 | git checkout -q $(ref) >/dev/null 2>&1 || \ 300 | ) \ 301 | (echo "error: no valid pathspec among: $(call dep_rmq_commits,$(1))" \ 302 | 1>&2 && false) ) && \ 303 | (test "$$$$fetch_url" = "$$$$push_url" || \ 304 | git remote set-url --push origin "$$$$push_url") 305 | endef 306 | 307 | # -------------------------------------------------------------------- 308 | # Component distribution. 309 | # -------------------------------------------------------------------- 310 | 311 | list-dist-deps:: 312 | @: 313 | 314 | prepare-dist:: 315 | @: 316 | 317 | # -------------------------------------------------------------------- 318 | # Umbrella-specific settings. 319 | # -------------------------------------------------------------------- 320 | 321 | # If the top-level project is a RabbitMQ component, we override 322 | # $(DEPS_DIR) for this project to point to the top-level's one. 323 | # 324 | # We also verify that the guessed DEPS_DIR is actually named `deps`, 325 | # to rule out any situation where it is a coincidence that we found a 326 | # `rabbitmq-components.mk` up upper directories. 327 | 328 | possible_deps_dir_1 = $(abspath ..) 329 | possible_deps_dir_2 = $(abspath ../../..) 330 | 331 | ifeq ($(notdir $(possible_deps_dir_1)),deps) 332 | ifneq ($(wildcard $(possible_deps_dir_1)/../rabbitmq-components.mk),) 333 | deps_dir_overriden = 1 334 | DEPS_DIR ?= $(possible_deps_dir_1) 335 | DISABLE_DISTCLEAN = 1 336 | endif 337 | endif 338 | 339 | ifeq ($(deps_dir_overriden),) 340 | ifeq ($(notdir $(possible_deps_dir_2)),deps) 341 | ifneq ($(wildcard $(possible_deps_dir_2)/../rabbitmq-components.mk),) 342 | deps_dir_overriden = 1 343 | DEPS_DIR ?= $(possible_deps_dir_2) 344 | DISABLE_DISTCLEAN = 1 345 | endif 346 | endif 347 | endif 348 | 349 | ifneq ($(wildcard UMBRELLA.md),) 350 | DISABLE_DISTCLEAN = 1 351 | endif 352 | 353 | # We disable `make distclean` so $(DEPS_DIR) is not accidentally removed. 354 | 355 | ifeq ($(DISABLE_DISTCLEAN),1) 356 | ifneq ($(filter distclean distclean-deps,$(MAKECMDGOALS)),) 357 | SKIP_DEPS = 1 358 | endif 359 | endif 360 | -------------------------------------------------------------------------------- /src/Elixir.RabbitMQ.CLI.Ctl.Commands.ResetStatsDbCommand.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module('Elixir.RabbitMQ.CLI.Ctl.Commands.ResetStatsDbCommand'). 9 | 10 | -behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour'). 11 | 12 | -export([ 13 | usage/0, 14 | validate/2, 15 | merge_defaults/2, 16 | banner/2, 17 | run/2, 18 | output/2, 19 | switches/0, 20 | description/0 21 | ]). 22 | 23 | 24 | %%---------------------------------------------------------------------------- 25 | %% Callbacks 26 | %%---------------------------------------------------------------------------- 27 | usage() -> 28 | <<"reset_stats_db [--all]">>. 29 | 30 | validate(_, _) -> 31 | ok. 32 | 33 | merge_defaults(A, Opts) -> 34 | {A, maps:merge(#{all => false}, Opts)}. 35 | 36 | switches() -> 37 | [{all, boolean}]. 38 | 39 | run(_Args, #{node := Node, all := true}) -> 40 | rabbit_misc:rpc_call(Node, rabbit_mgmt_storage, reset_all, []); 41 | run(_Args, #{node := Node, all := false}) -> 42 | rabbit_misc:rpc_call(Node, rabbit_mgmt_storage, reset, []). 43 | 44 | output(Output, _Opts) -> 45 | 'Elixir.RabbitMQ.CLI.DefaultOutput':output(Output). 46 | 47 | banner(_, #{all := true}) -> 48 | <<"Resetting statistics database in all nodes">>; 49 | banner(_, #{node := Node}) -> 50 | erlang:iolist_to_binary([<<"Resetting statistics database on node ">>, 51 | atom_to_binary(Node, utf8)]). 52 | 53 | description() -> 54 | <<"Resets statistics database. This will remove all metrics data!">>. 55 | -------------------------------------------------------------------------------- /src/exometer_slide.erl: -------------------------------------------------------------------------------- 1 | %% This file is a copy of exometer_slide.erl from https://github.com/Feuerlabs/exometer_core, 2 | %% with the following modifications: 3 | %% 4 | %% 1) The elements are tuples of numbers 5 | %% 6 | %% 2) Only one element for each expected interval point is added, intermediate values 7 | %% are discarded. Thus, if we have a window of 60s and interval of 5s, at max 12 elements 8 | %% are stored. 9 | %% 10 | %% 3) Additions can be provided as increments to the last value stored 11 | %% 12 | %% 4) sum/1 implements the sum of several slides, generating a new timestamp sequence based 13 | %% on the given intervals. Elements on each window are added to the closest interval point. 14 | %% 15 | %% Original commit: https://github.com/Feuerlabs/exometer_core/commit/2759edc804211b5245867b32c9a20c8fe1d93441 16 | %% 17 | %% ------------------------------------------------------------------- 18 | %% 19 | %% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. 20 | %% 21 | %% This Source Code Form is subject to the terms of the Mozilla Public 22 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 23 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 24 | %% 25 | %% ------------------------------------------------------------------- 26 | %% 27 | %% @author Tony Rogvall 28 | %% @author Ulf Wiger 29 | %% @author Magnus Feuer 30 | %% 31 | %% @doc Efficient sliding-window buffer 32 | %% 33 | %% Initial implementation: 29 Sep 2009 by Tony Rogvall 34 | %% 35 | %% This module implements an efficient sliding window, maintaining 36 | %% two lists - a primary and a secondary. Values are paired with a 37 | %% timestamp (millisecond resolution, see `timestamp/0') 38 | %% and prepended to the primary list. When the time span between the oldest 39 | %% and the newest entry in the primary list exceeds the given window size, 40 | %% the primary list is shifted into the secondary list position, and the 41 | %% new entry is added to a new (empty) primary list. 42 | %% 43 | %% The window can be converted to a list using `to_list/1'. 44 | %% @end 45 | %% 46 | %% 47 | %% All modifications are (C) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 48 | %% The Initial Developer of the Original Code is Basho Technologies, Inc. 49 | 50 | -module(exometer_slide). 51 | 52 | -export([new/2, new/3, 53 | reset/1, 54 | add_element/3, 55 | to_list/2, 56 | to_list/3, 57 | foldl/5, 58 | map/2, 59 | to_normalized_list/5]). 60 | 61 | -export([timestamp/0, 62 | last_two/1, 63 | last/1]). 64 | 65 | -export([sum/1, 66 | sum/2, 67 | sum/5, 68 | optimize/1]). 69 | 70 | %% For testing 71 | -export([buffer/1]). 72 | 73 | -compile(inline). 74 | -compile(inline_list_funcs). 75 | 76 | 77 | -type value() :: tuple(). 78 | -type internal_value() :: tuple() | drop. 79 | -type timestamp() :: non_neg_integer(). 80 | 81 | -type fold_acc() :: any(). 82 | -type fold_fun() :: fun(({timestamp(), internal_value()}, fold_acc()) -> fold_acc()). 83 | 84 | %% Fixed size event buffer 85 | -record(slide, {size = 0 :: integer(), % ms window 86 | n = 0 :: integer(), % number of elements in buf1 87 | max_n :: infinity | integer(), % max no of elements 88 | incremental = false :: boolean(), 89 | interval :: integer(), 90 | last = 0 :: integer(), % millisecond timestamp 91 | first = undefined :: undefined | integer(), % millisecond timestamp 92 | buf1 = [] :: [internal_value()], 93 | buf2 = [] :: [internal_value()], 94 | total :: undefined | value()}). 95 | 96 | -opaque slide() :: #slide{}. 97 | 98 | -export_type([slide/0, timestamp/0]). 99 | 100 | -spec timestamp() -> timestamp(). 101 | %% @doc Generate a millisecond-resolution timestamp. 102 | %% 103 | %% This timestamp format is used e.g. by the `exometer_slide' and 104 | %% `exometer_histogram' implementations. 105 | %% @end 106 | timestamp() -> 107 | os:system_time(milli_seconds). 108 | 109 | -spec new(_Size::integer(), _Options::list()) -> slide(). 110 | %% @doc Create a new sliding-window buffer. 111 | %% 112 | %% `Size' determines the size in milliseconds of the sliding window. 113 | %% The implementation prepends values into a primary list until the oldest 114 | %% element in the list is `Size' ms older than the current value. It then 115 | %% swaps the primary list into a secondary list, and starts prepending to 116 | %% a new primary list. This means that more data than fits inside the window 117 | %% will be kept - upwards of twice as much. On the other hand, updating the 118 | %% buffer is very cheap. 119 | %% @end 120 | new(Size, Opts) -> new(timestamp(), Size, Opts). 121 | 122 | -spec new(Timestamp :: timestamp(), Size::integer(), Options::list()) -> slide(). 123 | new(TS, Size, Opts) -> 124 | #slide{size = Size, 125 | max_n = proplists:get_value(max_n, Opts, infinity), 126 | interval = proplists:get_value(interval, Opts, infinity), 127 | last = TS, 128 | first = undefined, 129 | incremental = proplists:get_value(incremental, Opts, false), 130 | buf1 = [], 131 | buf2 = []}. 132 | 133 | -spec reset(slide()) -> slide(). 134 | 135 | %% @doc Empty the buffer 136 | %% 137 | reset(Slide) -> 138 | Slide#slide{n = 0, buf1 = [], buf2 = [], last = 0, first = undefined}. 139 | 140 | %% @doc Add an element to the buffer, tagged with the given timestamp. 141 | %% 142 | %% Apart from the specified timestamp, this function works just like 143 | %% {@link add_element/2}. 144 | %% @end 145 | -spec add_element(timestamp(), value(), slide()) -> slide(). 146 | add_element(_TS, _Evt, Slide) when Slide#slide.size == 0 -> 147 | Slide; 148 | add_element(TS, Evt, #slide{last = Last, interval = Interval, total = Total0, 149 | incremental = true} = Slide) 150 | when (TS - Last) < Interval -> 151 | Total = add_to_total(Evt, Total0), 152 | Slide#slide{total = Total}; 153 | add_element(TS, Evt, #slide{last = Last, interval = Interval} = Slide) 154 | when (TS - Last) < Interval -> 155 | Slide#slide{total = Evt}; 156 | add_element(TS, Evt, #slide{last = Last, size = Sz, incremental = true, 157 | n = N, max_n = MaxN, total = Total0, 158 | buf1 = Buf1} = Slide) -> 159 | N1 = N+1, 160 | Total = add_to_total(Evt, Total0), 161 | %% Total could be the same as the last sample, by adding and substracting 162 | %% the same amout to the totals. That is not strictly a drop, but should 163 | %% generate new samples. 164 | %% I.e. 0, 0, -14, 14 (total = 0, samples = 14, -14, 0, drop) 165 | case {is_zeros(Evt), Buf1} of 166 | {_, []} -> 167 | Slide#slide{n = N1, first = TS, buf1 = [{TS, Total} | Buf1], 168 | last = TS, total = Total}; 169 | _ when TS - Last > Sz; N1 > MaxN -> 170 | %% swap 171 | Slide#slide{last = TS, n = 1, buf1 = [{TS, Total}], 172 | buf2 = Buf1, total = Total}; 173 | {true, [{_, Total}, {_, drop} = Drop | Tail]} -> 174 | %% Memory optimisation 175 | Slide#slide{buf1 = [{TS, Total}, Drop | Tail], 176 | n = N1, last = TS}; 177 | {true, [{DropTS, Total} | Tail]} -> 178 | %% Memory optimisation 179 | Slide#slide{buf1 = [{TS, Total}, {DropTS, drop} | Tail], 180 | n = N1, last = TS}; 181 | _ -> 182 | Slide#slide{n = N1, buf1 = [{TS, Total} | Buf1], 183 | last = TS, total = Total} 184 | end; 185 | add_element(TS, Evt, #slide{last = Last, size = Sz, n = N, max_n = MaxN, 186 | buf1 = Buf1} = Slide) 187 | when TS - Last > Sz; N + 1 > MaxN -> 188 | Slide#slide{last = TS, n = 1, buf1 = [{TS, Evt}], 189 | buf2 = Buf1, total = Evt}; 190 | add_element(TS, Evt, #slide{buf1 = [{_, Evt}, {_, drop} = Drop | Tail], 191 | n = N} = Slide) -> 192 | %% Memory optimisation 193 | Slide#slide{buf1 = [{TS, Evt}, Drop | Tail], n = N + 1, last = TS}; 194 | add_element(TS, Evt, #slide{buf1 = [{DropTS, Evt} | Tail], n = N} = Slide) -> 195 | %% Memory optimisation 196 | Slide#slide{buf1 = [{TS, Evt}, {DropTS, drop} | Tail], 197 | n = N + 1, last = TS}; 198 | add_element(TS, Evt, #slide{n = N, buf1 = Buf1} = Slide) -> 199 | N1 = N+1, 200 | case Buf1 of 201 | [] -> 202 | Slide#slide{n = N1, buf1 = [{TS, Evt} | Buf1], 203 | last = TS, first = TS, total = Evt}; 204 | _ -> 205 | Slide#slide{n = N1, buf1 = [{TS, Evt} | Buf1], 206 | last = TS, total = Evt} 207 | end. 208 | 209 | add_to_total(Evt, undefined) -> 210 | Evt; 211 | add_to_total({A0}, {B0}) -> 212 | {B0 + A0}; 213 | add_to_total({A0, A1}, {B0, B1}) -> 214 | {B0 + A0, B1 + A1}; 215 | add_to_total({A0, A1, A2}, {B0, B1, B2}) -> 216 | {B0 + A0, B1 + A1, B2 + A2}; 217 | add_to_total({A0, A1, A2, A3}, {B0, B1, B2, B3}) -> 218 | {B0 + A0, B1 + A1, B2 + A2, B3 + A3}; 219 | add_to_total({A0, A1, A2, A3, A4}, {B0, B1, B2, B3, B4}) -> 220 | {B0 + A0, B1 + A1, B2 + A2, B3 + A3, B4 + A4}; 221 | add_to_total({A0, A1, A2, A3, A4, A5}, {B0, B1, B2, B3, B4, B5}) -> 222 | {B0 + A0, B1 + A1, B2 + A2, B3 + A3, B4 + A4, B5 + A5}; 223 | add_to_total({A0, A1, A2, A3, A4, A5, A6}, {B0, B1, B2, B3, B4, B5, B6}) -> 224 | {B0 + A0, B1 + A1, B2 + A2, B3 + A3, B4 + A4, B5 + A5, B6 + A6}; 225 | add_to_total({A0, A1, A2, A3, A4, A5, A6, A7}, {B0, B1, B2, B3, B4, B5, B6, B7}) -> 226 | {B0 + A0, B1 + A1, B2 + A2, B3 + A3, B4 + A4, B5 + A5, B6 + A6, B7 + A7}; 227 | add_to_total({A0, A1, A2, A3, A4, A5, A6, A7, A8}, {B0, B1, B2, B3, B4, B5, B6, B7, B8}) -> 228 | {B0 + A0, B1 + A1, B2 + A2, B3 + A3, B4 + A4, B5 + A5, B6 + A6, B7 + A7, B8 + A8}; 229 | add_to_total({A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, 230 | A15, A16, A17, A18, A19}, 231 | {B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, 232 | B15, B16, B17, B18, B19}) -> 233 | {B0 + A0, B1 + A1, B2 + A2, B3 + A3, B4 + A4, B5 + A5, B6 + A6, B7 + A7, B8 + A8, 234 | B9 + A9, B10 + A10, B11 + A11, B12 + A12, B13 + A13, B14 + A14, B15 + A15, B16 + A16, 235 | B17 + A17, B18 + A18, B19 + A19}. 236 | 237 | is_zeros({0}) -> 238 | true; 239 | is_zeros({0, 0}) -> 240 | true; 241 | is_zeros({0, 0, 0}) -> 242 | true; 243 | is_zeros({0, 0, 0, 0}) -> 244 | true; 245 | is_zeros({0, 0, 0, 0, 0}) -> 246 | true; 247 | is_zeros({0, 0, 0, 0, 0, 0}) -> 248 | true; 249 | is_zeros({0, 0, 0, 0, 0, 0, 0}) -> 250 | true; 251 | is_zeros({0, 0, 0, 0, 0, 0, 0, 0, 0}) -> 252 | true; 253 | is_zeros({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) -> 254 | true; 255 | is_zeros(_) -> 256 | false. 257 | 258 | -spec optimize(slide()) -> slide(). 259 | optimize(#slide{buf2 = []} = Slide) -> 260 | Slide; 261 | optimize(#slide{buf1 = Buf1, buf2 = Buf2, max_n = MaxN, n = N} = Slide) 262 | when is_integer(MaxN) andalso length(Buf1) < MaxN -> 263 | Slide#slide{buf1 = Buf1, 264 | buf2 = lists:sublist(Buf2, n_diff(MaxN, N) + 1)}; 265 | optimize(Slide) -> Slide. 266 | 267 | snd(T) when is_tuple(T) -> 268 | element(2, T). 269 | 270 | 271 | -spec to_list(timestamp(), slide()) -> [{timestamp(), value()}]. 272 | %% @doc Convert the sliding window into a list of timestamped values. 273 | %% @end 274 | to_list(_Now, #slide{size = Sz}) when Sz == 0 -> 275 | []; 276 | to_list(Now, #slide{size = Sz} = Slide) -> 277 | snd(to_list_from(Now, Now - Sz, Slide)). 278 | 279 | to_list(Now, Start, Slide) -> 280 | snd(to_list_from(Now, Start, Slide)). 281 | 282 | to_list_from(Now, Start0, #slide{max_n = MaxN, buf2 = Buf2, first = FirstTS, 283 | interval = Interval} = Slide) -> 284 | 285 | {NewN, Buf1} = maybe_add_last_sample(Now, Slide), 286 | Start = first_max(FirstTS, Start0), 287 | {Prev0, Buf1_1} = take_since(Buf1, Now, Start, first_max(MaxN, NewN), [], Interval), 288 | case take_since(Buf2, Now, Start, first_max(MaxN, NewN), Buf1_1, Interval) of 289 | {undefined, Buf1_1} -> 290 | {Prev0, Buf1_1}; 291 | {_Prev, Buf1_1} = Res -> 292 | case Prev0 of 293 | undefined -> 294 | Res; 295 | _ -> 296 | %% If take_since returns the same buffer, that means we don't 297 | %% need Buf2 at all. We might be returning a too old sample 298 | %% in previous, so we must use the one from Buf1 299 | {Prev0, Buf1_1} 300 | end; 301 | Res -> 302 | Res 303 | end. 304 | 305 | first_max(F, X) when is_integer(F) -> max(F, X); 306 | first_max(_, X) -> X. 307 | 308 | -spec last_two(slide()) -> [{timestamp(), value()}]. 309 | %% @doc Returns the newest 2 elements on the sample 310 | last_two(#slide{buf1 = [{TS, Evt} = H1, {_, drop} | _], interval = Interval}) -> 311 | [H1, {TS - Interval, Evt}]; 312 | last_two(#slide{buf1 = [H1, H2_0 | _], interval = Interval}) -> 313 | H2 = adjust_timestamp(H1, H2_0, Interval), 314 | [H1, H2]; 315 | last_two(#slide{buf1 = [H1], buf2 = [H2_0 | _], 316 | interval = Interval}) -> 317 | H2 = adjust_timestamp(H1, H2_0, Interval), 318 | [H1, H2]; 319 | last_two(#slide{buf1 = [H1], buf2 = []}) -> 320 | [H1]; 321 | last_two(_) -> 322 | []. 323 | 324 | adjust_timestamp({TS1, _}, {TS2, V2}, Interval) -> 325 | case TS1 - TS2 > Interval of 326 | true -> {TS1 - Interval, V2}; 327 | false -> {TS2, V2} 328 | end. 329 | 330 | -spec last(slide()) -> value() | undefined. 331 | last(#slide{total = T}) when T =/= undefined -> 332 | T; 333 | last(#slide{buf1 = [{_TS, T} | _]}) -> 334 | T; 335 | last(#slide{buf2 = [{_TS, T} | _]}) -> 336 | T; 337 | last(_) -> 338 | undefined. 339 | 340 | -spec foldl(timestamp(), timestamp(), fold_fun(), fold_acc(), slide()) -> fold_acc(). 341 | %% @doc Fold over the sliding window, starting from `Timestamp'. 342 | %% Now provides a reference point to evaluate whether to include 343 | %% partial, unrealised sample values in the sequence. Unrealised values will be 344 | %% appended to the sequence when Now >= LastTS + Interval 345 | %% 346 | %% The fun should as `fun({Timestamp, Value}, Acc) -> NewAcc'. 347 | %% The values are processed in order from oldest to newest. 348 | %% @end 349 | foldl(_Now, _Timestamp, _Fun, _Acc, #slide{size = Sz}) when Sz == 0 -> 350 | []; 351 | foldl(Now, Start0, Fun, Acc, #slide{max_n = _MaxN, buf2 = _Buf2, 352 | interval = _Interval} = Slide) -> 353 | lists:foldl(Fun, Acc, element(2, to_list_from(Now, Start0, Slide)) ++ [last]). 354 | 355 | map(Fun, #slide{buf1 = Buf1, buf2 = Buf2, total = Total} = Slide) -> 356 | BufFun = fun({Timestamp, Value}) -> 357 | {Timestamp, Fun(Value)} 358 | end, 359 | MappedBuf1 = lists:map(BufFun, Buf1), 360 | MappedBuf2 = lists:map(BufFun, Buf2), 361 | MappedTotal = Fun(Total), 362 | Slide#slide{buf1 = MappedBuf1, buf2 = MappedBuf2, total = MappedTotal}. 363 | 364 | maybe_add_last_sample(_Now, #slide{total = T, n = N, 365 | buf1 = [{_, T} | _] = Buf1}) -> 366 | {N, Buf1}; 367 | maybe_add_last_sample(Now, #slide{total = T, 368 | n = N, 369 | last = Last, 370 | interval = I, 371 | buf1 = Buf1}) 372 | when T =/= undefined andalso Now >= Last + I -> 373 | {N + 1, [{Last + I, T} | Buf1]}; 374 | maybe_add_last_sample(_Now, #slide{buf1 = Buf1, n = N}) -> 375 | {N, Buf1}. 376 | 377 | 378 | create_normalized_lookup(Start, Interval, RoundFun, Samples) -> 379 | lists:foldl(fun({TS, Value}, Acc) when TS - Start >= 0 -> 380 | NewTS = map_timestamp(TS, Start, Interval, RoundFun), 381 | maps:update_with(NewTS, 382 | fun({T, V}) when T > TS -> 383 | {T, V}; 384 | (_) -> 385 | {TS, Value} 386 | end, {TS, Value}, Acc); 387 | (_, Acc) -> 388 | Acc 389 | end, #{}, Samples). 390 | 391 | -spec to_normalized_list(timestamp(), timestamp(), integer(), slide(), 392 | no_pad | tuple()) -> [tuple()]. 393 | to_normalized_list(Now, Start, Interval, Slide, Empty) -> 394 | to_normalized_list(Now, Start, Interval, Slide, Empty, fun ceil/1). 395 | 396 | to_normalized_list(Now, Start, Interval, #slide{first = FirstTS0, 397 | total = Total} = Slide, 398 | Empty, RoundFun) -> 399 | 400 | RoundTSFun = fun (TS) -> map_timestamp(TS, Start, Interval, RoundFun) end, 401 | 402 | % add interval as we don't want to miss a sample due to rounding 403 | {Prev, Samples} = to_list_from(Now + Interval, Start, Slide), 404 | Lookup = create_normalized_lookup(Start, Interval, RoundFun, Samples), 405 | 406 | NowRound = RoundTSFun(Now), 407 | 408 | Pad = case Samples of 409 | _ when Empty =:= no_pad -> 410 | []; 411 | [{TS, _} | _] when Prev =/= undefined, Start =< TS -> 412 | [{T, snd(Prev)} 413 | || T <- lists:seq(RoundTSFun(TS) - Interval, Start, 414 | -Interval)]; 415 | [{TS, _} | _] when is_number(FirstTS0) andalso Start < FirstTS0 -> 416 | % only if we know there is nothing in the past can we 417 | % generate a 0 pad 418 | [{T, Empty} || T <- lists:seq(RoundTSFun(TS) - Interval, Start, 419 | -Interval)]; 420 | _ when FirstTS0 =:= undefined andalso Total =:= undefined -> 421 | [{T, Empty} || T <- lists:seq(NowRound, Start, -Interval)]; 422 | [] -> % samples have been seen, use the total to pad 423 | [{T, Total} || T <- lists:seq(NowRound, Start, -Interval)]; 424 | _ -> [] 425 | end, 426 | 427 | {_, Res1} = lists:foldl( 428 | fun(T, {Last, Acc}) -> 429 | case maps:find(T, Lookup) of 430 | {ok, {_, V}} -> 431 | {V, [{T, V} | Acc]}; 432 | error when Last =:= undefined -> 433 | {Last, Acc}; 434 | error -> % this pads the last value into the future 435 | {Last, [{T, Last} | Acc]} 436 | end 437 | end, {undefined, []}, 438 | lists:seq(Start, NowRound, Interval)), 439 | Res1 ++ Pad. 440 | 441 | 442 | %% @doc Sums a list of slides 443 | %% 444 | %% Takes the last known timestamp and creates an template version of the 445 | %% sliding window. Timestamps are then truncated and summed with the value 446 | %% in the template slide. 447 | %% @end 448 | -spec sum([slide()]) -> slide(). 449 | sum(Slides) -> sum(Slides, no_pad). 450 | 451 | sum([#slide{size = Size, interval = Interval} | _] = Slides, Pad) -> 452 | % take the freshest timestamp as reference point for summing operation 453 | Now = lists:max([Last || #slide{last = Last} <- Slides]), 454 | Start = Now - Size, 455 | sum(Now, Start, Interval, Slides, Pad). 456 | 457 | 458 | sum(Now, Start, Interval, [Slide | _ ] = All, Pad) -> 459 | Fun = fun({TS, Value}, Acc) -> 460 | maps:update_with(TS, fun(V) -> add_to_total(V, Value) end, 461 | Value, Acc) 462 | end, 463 | {Total, Dict} = 464 | lists:foldl(fun(#slide{total = T} = S, {Tot, Acc}) -> 465 | Samples = to_normalized_list(Now, Start, Interval, S, 466 | Pad, fun ceil/1), 467 | Total = add_to_total(T, Tot), 468 | Folded = lists:foldl(Fun, Acc, Samples), 469 | {Total, Folded} 470 | end, {undefined, #{}}, All), 471 | 472 | {First, Buffer} = case lists:sort(maps:to_list(Dict)) of 473 | [] -> 474 | F = case [TS || #slide{first = TS} <- All, 475 | is_integer(TS)] of 476 | [] -> undefined; 477 | FS -> lists:min(FS) 478 | end, 479 | {F, []}; 480 | [{F, _} | _ ] = B -> 481 | {F, lists:reverse(B)} 482 | end, 483 | Slide#slide{buf1 = Buffer, buf2 = [], total = Total, n = length(Buffer), 484 | first = First, last = Now}. 485 | 486 | 487 | truncated_seq(_First, _Last, _Incr, 0) -> 488 | []; 489 | truncated_seq(TS, TS, _Incr, MaxN) when MaxN > 0 -> 490 | [TS]; 491 | truncated_seq(First, Last, Incr, MaxN) when First =< Last andalso MaxN > 0 -> 492 | End = min(Last, First + (MaxN * Incr) - Incr), 493 | lists:seq(First, End, Incr); 494 | truncated_seq(First, Last, Incr, MaxN) -> 495 | End = max(Last, First + (MaxN * Incr) - Incr), 496 | lists:seq(First, End, Incr). 497 | 498 | 499 | take_since([{DropTS, drop} | T], Now, Start, N, [{TS, Evt} | _] = Acc, 500 | Interval) -> 501 | case T of 502 | [] -> 503 | Fill = [{TS0, Evt} || TS0 <- truncated_seq(TS - Interval, 504 | max(DropTS, Start), 505 | -Interval, N)], 506 | {undefined, lists:reverse(Fill) ++ Acc}; 507 | [{TS0, _} = E | Rest] when TS0 >= Start, N > 0 -> 508 | Fill = [{TS1, Evt} || TS1 <- truncated_seq(TS0 + Interval, 509 | max(TS0 + Interval, TS - Interval), 510 | Interval, N)], 511 | take_since(Rest, Now, Start, decr(N), [E | Fill ++ Acc], Interval); 512 | [Prev | _] -> % next sample is out of range so needs to be filled from Start 513 | Fill = [{TS1, Evt} || TS1 <- truncated_seq(Start, max(Start, TS - Interval), 514 | Interval, N)], 515 | {Prev, Fill ++ Acc} 516 | end; 517 | take_since([{TS, V} = H | T], Now, Start, N, Acc, Interval) when TS >= Start, 518 | N > 0, 519 | TS =< Now, 520 | is_tuple(V) -> 521 | take_since(T, Now, Start, decr(N), [H|Acc], Interval); 522 | take_since([{TS,_} | T], Now, Start, N, Acc, Interval) when TS >= Start, N > 0 -> 523 | take_since(T, Now, Start, decr(N), Acc, Interval); 524 | take_since([Prev | _], _, _, _, Acc, _) -> 525 | {Prev, Acc}; 526 | take_since(_, _, _, _, Acc, _) -> 527 | %% Don't reverse; already the wanted order. 528 | {undefined, Acc}. 529 | 530 | decr(N) when is_integer(N) -> 531 | N-1; 532 | decr(N) -> N. 533 | 534 | n_diff(A, B) when is_integer(A) -> 535 | A - B. 536 | 537 | ceil(X) when X < 0 -> 538 | trunc(X); 539 | ceil(X) -> 540 | T = trunc(X), 541 | case X - T == 0 of 542 | true -> T; 543 | false -> T + 1 544 | end. 545 | 546 | map_timestamp(TS, Start, Interval, Round) -> 547 | Factor = Round((TS - Start) / Interval), 548 | Start + Interval * Factor. 549 | 550 | buffer(#slide{buf1 = Buf1, buf2 = Buf2}) -> 551 | Buf1 ++ Buf2. 552 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_agent_app.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_agent_app). 9 | 10 | -behaviour(application). 11 | -export([start/2, stop/1]). 12 | 13 | start(_Type, _StartArgs) -> 14 | rabbit_mgmt_agent_sup_sup:start_link(). 15 | 16 | stop(_State) -> 17 | ok. 18 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_agent_config.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(rabbit_mgmt_agent_config). 8 | 9 | -export([get_env/1, get_env/2]). 10 | 11 | %% some people have reasons to only run with the agent enabled: 12 | %% make it possible for them to configure key management app 13 | %% settings such as rates_mode. 14 | get_env(Key) -> 15 | rabbit_misc:get_env(rabbitmq_management, Key, 16 | rabbit_misc:get_env(rabbitmq_management_agent, Key, 17 | undefined)). 18 | 19 | get_env(Key, Default) -> 20 | rabbit_misc:get_env(rabbitmq_management, Key, 21 | rabbit_misc:get_env(rabbitmq_management_agent, Key, 22 | Default)). 23 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_agent_sup.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_agent_sup). 9 | 10 | %% pg2 is deprecated in OTP 23. 11 | -compile(nowarn_deprecated_function). 12 | 13 | -behaviour(supervisor). 14 | 15 | -include_lib("rabbit_common/include/rabbit.hrl"). 16 | -include_lib("rabbit_common/include/rabbit_core_metrics.hrl"). 17 | -include("rabbit_mgmt_metrics.hrl"). 18 | 19 | -export([init/1]). 20 | -export([start_link/0]). 21 | 22 | init([]) -> 23 | MCs = maybe_enable_metrics_collector(), 24 | ExternalStats = {rabbit_mgmt_external_stats, 25 | {rabbit_mgmt_external_stats, start_link, []}, 26 | permanent, 5000, worker, [rabbit_mgmt_external_stats]}, 27 | {ok, {{one_for_one, 100, 10}, [ExternalStats] ++ MCs}}. 28 | 29 | start_link() -> 30 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 31 | 32 | 33 | maybe_enable_metrics_collector() -> 34 | case application:get_env(rabbitmq_management_agent, disable_metrics_collector, false) of 35 | false -> 36 | pg2:create(management_db), 37 | ok = pg2:join(management_db, self()), 38 | ST = {rabbit_mgmt_storage, {rabbit_mgmt_storage, start_link, []}, 39 | permanent, ?WORKER_WAIT, worker, [rabbit_mgmt_storage]}, 40 | MD = {delegate_management_sup, {delegate_sup, start_link, [5, ?DELEGATE_PREFIX]}, 41 | permanent, ?SUPERVISOR_WAIT, supervisor, [delegate_sup]}, 42 | MC = [{rabbit_mgmt_metrics_collector:name(Table), 43 | {rabbit_mgmt_metrics_collector, start_link, [Table]}, 44 | permanent, ?WORKER_WAIT, worker, [rabbit_mgmt_metrics_collector]} 45 | || {Table, _} <- ?CORE_TABLES], 46 | MGC = [{rabbit_mgmt_metrics_gc:name(Event), 47 | {rabbit_mgmt_metrics_gc, start_link, [Event]}, 48 | permanent, ?WORKER_WAIT, worker, [rabbit_mgmt_metrics_gc]} 49 | || Event <- ?GC_EVENTS], 50 | GC = {rabbit_mgmt_gc, {rabbit_mgmt_gc, start_link, []}, 51 | permanent, ?WORKER_WAIT, worker, [rabbit_mgmt_gc]}, 52 | [ST, MD, GC | MC ++ MGC]; 53 | true -> 54 | [] 55 | end. 56 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_agent_sup_sup.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_agent_sup_sup). 9 | 10 | -behaviour(supervisor2). 11 | 12 | -export([init/1]). 13 | -export([start_link/0, start_child/0]). 14 | 15 | -include_lib("rabbit_common/include/rabbit.hrl"). 16 | 17 | start_child() -> 18 | supervisor2:start_child(?MODULE, sup()). 19 | 20 | sup() -> 21 | {rabbit_mgmt_agent_sup, {rabbit_mgmt_agent_sup, start_link, []}, 22 | temporary, ?SUPERVISOR_WAIT, supervisor, [rabbit_mgmt_agent_sup]}. 23 | 24 | init([]) -> 25 | {ok, {{one_for_one, 0, 1}, [sup()]}}. 26 | 27 | start_link() -> 28 | supervisor2:start_link({local, ?MODULE}, ?MODULE, []). 29 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_data.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_data). 9 | 10 | -include("rabbit_mgmt_records.hrl"). 11 | -include("rabbit_mgmt_metrics.hrl"). 12 | -include_lib("rabbit_common/include/rabbit.hrl"). 13 | -include_lib("rabbit_common/include/rabbit_core_metrics.hrl"). 14 | 15 | -export([empty/2, pick_range/2]). 16 | 17 | % delegate api 18 | -export([overview_data/4, 19 | consumer_data/2, 20 | all_list_queue_data/3, 21 | all_detail_queue_data/3, 22 | all_exchange_data/3, 23 | all_connection_data/3, 24 | all_list_channel_data/3, 25 | all_detail_channel_data/3, 26 | all_vhost_data/3, 27 | all_node_data/3, 28 | augmented_created_stats/2, 29 | augmented_created_stats/3, 30 | augment_channel_pids/2, 31 | augment_details/2, 32 | lookup_element/2, 33 | lookup_element/3 34 | ]). 35 | 36 | -import(rabbit_misc, [pget/2]). 37 | 38 | -type maybe_slide() :: exometer_slide:slide() | not_found. 39 | -type ranges() :: {maybe_range(), maybe_range(), maybe_range(), maybe_range()}. 40 | -type maybe_range() :: no_range | #range{}. 41 | 42 | %%---------------------------------------------------------------------------- 43 | %% Internal, query-time - node-local operations 44 | %%---------------------------------------------------------------------------- 45 | 46 | created_stats(Name, Type) -> 47 | case ets:select(Type, [{{'_', '$2', '$3'}, [{'==', Name, '$2'}], ['$3']}]) of 48 | [] -> not_found; 49 | [Elem] -> Elem 50 | end. 51 | 52 | created_stats(Type) -> 53 | %% TODO better tab2list? 54 | ets:select(Type, [{{'_', '_', '$3'}, [], ['$3']}]). 55 | 56 | -spec all_detail_queue_data(pid(), [any()], ranges()) -> #{atom() => any()}. 57 | all_detail_queue_data(_Pid, Ids, Ranges) -> 58 | lists:foldl(fun (Id, Acc) -> 59 | Data = detail_queue_data(Ranges, Id), 60 | maps:put(Id, Data, Acc) 61 | end, #{}, Ids). 62 | 63 | all_list_queue_data(_Pid, Ids, Ranges) -> 64 | lists:foldl(fun (Id, Acc) -> 65 | Data = list_queue_data(Ranges, Id), 66 | maps:put(Id, Data, Acc) 67 | end, #{}, Ids). 68 | 69 | all_detail_channel_data(_Pid, Ids, Ranges) -> 70 | lists:foldl(fun (Id, Acc) -> 71 | Data = detail_channel_data(Ranges, Id), 72 | maps:put(Id, Data, Acc) 73 | end, #{}, Ids). 74 | 75 | all_list_channel_data(_Pid, Ids, Ranges) -> 76 | lists:foldl(fun (Id, Acc) -> 77 | Data = list_channel_data(Ranges, Id), 78 | maps:put(Id, Data, Acc) 79 | end, #{}, Ids). 80 | 81 | connection_data(Ranges, Id) -> 82 | maps:from_list([raw_message_data(connection_stats_coarse_conn_stats, 83 | pick_range(coarse_conn_stats, Ranges), Id), 84 | {connection_stats, lookup_element(connection_stats, Id)}]). 85 | 86 | exchange_data(Ranges, Id) -> 87 | maps:from_list( 88 | exchange_raw_detail_stats_data(Ranges, Id) ++ 89 | [raw_message_data(exchange_stats_publish_out, 90 | pick_range(fine_stats, Ranges), Id), 91 | raw_message_data(exchange_stats_publish_in, 92 | pick_range(fine_stats, Ranges), Id)]). 93 | 94 | vhost_data(Ranges, Id) -> 95 | maps:from_list([raw_message_data(vhost_stats_coarse_conn_stats, 96 | pick_range(coarse_conn_stats, Ranges), Id), 97 | raw_message_data(vhost_msg_stats, 98 | pick_range(queue_msg_rates, Ranges), Id), 99 | raw_message_data(vhost_stats_fine_stats, 100 | pick_range(fine_stats, Ranges), Id), 101 | raw_message_data(vhost_stats_deliver_stats, 102 | pick_range(deliver_get, Ranges), Id)]). 103 | 104 | node_data(Ranges, Id) -> 105 | maps:from_list( 106 | [{mgmt_stats, mgmt_queue_length_stats(Id)}] ++ 107 | [{node_node_metrics, node_node_metrics()}] ++ 108 | node_raw_detail_stats_data(Ranges, Id) ++ 109 | [raw_message_data(node_coarse_stats, 110 | pick_range(coarse_node_stats, Ranges), Id), 111 | raw_message_data(node_persister_stats, 112 | pick_range(coarse_node_stats, Ranges), Id), 113 | {node_stats, lookup_element(node_stats, Id)}] ++ 114 | node_connection_churn_rates_data(Ranges, Id)). 115 | 116 | overview_data(_Pid, User, Ranges, VHosts) -> 117 | Raw = [raw_all_message_data(vhost_msg_stats, pick_range(queue_msg_counts, Ranges), VHosts), 118 | raw_all_message_data(vhost_stats_fine_stats, pick_range(fine_stats, Ranges), VHosts), 119 | raw_all_message_data(vhost_msg_rates, pick_range(queue_msg_rates, Ranges), VHosts), 120 | raw_all_message_data(vhost_stats_deliver_stats, pick_range(deliver_get, Ranges), VHosts), 121 | raw_message_data(connection_churn_rates, pick_range(queue_msg_rates, Ranges), node())], 122 | maps:from_list(Raw ++ 123 | [{connections_count, count_created_stats(connection_created_stats, User)}, 124 | {channels_count, count_created_stats(channel_created_stats, User)}, 125 | {consumers_count, ets:info(consumer_stats, size)}]). 126 | 127 | consumer_data(_Pid, VHost) -> 128 | maps:from_list( 129 | [{C, augment_msg_stats(augment_consumer(C))} 130 | || C <- consumers_by_vhost(VHost)]). 131 | 132 | all_connection_data(_Pid, Ids, Ranges) -> 133 | maps:from_list([{Id, connection_data(Ranges, Id)} || Id <- Ids]). 134 | 135 | all_exchange_data(_Pid, Ids, Ranges) -> 136 | maps:from_list([{Id, exchange_data(Ranges, Id)} || Id <- Ids]). 137 | 138 | all_vhost_data(_Pid, Ids, Ranges) -> 139 | maps:from_list([{Id, vhost_data(Ranges, Id)} || Id <- Ids]). 140 | 141 | all_node_data(_Pid, Ids, Ranges) -> 142 | maps:from_list([{Id, node_data(Ranges, Id)} || Id <- Ids]). 143 | 144 | channel_raw_message_data(Ranges, Id) -> 145 | [raw_message_data(channel_stats_fine_stats, pick_range(fine_stats, Ranges), Id), 146 | raw_message_data(channel_stats_deliver_stats, pick_range(deliver_get, Ranges), Id), 147 | raw_message_data(channel_process_stats, pick_range(process_stats, Ranges), Id)]. 148 | 149 | queue_raw_message_data(Ranges, Id) -> 150 | [raw_message_data(queue_stats_publish, pick_range(fine_stats, Ranges), Id), 151 | raw_message_data(queue_stats_deliver_stats, pick_range(deliver_get, Ranges), Id), 152 | raw_message_data(queue_process_stats, pick_range(process_stats, Ranges), Id), 153 | raw_message_data(queue_msg_stats, pick_range(queue_msg_counts, Ranges), Id)]. 154 | 155 | queue_raw_deliver_stats_data(Ranges, Id) -> 156 | [raw_message_data2(channel_queue_stats_deliver_stats, 157 | pick_range(deliver_get, Ranges), Key) 158 | || Key <- get_table_keys(channel_queue_stats_deliver_stats, second(Id))] ++ 159 | [raw_message_data2(queue_exchange_stats_publish, 160 | pick_range(fine_stats, Ranges), Key) 161 | || Key <- get_table_keys(queue_exchange_stats_publish, first(Id))]. 162 | 163 | node_raw_detail_stats_data(Ranges, Id) -> 164 | [raw_message_data2(node_node_coarse_stats, 165 | pick_range(coarse_node_node_stats, Ranges), Key) 166 | || Key <- get_table_keys(node_node_coarse_stats, first(Id))]. 167 | 168 | node_connection_churn_rates_data(Ranges, Id) -> 169 | [raw_message_data(connection_churn_rates, 170 | pick_range(churn_rates, Ranges), Id)]. 171 | 172 | exchange_raw_detail_stats_data(Ranges, Id) -> 173 | [raw_message_data2(channel_exchange_stats_fine_stats, 174 | pick_range(fine_stats, Ranges), Key) 175 | || Key <- get_table_keys(channel_exchange_stats_fine_stats, second(Id))] ++ 176 | [raw_message_data2(queue_exchange_stats_publish, 177 | pick_range(fine_stats, Ranges), Key) 178 | || Key <- get_table_keys(queue_exchange_stats_publish, second(Id))]. 179 | 180 | channel_raw_detail_stats_data(Ranges, Id) -> 181 | [raw_message_data2(channel_exchange_stats_fine_stats, 182 | pick_range(fine_stats, Ranges), Key) 183 | || Key <- get_table_keys(channel_exchange_stats_fine_stats, first(Id))] ++ 184 | [raw_message_data2(channel_queue_stats_deliver_stats, 185 | pick_range(fine_stats, Ranges), Key) 186 | || Key <- get_table_keys(channel_queue_stats_deliver_stats, first(Id))]. 187 | 188 | raw_message_data2(Table, no_range, Id) -> 189 | SmallSample = lookup_smaller_sample(Table, Id), 190 | {{Table, Id}, {SmallSample, not_found}}; 191 | raw_message_data2(Table, Range, Id) -> 192 | SmallSample = lookup_smaller_sample(Table, Id), 193 | Samples = lookup_samples(Table, Id, Range), 194 | {{Table, Id}, {SmallSample, Samples}}. 195 | 196 | detail_queue_data(Ranges, Id) -> 197 | maps:from_list(queue_raw_message_data(Ranges, Id) ++ 198 | queue_raw_deliver_stats_data(Ranges, Id) ++ 199 | [{queue_stats, lookup_element(queue_stats, Id)}, 200 | {consumer_stats, get_queue_consumer_stats(Id)}]). 201 | 202 | list_queue_data(Ranges, Id) -> 203 | maps:from_list(queue_raw_message_data(Ranges, Id) ++ 204 | queue_raw_deliver_stats_data(Ranges, Id) ++ 205 | [{queue_stats, lookup_element(queue_stats, Id)}]). 206 | 207 | detail_channel_data(Ranges, Id) -> 208 | maps:from_list(channel_raw_message_data(Ranges, Id) ++ 209 | channel_raw_detail_stats_data(Ranges, Id) ++ 210 | [{channel_stats, lookup_element(channel_stats, Id)}, 211 | {consumer_stats, get_consumer_stats(Id)}]). 212 | 213 | list_channel_data(Ranges, Id) -> 214 | maps:from_list(channel_raw_message_data(Ranges, Id) ++ 215 | channel_raw_detail_stats_data(Ranges, Id) ++ 216 | [{channel_stats, lookup_element(channel_stats, Id)}]). 217 | 218 | -spec raw_message_data(atom(), maybe_range(), any()) -> 219 | {atom(), {maybe_slide(), maybe_slide()}}. 220 | raw_message_data(Table, no_range, Id) -> 221 | SmallSample = lookup_smaller_sample(Table, Id), 222 | {Table, {SmallSample, not_found}}; 223 | raw_message_data(Table, Range, Id) -> 224 | SmallSample = lookup_smaller_sample(Table, Id), 225 | Samples = lookup_samples(Table, Id, Range), 226 | {Table, {SmallSample, Samples}}. 227 | 228 | raw_all_message_data(Table, Range, VHosts) -> 229 | SmallSample = lookup_all(Table, VHosts, select_smaller_sample(Table)), 230 | RangeSample = case Range of 231 | no_range -> not_found; 232 | _ -> 233 | lookup_all(Table, VHosts, select_range_sample(Table, 234 | Range)) 235 | end, 236 | {Table, {SmallSample, RangeSample}}. 237 | 238 | get_queue_consumer_stats(Id) -> 239 | Consumers = ets:select(consumer_stats, match_queue_consumer_spec(Id)), 240 | [augment_consumer(C) || C <- Consumers]. 241 | 242 | get_consumer_stats(Id) -> 243 | Consumers = ets:select(consumer_stats, match_consumer_spec(Id)), 244 | [augment_consumer(C) || C <- Consumers]. 245 | 246 | count_created_stats(Type, all) -> 247 | ets:info(Type, size); 248 | count_created_stats(Type, User) -> 249 | length(filter_user(created_stats(Type), User)). 250 | 251 | augment_consumer({{Q, Ch, CTag}, Props}) -> 252 | [{queue, format_resource(Q)}, 253 | {channel_details, augment_channel_pid(Ch)}, 254 | {channel_pid, Ch}, 255 | {consumer_tag, CTag} | Props]. 256 | 257 | consumers_by_vhost(VHost) -> 258 | ets:select(consumer_stats, 259 | [{{{#resource{virtual_host = '$1', _ = '_'}, '_', '_'}, '_'}, 260 | [{'orelse', {'==', 'all', VHost}, {'==', VHost, '$1'}}], 261 | ['$_']}]). 262 | 263 | augment_msg_stats(Props) -> 264 | augment_details(Props, []) ++ Props. 265 | 266 | augment_details([{_, none} | T], Acc) -> 267 | augment_details(T, Acc); 268 | augment_details([{_, unknown} | T], Acc) -> 269 | augment_details(T, Acc); 270 | augment_details([{connection, Value} | T], Acc) -> 271 | augment_details(T, [{connection_details, augment_connection_pid(Value)} | Acc]); 272 | augment_details([{channel, Value} | T], Acc) -> 273 | augment_details(T, [{channel_details, augment_channel_pid(Value)} | Acc]); 274 | augment_details([{owner_pid, Value} | T], Acc) -> 275 | augment_details(T, [{owner_pid_details, augment_connection_pid(Value)} | Acc]); 276 | augment_details([_ | T], Acc) -> 277 | augment_details(T, Acc); 278 | augment_details([], Acc) -> 279 | Acc. 280 | 281 | augment_channel_pids(_Pid, ChPids) -> 282 | lists:map(fun (ChPid) -> augment_channel_pid(ChPid) end, ChPids). 283 | 284 | augment_channel_pid(Pid) -> 285 | Ch = lookup_element(channel_created_stats, Pid, 3), 286 | Conn = lookup_element(connection_created_stats, pget(connection, Ch), 3), 287 | case Conn of 288 | [] -> %% If the connection has just been opened, we might not yet have the data 289 | []; 290 | _ -> 291 | [{name, pget(name, Ch)}, 292 | {pid, pget(pid, Ch)}, 293 | {number, pget(number, Ch)}, 294 | {user, pget(user, Ch)}, 295 | {connection_name, pget(name, Conn)}, 296 | {peer_port, pget(peer_port, Conn)}, 297 | {peer_host, pget(peer_host, Conn)}] 298 | end. 299 | 300 | augment_connection_pid(Pid) -> 301 | Conn = lookup_element(connection_created_stats, Pid, 3), 302 | case Conn of 303 | [] -> %% If the connection has just been opened, we might not yet have the data 304 | []; 305 | _ -> 306 | [{name, pget(name, Conn)}, 307 | {peer_port, pget(peer_port, Conn)}, 308 | {peer_host, pget(peer_host, Conn)}] 309 | end. 310 | 311 | augmented_created_stats(_Pid, Key, Type) -> 312 | case created_stats(Key, Type) of 313 | not_found -> not_found; 314 | S -> augment_msg_stats(S) 315 | end. 316 | 317 | augmented_created_stats(_Pid, Type) -> 318 | [ augment_msg_stats(S) || S <- created_stats(Type) ]. 319 | 320 | match_consumer_spec(Id) -> 321 | [{{{'_', '$1', '_'}, '_'}, [{'==', Id, '$1'}], ['$_']}]. 322 | 323 | match_queue_consumer_spec(Id) -> 324 | [{{{'$1', '_', '_'}, '_'}, [{'==', {Id}, '$1'}], ['$_']}]. 325 | 326 | lookup_element(Table, Key) -> lookup_element(Table, Key, 2). 327 | 328 | lookup_element(Table, Key, Pos) -> 329 | try ets:lookup_element(Table, Key, Pos) 330 | catch error:badarg -> [] 331 | end. 332 | 333 | -spec lookup_smaller_sample(atom(), any()) -> maybe_slide(). 334 | lookup_smaller_sample(Table, Id) -> 335 | case ets:lookup(Table, {Id, select_smaller_sample(Table)}) of 336 | [] -> 337 | not_found; 338 | [{_, Slide}] -> 339 | Slide1 = exometer_slide:optimize(Slide), 340 | maybe_convert_for_compatibility(Table, Slide1) 341 | end. 342 | 343 | -spec lookup_samples(atom(), any(), #range{}) -> maybe_slide(). 344 | lookup_samples(Table, Id, Range) -> 345 | case ets:lookup(Table, {Id, select_range_sample(Table, Range)}) of 346 | [] -> 347 | not_found; 348 | [{_, Slide}] -> 349 | Slide1 = exometer_slide:optimize(Slide), 350 | maybe_convert_for_compatibility(Table, Slide1) 351 | end. 352 | 353 | lookup_all(Table, Ids, SecondKey) -> 354 | Slides = lists:foldl(fun(Id, Acc) -> 355 | case ets:lookup(Table, {Id, SecondKey}) of 356 | [] -> 357 | Acc; 358 | [{_, Slide}] -> 359 | [Slide | Acc] 360 | end 361 | end, [], Ids), 362 | case Slides of 363 | [] -> 364 | not_found; 365 | _ -> 366 | Slide = exometer_slide:sum(Slides, empty(Table, 0)), 367 | maybe_convert_for_compatibility(Table, Slide) 368 | end. 369 | 370 | maybe_convert_for_compatibility(Table, Slide) 371 | when Table =:= channel_stats_fine_stats orelse 372 | Table =:= channel_exchange_stats_fine_stats orelse 373 | Table =:= vhost_stats_fine_stats -> 374 | ConversionNeeded = rabbit_feature_flags:is_disabled( 375 | drop_unroutable_metric), 376 | case ConversionNeeded of 377 | false -> 378 | Slide; 379 | true -> 380 | %% drop_drop because the metric is named "drop_unroutable" 381 | rabbit_mgmt_data_compat:drop_drop_unroutable_metric(Slide) 382 | end; 383 | maybe_convert_for_compatibility(Table, Slide) 384 | when Table =:= channel_queue_stats_deliver_stats orelse 385 | Table =:= channel_stats_deliver_stats orelse 386 | Table =:= queue_stats_deliver_stats orelse 387 | Table =:= vhost_stats_deliver_stats -> 388 | ConversionNeeded = rabbit_feature_flags:is_disabled( 389 | empty_basic_get_metric), 390 | case ConversionNeeded of 391 | false -> 392 | Slide; 393 | true -> 394 | rabbit_mgmt_data_compat:drop_get_empty_queue_metric(Slide) 395 | end; 396 | maybe_convert_for_compatibility(_, Slide) -> 397 | Slide. 398 | 399 | get_table_keys(Table, Id0) -> 400 | ets:select(Table, match_spec_keys(Id0)). 401 | 402 | match_spec_keys(Id) -> 403 | MatchCondition = to_match_condition(Id), 404 | MatchHead = {{{'$1', '$2'}, '_'}, '_'}, 405 | [{MatchHead, [MatchCondition], [{{'$1', '$2'}}]}]. 406 | 407 | to_match_condition({'_', Id1}) when is_tuple(Id1) -> 408 | {'==', {Id1}, '$2'}; 409 | to_match_condition({'_', Id1}) -> 410 | {'==', Id1, '$2'}; 411 | to_match_condition({Id0, '_'}) when is_tuple(Id0) -> 412 | {'==', {Id0}, '$1'}; 413 | to_match_condition({Id0, '_'}) -> 414 | {'==', Id0, '$1'}. 415 | 416 | mgmt_queue_length_stats(Id) when Id =:= node() -> 417 | GCsQueueLengths = lists:map(fun (T) -> 418 | case whereis(rabbit_mgmt_metrics_gc:name(T)) of 419 | P when is_pid(P) -> 420 | {message_queue_len, Len} = 421 | erlang:process_info(P, message_queue_len), 422 | {T, Len}; 423 | _ -> {T, 0} 424 | end 425 | end, 426 | ?GC_EVENTS), 427 | [{metrics_gc_queue_length, GCsQueueLengths}]; 428 | mgmt_queue_length_stats(_Id) -> 429 | % if it isn't for the current node just return an empty list 430 | []. 431 | 432 | node_node_metrics() -> 433 | maps:from_list(ets:tab2list(node_node_metrics)). 434 | 435 | select_range_sample(Table, #range{first = First, last = Last}) -> 436 | Range = Last - First, 437 | Policies = rabbit_mgmt_agent_config:get_env(sample_retention_policies), 438 | Policy = retention_policy(Table), 439 | [T | _] = TablePolicies = lists:sort(proplists:get_value(Policy, Policies)), 440 | {_, Sample} = select_smallest_above(T, TablePolicies, Range), 441 | Sample. 442 | 443 | select_smaller_sample(Table) -> 444 | Policies = rabbit_mgmt_agent_config:get_env(sample_retention_policies), 445 | Policy = retention_policy(Table), 446 | TablePolicies = proplists:get_value(Policy, Policies), 447 | [V | _] = lists:sort([I || {_, I} <- TablePolicies]), 448 | V. 449 | 450 | select_smallest_above(V, [], _) -> 451 | V; 452 | select_smallest_above(_, [{H, _} = S | _T], Interval) when (H * 1000) > Interval -> 453 | S; 454 | select_smallest_above(_, [H | T], Interval) -> 455 | select_smallest_above(H, T, Interval). 456 | 457 | pick_range(queue_msg_counts, {RangeL, _RangeM, _RangeD, _RangeN}) -> 458 | RangeL; 459 | pick_range(K, {_RangeL, RangeM, _RangeD, _RangeN}) when K == fine_stats; 460 | K == deliver_get; 461 | K == queue_msg_rates -> 462 | RangeM; 463 | pick_range(K, {_RangeL, _RangeM, RangeD, _RangeN}) when K == coarse_conn_stats; 464 | K == process_stats -> 465 | RangeD; 466 | pick_range(K, {_RangeL, _RangeM, _RangeD, RangeN}) 467 | when K == coarse_node_stats; 468 | K == coarse_node_node_stats; 469 | K == churn_rates -> 470 | RangeN. 471 | 472 | first(Id) -> 473 | {Id, '_'}. 474 | 475 | second(Id) -> 476 | {'_', Id}. 477 | 478 | empty(Type, V) when Type =:= connection_stats_coarse_conn_stats; 479 | Type =:= queue_msg_stats; 480 | Type =:= vhost_msg_stats -> 481 | {V, V, V}; 482 | empty(Type, V) when Type =:= channel_stats_fine_stats; 483 | Type =:= channel_exchange_stats_fine_stats; 484 | Type =:= vhost_stats_fine_stats -> 485 | {V, V, V, V}; 486 | empty(Type, V) when Type =:= channel_queue_stats_deliver_stats; 487 | Type =:= queue_stats_deliver_stats; 488 | Type =:= vhost_stats_deliver_stats; 489 | Type =:= channel_stats_deliver_stats -> 490 | {V, V, V, V, V, V, V, V}; 491 | empty(Type, V) when Type =:= channel_process_stats; 492 | Type =:= queue_process_stats; 493 | Type =:= queue_stats_publish; 494 | Type =:= queue_exchange_stats_publish; 495 | Type =:= exchange_stats_publish_out; 496 | Type =:= exchange_stats_publish_in -> 497 | {V}; 498 | empty(node_coarse_stats, V) -> 499 | {V, V, V, V, V, V, V, V}; 500 | empty(node_persister_stats, V) -> 501 | {V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}; 502 | empty(Type, V) when Type =:= node_node_coarse_stats; 503 | Type =:= vhost_stats_coarse_conn_stats; 504 | Type =:= queue_msg_rates; 505 | Type =:= vhost_msg_rates -> 506 | {V, V}; 507 | empty(connection_churn_rates, V) -> 508 | {V, V, V, V, V, V, V}. 509 | 510 | retention_policy(connection_stats_coarse_conn_stats) -> 511 | basic; 512 | retention_policy(channel_stats_fine_stats) -> 513 | basic; 514 | retention_policy(channel_queue_stats_deliver_stats) -> 515 | detailed; 516 | retention_policy(channel_exchange_stats_fine_stats) -> 517 | detailed; 518 | retention_policy(channel_process_stats) -> 519 | basic; 520 | retention_policy(vhost_stats_fine_stats) -> 521 | global; 522 | retention_policy(vhost_stats_deliver_stats) -> 523 | global; 524 | retention_policy(vhost_stats_coarse_conn_stats) -> 525 | global; 526 | retention_policy(vhost_msg_rates) -> 527 | global; 528 | retention_policy(channel_stats_deliver_stats) -> 529 | basic; 530 | retention_policy(queue_stats_deliver_stats) -> 531 | basic; 532 | retention_policy(queue_stats_publish) -> 533 | basic; 534 | retention_policy(queue_exchange_stats_publish) -> 535 | basic; 536 | retention_policy(exchange_stats_publish_out) -> 537 | basic; 538 | retention_policy(exchange_stats_publish_in) -> 539 | basic; 540 | retention_policy(queue_process_stats) -> 541 | basic; 542 | retention_policy(queue_msg_stats) -> 543 | basic; 544 | retention_policy(queue_msg_rates) -> 545 | basic; 546 | retention_policy(vhost_msg_stats) -> 547 | global; 548 | retention_policy(node_coarse_stats) -> 549 | global; 550 | retention_policy(node_persister_stats) -> 551 | global; 552 | retention_policy(node_node_coarse_stats) -> 553 | global; 554 | retention_policy(connection_churn_rates) -> 555 | global. 556 | 557 | format_resource(unknown) -> unknown; 558 | format_resource(Res) -> format_resource(name, Res). 559 | 560 | format_resource(_, unknown) -> 561 | unknown; 562 | format_resource(NameAs, #resource{name = Name, virtual_host = VHost}) -> 563 | [{NameAs, Name}, {vhost, VHost}]. 564 | 565 | filter_user(List, #user{username = Username, tags = Tags}) -> 566 | case is_monitor(Tags) of 567 | true -> List; 568 | false -> [I || I <- List, pget(user, I) == Username] 569 | end. 570 | 571 | is_monitor(T) -> intersects(T, [administrator, monitoring]). 572 | intersects(A, B) -> lists:any(fun(I) -> lists:member(I, B) end, A). 573 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_data_compat.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2018-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_data_compat). 9 | 10 | -export([fill_get_empty_queue_metric/1, 11 | drop_get_empty_queue_metric/1, 12 | fill_consumer_active_fields/1, 13 | fill_drop_unroutable_metric/1, 14 | drop_drop_unroutable_metric/1]). 15 | 16 | fill_get_empty_queue_metric(Slide) -> 17 | exometer_slide:map( 18 | fun 19 | (Value) when is_tuple(Value) andalso size(Value) =:= 8 -> 20 | Value; 21 | (Value) when is_tuple(Value) andalso size(Value) =:= 7 -> 22 | %% Inject a 0 for the new metric 23 | list_to_tuple( 24 | tuple_to_list(Value) ++ [0]); 25 | (Value) -> 26 | Value 27 | end, Slide). 28 | 29 | drop_get_empty_queue_metric(Slide) -> 30 | exometer_slide:map( 31 | fun 32 | (Value) when is_tuple(Value) andalso size(Value) =:= 8 -> 33 | %% We want to remove the last element, which is 34 | %% the count of basic.get on empty queues. 35 | list_to_tuple( 36 | lists:sublist( 37 | tuple_to_list(Value), size(Value) - 1)); 38 | (Value) when is_tuple(Value) andalso size(Value) =:= 7 -> 39 | Value; 40 | (Value) -> 41 | Value 42 | end, Slide). 43 | 44 | fill_drop_unroutable_metric(Slide) -> 45 | exometer_slide:map( 46 | fun 47 | (Value) when is_tuple(Value) andalso size(Value) =:= 4 -> 48 | Value; 49 | (Value) when is_tuple(Value) andalso size(Value) =:= 3 -> 50 | %% Inject a 0 51 | list_to_tuple( 52 | tuple_to_list(Value) ++ [0]); 53 | (Value) -> 54 | Value 55 | end, Slide). 56 | 57 | drop_drop_unroutable_metric(Slide) -> 58 | exometer_slide:map( 59 | fun 60 | (Value) when is_tuple(Value) andalso size(Value) =:= 4 -> 61 | %% Remove the last element. 62 | list_to_tuple( 63 | lists:sublist( 64 | tuple_to_list(Value), size(Value) - 1)); 65 | (Value) when is_tuple(Value) andalso size(Value) =:= 3 -> 66 | Value; 67 | (Value) -> 68 | Value 69 | end, Slide). 70 | 71 | fill_consumer_active_fields(ConsumersStats) -> 72 | [case proplists:get_value(active, ConsumerStats) of 73 | undefined -> 74 | [{active, true}, 75 | {activity_status, up} 76 | | ConsumerStats]; 77 | _ -> 78 | ConsumerStats 79 | end 80 | || ConsumerStats <- ConsumersStats]. 81 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_db_handler.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_db_handler). 9 | 10 | -include_lib("rabbit_common/include/rabbit.hrl"). 11 | 12 | %% Make sure our database is hooked in *before* listening on the network or 13 | %% recovering queues (i.e. so there can't be any events fired before it starts). 14 | -rabbit_boot_step({rabbit_mgmt_db_handler, 15 | [{description, "management agent"}, 16 | {mfa, {?MODULE, add_handler, []}}, 17 | {cleanup, {gen_event, delete_handler, 18 | [rabbit_event, ?MODULE, []]}}, 19 | {requires, rabbit_event}, 20 | {enables, recovery}]}). 21 | 22 | -behaviour(gen_event). 23 | 24 | -export([add_handler/0, gc/0, rates_mode/0]). 25 | 26 | -export([init/1, handle_call/2, handle_event/2, handle_info/2, 27 | terminate/2, code_change/3]). 28 | 29 | %%---------------------------------------------------------------------------- 30 | 31 | add_handler() -> 32 | ok = ensure_statistics_enabled(), 33 | gen_event:add_handler(rabbit_event, ?MODULE, []). 34 | 35 | gc() -> 36 | erlang:garbage_collect(whereis(rabbit_event)). 37 | 38 | rates_mode() -> 39 | case rabbit_mgmt_agent_config:get_env(rates_mode) of 40 | undefined -> basic; 41 | Mode -> Mode 42 | end. 43 | 44 | handle_force_fine_statistics() -> 45 | case rabbit_mgmt_agent_config:get_env(force_fine_statistics) of 46 | undefined -> 47 | ok; 48 | X -> 49 | rabbit_log:warning( 50 | "force_fine_statistics set to ~p; ignored.~n" 51 | "Replaced by {rates_mode, none} in the rabbitmq_management " 52 | "application.~n", [X]) 53 | end. 54 | 55 | %%---------------------------------------------------------------------------- 56 | 57 | ensure_statistics_enabled() -> 58 | ForceStats = rates_mode() =/= none, 59 | handle_force_fine_statistics(), 60 | {ok, StatsLevel} = application:get_env(rabbit, collect_statistics), 61 | rabbit_log:info("Management plugin: using rates mode '~p'~n", [rates_mode()]), 62 | case {ForceStats, StatsLevel} of 63 | {true, fine} -> 64 | ok; 65 | {true, _} -> 66 | application:set_env(rabbit, collect_statistics, fine); 67 | {false, none} -> 68 | application:set_env(rabbit, collect_statistics, coarse); 69 | {_, _} -> 70 | ok 71 | end, 72 | ok = rabbit:force_event_refresh(erlang:make_ref()). 73 | 74 | %%---------------------------------------------------------------------------- 75 | 76 | init([]) -> 77 | {ok, []}. 78 | 79 | handle_call(_Request, State) -> 80 | {ok, not_understood, State}. 81 | 82 | handle_event(#event{type = Type} = Event, State) 83 | when Type == connection_closed; Type == channel_closed; Type == queue_deleted; 84 | Type == exchange_deleted; Type == vhost_deleted; 85 | Type == consumer_deleted; Type == node_node_deleted; 86 | Type == channel_consumer_deleted -> 87 | gen_server:cast(rabbit_mgmt_metrics_gc:name(Type), {event, Event}), 88 | {ok, State}; 89 | handle_event(_, State) -> 90 | {ok, State}. 91 | 92 | handle_info(_Info, State) -> 93 | {ok, State}. 94 | 95 | terminate(_Arg, _State) -> 96 | ok. 97 | 98 | code_change(_OldVsn, State, _Extra) -> 99 | {ok, State}. 100 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_external_stats.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_external_stats). 9 | 10 | %% Transitional step until we can require Erlang/OTP 21 and 11 | %% use the now recommended try/catch syntax for obtaining the stack trace. 12 | -compile(nowarn_deprecated_function). 13 | 14 | -behaviour(gen_server). 15 | 16 | -export([start_link/0]). 17 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 18 | code_change/3]). 19 | 20 | -export([list_registry_plugins/1]). 21 | 22 | -import(rabbit_misc, [pget/2]). 23 | 24 | -include_lib("rabbit_common/include/rabbit.hrl"). 25 | 26 | -define(METRICS_KEYS, [fd_used, sockets_used, mem_used, disk_free, proc_used, gc_num, 27 | gc_bytes_reclaimed, context_switches]). 28 | 29 | -define(PERSISTER_KEYS, [persister_stats]). 30 | 31 | -define(OTHER_KEYS, [name, partitions, os_pid, fd_total, sockets_total, mem_limit, 32 | mem_alarm, disk_free_limit, disk_free_alarm, proc_total, 33 | rates_mode, uptime, run_queue, processors, exchange_types, 34 | auth_mechanisms, applications, contexts, log_files, 35 | db_dir, config_files, net_ticktime, enabled_plugins, 36 | mem_calculation_strategy, ra_open_file_metrics]). 37 | 38 | -define(TEN_MINUTES_AS_SECONDS, 600). 39 | 40 | %%-------------------------------------------------------------------- 41 | 42 | -record(state, { 43 | fd_total, 44 | fhc_stats, 45 | node_owners, 46 | last_ts, 47 | interval, 48 | error_logged_time 49 | }). 50 | 51 | %%-------------------------------------------------------------------- 52 | 53 | start_link() -> 54 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 55 | 56 | %%-------------------------------------------------------------------- 57 | 58 | get_used_fd(State0) -> 59 | try 60 | case get_used_fd(os:type(), State0) of 61 | {State1, UsedFd} when is_number(UsedFd) -> 62 | {State1, UsedFd}; 63 | {State1, _Other} -> 64 | %% Defaults to 0 if data is not available 65 | {State1, 0} 66 | end 67 | catch 68 | _:Error -> 69 | State2 = log_fd_error("Could not infer the number of file handles used: ~p~n", [Error], State0), 70 | {State2, 0} 71 | end. 72 | 73 | get_used_fd({unix, linux}, State0) -> 74 | case file:list_dir("/proc/" ++ os:getpid() ++ "/fd") of 75 | {ok, Files} -> 76 | {State0, length(Files)}; 77 | {error, _} -> 78 | get_used_fd({unix, generic}, State0) 79 | end; 80 | 81 | get_used_fd({unix, BSD}, State0) 82 | when BSD == openbsd; BSD == freebsd; BSD == netbsd -> 83 | IsDigit = fun (D) -> lists:member(D, "0123456789*") end, 84 | Output = os:cmd("fstat -p " ++ os:getpid()), 85 | try 86 | F = fun (Line) -> 87 | lists:all(IsDigit, lists:nth(4, string:tokens(Line, " "))) 88 | end, 89 | UsedFd = length(lists:filter(F, string:tokens(Output, "\n"))), 90 | {State0, UsedFd} 91 | catch _:Error:Stacktrace -> 92 | State1 = log_fd_error("Could not parse fstat output:~n~s~n~p~n", 93 | [Output, {Error, Stacktrace}], State0), 94 | {State1, 0} 95 | end; 96 | 97 | get_used_fd({unix, _}, State0) -> 98 | Cmd = rabbit_misc:format( 99 | "lsof -d \"0-9999999\" -lna -p ~s || echo failed", [os:getpid()]), 100 | Res = os:cmd(Cmd), 101 | case string:right(Res, 7) of 102 | "failed\n" -> 103 | State1 = log_fd_error("Could not obtain lsof output~n", [], State0), 104 | {State1, 0}; 105 | _ -> 106 | UsedFd = string:words(Res, $\n) - 1, 107 | {State0, UsedFd} 108 | end; 109 | 110 | %% handle.exe can be obtained from 111 | %% https://technet.microsoft.com/en-us/sysinternals/bb896655.aspx 112 | 113 | %% Output looks like: 114 | 115 | %% Handle v3.42 116 | %% Copyright (C) 1997-2008 Mark Russinovich 117 | %% Sysinternals - www.sysinternals.com 118 | %% 119 | %% Handle type summary: 120 | %% ALPC Port : 2 121 | %% Desktop : 1 122 | %% Directory : 1 123 | %% Event : 108 124 | %% File : 25 125 | %% IoCompletion : 3 126 | %% Key : 7 127 | %% KeyedEvent : 1 128 | %% Mutant : 1 129 | %% Process : 3 130 | %% Process : 38 131 | %% Thread : 41 132 | %% Timer : 3 133 | %% TpWorkerFactory : 2 134 | %% WindowStation : 2 135 | %% Total handles: 238 136 | 137 | %% Nthandle v4.22 - Handle viewer 138 | %% Copyright (C) 1997-2019 Mark Russinovich 139 | %% Sysinternals - www.sysinternals.com 140 | %% 141 | %% Handle type summary: 142 | %% : 1 143 | %% : 166 144 | %% ALPC Port : 11 145 | %% Desktop : 1 146 | %% Directory : 2 147 | %% Event : 226 148 | %% File : 122 149 | %% IoCompletion : 8 150 | %% IRTimer : 6 151 | %% Key : 42 152 | %% Mutant : 7 153 | %% Process : 3 154 | %% Section : 2 155 | %% Semaphore : 43 156 | %% Thread : 36 157 | %% TpWorkerFactory : 3 158 | %% WaitCompletionPacket: 25 159 | %% WindowStation : 2 160 | %% Total handles: 706 161 | 162 | %% Note that the "File" number appears to include network sockets too; I assume 163 | %% that's the number we care about. Note also that if you omit "-s" you will 164 | %% see a list of file handles *without* network sockets. If you then add "-a" 165 | %% you will see a list of handles of various types, including network sockets 166 | %% shown as file handles to \Device\Afd. 167 | 168 | get_used_fd({win32, _}, State0) -> 169 | Handle = rabbit_misc:os_cmd( 170 | "handle.exe /accepteula -s -p " ++ os:getpid() ++ " 2> nul"), 171 | case Handle of 172 | [] -> 173 | State1 = log_fd_error("Could not find handle.exe, please install from sysinternals~n", [], State0), 174 | {State1, 0}; 175 | _ -> 176 | case find_files_line(string:tokens(Handle, "\r\n")) of 177 | unknown -> 178 | State1 = log_fd_error("handle.exe output did not contain " 179 | "a line beginning with ' File ', unable " 180 | "to determine used file descriptor " 181 | "count: ~p~n", [Handle], State0), 182 | {State1, 0}; 183 | UsedFd -> 184 | {State0, UsedFd} 185 | end 186 | end. 187 | 188 | find_files_line([]) -> 189 | unknown; 190 | find_files_line([" File " ++ Rest | _T]) -> 191 | [Files] = string:tokens(Rest, ": "), 192 | list_to_integer(Files); 193 | find_files_line([_H | T]) -> 194 | find_files_line(T). 195 | 196 | -define(SAFE_CALL(Fun, NoProcFailResult), 197 | try 198 | Fun 199 | catch exit:{noproc, _} -> NoProcFailResult 200 | end). 201 | 202 | get_disk_free_limit() -> ?SAFE_CALL(rabbit_disk_monitor:get_disk_free_limit(), 203 | disk_free_monitoring_disabled). 204 | 205 | get_disk_free() -> ?SAFE_CALL(rabbit_disk_monitor:get_disk_free(), 206 | disk_free_monitoring_disabled). 207 | 208 | log_fd_error(Fmt, Args, #state{error_logged_time = undefined}=State) -> 209 | % rabbitmq/rabbitmq-management#90 210 | % no errors have been logged, so log it and make a note of when 211 | Now = erlang:monotonic_time(second), 212 | ok = rabbit_log:error(Fmt, Args), 213 | State#state{error_logged_time = Now}; 214 | log_fd_error(Fmt, Args, #state{error_logged_time = Time}=State) -> 215 | Now = erlang:monotonic_time(second), 216 | case Now >= Time + ?TEN_MINUTES_AS_SECONDS of 217 | true -> 218 | % rabbitmq/rabbitmq-management#90 219 | % it has been longer than 10 minutes, 220 | % re-log the error 221 | ok = rabbit_log:error(Fmt, Args), 222 | State#state{error_logged_time = Now}; 223 | _ -> 224 | % 10 minutes have not yet passed 225 | State 226 | end. 227 | %%-------------------------------------------------------------------- 228 | 229 | infos([], Acc, State) -> 230 | {State, lists:reverse(Acc)}; 231 | infos([Item|T], Acc0, State0) -> 232 | {State1, Infos} = i(Item, State0), 233 | Acc1 = [{Item, Infos}|Acc0], 234 | infos(T, Acc1, State1). 235 | 236 | i(name, State) -> 237 | {State, node()}; 238 | i(partitions, State) -> 239 | {State, rabbit_node_monitor:partitions()}; 240 | i(fd_used, State) -> 241 | get_used_fd(State); 242 | i(fd_total, #state{fd_total = FdTotal}=State) -> 243 | {State, FdTotal}; 244 | i(sockets_used, State) -> 245 | {State, proplists:get_value(sockets_used, file_handle_cache:info([sockets_used]))}; 246 | i(sockets_total, State) -> 247 | {State, proplists:get_value(sockets_limit, file_handle_cache:info([sockets_limit]))}; 248 | i(os_pid, State) -> 249 | {State, list_to_binary(os:getpid())}; 250 | i(mem_used, State) -> 251 | {State, vm_memory_monitor:get_process_memory()}; 252 | i(mem_calculation_strategy, State) -> 253 | {State, vm_memory_monitor:get_memory_calculation_strategy()}; 254 | i(mem_limit, State) -> 255 | {State, vm_memory_monitor:get_memory_limit()}; 256 | i(mem_alarm, State) -> 257 | {State, resource_alarm_set(memory)}; 258 | i(proc_used, State) -> 259 | {State, erlang:system_info(process_count)}; 260 | i(proc_total, State) -> 261 | {State, erlang:system_info(process_limit)}; 262 | i(run_queue, State) -> 263 | {State, erlang:statistics(run_queue)}; 264 | i(processors, State) -> 265 | {State, erlang:system_info(logical_processors)}; 266 | i(disk_free_limit, State) -> 267 | {State, get_disk_free_limit()}; 268 | i(disk_free, State) -> 269 | {State, get_disk_free()}; 270 | i(disk_free_alarm, State) -> 271 | {State, resource_alarm_set(disk)}; 272 | i(contexts, State) -> 273 | {State, rabbit_web_dispatch_contexts()}; 274 | i(uptime, State) -> 275 | {Total, _} = erlang:statistics(wall_clock), 276 | {State, Total}; 277 | i(rates_mode, State) -> 278 | {State, rabbit_mgmt_db_handler:rates_mode()}; 279 | i(exchange_types, State) -> 280 | {State, list_registry_plugins(exchange)}; 281 | i(log_files, State) -> 282 | {State, [list_to_binary(F) || F <- rabbit:log_locations()]}; 283 | i(db_dir, State) -> 284 | {State, list_to_binary(rabbit_mnesia:dir())}; 285 | i(config_files, State) -> 286 | {State, [list_to_binary(F) || F <- rabbit:config_files()]}; 287 | i(net_ticktime, State) -> 288 | {State, net_kernel:get_net_ticktime()}; 289 | i(persister_stats, State) -> 290 | {State, persister_stats(State)}; 291 | i(enabled_plugins, State) -> 292 | {ok, Dir} = application:get_env(rabbit, enabled_plugins_file), 293 | {State, rabbit_plugins:read_enabled(Dir)}; 294 | i(auth_mechanisms, State) -> 295 | {ok, Mechanisms} = application:get_env(rabbit, auth_mechanisms), 296 | F = fun (N) -> 297 | lists:member(list_to_atom(binary_to_list(N)), Mechanisms) 298 | end, 299 | {State, list_registry_plugins(auth_mechanism, F)}; 300 | i(applications, State) -> 301 | {State, [format_application(A) || A <- lists:keysort(1, rabbit_misc:which_applications())]}; 302 | i(gc_num, State) -> 303 | {GCs, _, _} = erlang:statistics(garbage_collection), 304 | {State, GCs}; 305 | i(gc_bytes_reclaimed, State) -> 306 | {_, Words, _} = erlang:statistics(garbage_collection), 307 | {State, Words * erlang:system_info(wordsize)}; 308 | i(context_switches, State) -> 309 | {Sw, 0} = erlang:statistics(context_switches), 310 | {State, Sw}; 311 | i(ra_open_file_metrics, State) -> 312 | {State, [{ra_log_wal, ra_metrics(ra_log_wal)}, 313 | {ra_log_segment_writer, ra_metrics(ra_log_segment_writer)}]}. 314 | 315 | ra_metrics(K) -> 316 | try 317 | case ets:lookup(ra_open_file_metrics, whereis(K)) of 318 | [] -> 0; 319 | [{_, C}] -> C 320 | end 321 | catch 322 | error:badarg -> 323 | %% On startup the mgmt might start before ra does 324 | 0 325 | end. 326 | 327 | resource_alarm_set(Source) -> 328 | lists:member({{resource_limit, Source, node()},[]}, 329 | rabbit_alarm:get_alarms()). 330 | 331 | list_registry_plugins(Type) -> 332 | list_registry_plugins(Type, fun(_) -> true end). 333 | 334 | list_registry_plugins(Type, Fun) -> 335 | [registry_plugin_enabled(set_plugin_name(Name, Module), Fun) || 336 | {Name, Module} <- rabbit_registry:lookup_all(Type)]. 337 | 338 | registry_plugin_enabled(Desc, Fun) -> 339 | Desc ++ [{enabled, Fun(proplists:get_value(name, Desc))}]. 340 | 341 | format_application({Application, Description, Version}) -> 342 | [{name, Application}, 343 | {description, list_to_binary(Description)}, 344 | {version, list_to_binary(Version)}]. 345 | 346 | set_plugin_name(Name, Module) -> 347 | [{name, list_to_binary(atom_to_list(Name))} | 348 | proplists:delete(name, Module:description())]. 349 | 350 | persister_stats(#state{fhc_stats = FHC}) -> 351 | [{flatten_key(K), V} || {{_Op, _Type} = K, V} <- FHC]. 352 | 353 | flatten_key({A, B}) -> 354 | list_to_atom(atom_to_list(A) ++ "_" ++ atom_to_list(B)). 355 | 356 | cluster_links() -> 357 | {ok, Items} = net_kernel:nodes_info(), 358 | [Link || Item <- Items, 359 | Link <- [format_nodes_info(Item)], Link =/= undefined]. 360 | 361 | format_nodes_info({Node, Info}) -> 362 | Owner = proplists:get_value(owner, Info), 363 | case catch process_info(Owner, links) of 364 | {links, Links} -> 365 | case [Link || Link <- Links, is_port(Link)] of 366 | [Port] -> 367 | {Node, Owner, format_nodes_info1(Port)}; 368 | _ -> 369 | undefined 370 | end; 371 | _ -> 372 | undefined 373 | end. 374 | 375 | format_nodes_info1(Port) -> 376 | case {rabbit_net:socket_ends(Port, inbound), 377 | rabbit_net:getstat(Port, [recv_oct, send_oct])} of 378 | {{ok, {PeerAddr, PeerPort, SockAddr, SockPort}}, {ok, Stats}} -> 379 | [{peer_addr, maybe_ntoab(PeerAddr)}, 380 | {peer_port, PeerPort}, 381 | {sock_addr, maybe_ntoab(SockAddr)}, 382 | {sock_port, SockPort}, 383 | {recv_bytes, pget(recv_oct, Stats)}, 384 | {send_bytes, pget(send_oct, Stats)}]; 385 | _ -> 386 | [] 387 | end. 388 | 389 | maybe_ntoab(A) when is_tuple(A) -> list_to_binary(rabbit_misc:ntoab(A)); 390 | maybe_ntoab(H) -> H. 391 | 392 | %%-------------------------------------------------------------------- 393 | 394 | %% This is slightly icky in that we introduce knowledge of 395 | %% rabbit_web_dispatch, which is not a dependency. But the last thing I 396 | %% want to do is create a rabbitmq_mochiweb_management_agent plugin. 397 | rabbit_web_dispatch_contexts() -> 398 | [format_context(C) || C <- rabbit_web_dispatch_registry_list_all()]. 399 | 400 | %% For similar reasons we don't declare a dependency on 401 | %% rabbitmq_mochiweb - so at startup there's no guarantee it will be 402 | %% running. So we have to catch this noproc. 403 | rabbit_web_dispatch_registry_list_all() -> 404 | case code:is_loaded(rabbit_web_dispatch_registry) of 405 | false -> []; 406 | _ -> try 407 | M = rabbit_web_dispatch_registry, %% Fool xref 408 | M:list_all() 409 | catch exit:{noproc, _} -> 410 | [] 411 | end 412 | end. 413 | 414 | format_context({Path, Description, Rest}) -> 415 | [{description, list_to_binary(Description)}, 416 | {path, list_to_binary("/" ++ Path)} | 417 | format_mochiweb_option_list(Rest)]. 418 | 419 | format_mochiweb_option_list(C) -> 420 | [{K, format_mochiweb_option(K, V)} || {K, V} <- C]. 421 | 422 | format_mochiweb_option(ssl_opts, V) -> 423 | format_mochiweb_option_list(V); 424 | format_mochiweb_option(_K, V) -> 425 | case io_lib:printable_list(V) of 426 | true -> list_to_binary(V); 427 | false -> list_to_binary(rabbit_misc:format("~w", [V])) 428 | end. 429 | 430 | %%-------------------------------------------------------------------- 431 | 432 | init([]) -> 433 | {ok, Interval} = application:get_env(rabbit, collect_statistics_interval), 434 | State = #state{fd_total = file_handle_cache:ulimit(), 435 | fhc_stats = get_fhc_stats(), 436 | node_owners = sets:new(), 437 | interval = Interval}, 438 | %% We can update stats straight away as they need to be available 439 | %% when the mgmt plugin starts a collector 440 | {ok, emit_update(State)}. 441 | 442 | handle_call(_Req, _From, State) -> 443 | {reply, unknown_request, State}. 444 | 445 | handle_cast(_C, State) -> 446 | {noreply, State}. 447 | 448 | handle_info(emit_update, State) -> 449 | {noreply, emit_update(State)}; 450 | 451 | handle_info(_I, State) -> 452 | {noreply, State}. 453 | 454 | terminate(_, _) -> ok. 455 | 456 | code_change(_, State, _) -> {ok, State}. 457 | 458 | %%-------------------------------------------------------------------- 459 | 460 | emit_update(State0) -> 461 | State1 = update_state(State0), 462 | {State2, MStats} = infos(?METRICS_KEYS, [], State1), 463 | {State3, PStats} = infos(?PERSISTER_KEYS, [], State2), 464 | {State4, OStats} = infos(?OTHER_KEYS, [], State3), 465 | [{persister_stats, PStats0}] = PStats, 466 | [{name, _Name} | OStats0] = OStats, 467 | rabbit_core_metrics:node_stats(persister_metrics, PStats0), 468 | rabbit_core_metrics:node_stats(coarse_metrics, MStats), 469 | rabbit_core_metrics:node_stats(node_metrics, OStats0), 470 | rabbit_event:notify(node_stats, PStats ++ MStats ++ OStats), 471 | erlang:send_after(State4#state.interval, self(), emit_update), 472 | emit_node_node_stats(State4). 473 | 474 | emit_node_node_stats(State = #state{node_owners = Owners}) -> 475 | Links = cluster_links(), 476 | NewOwners = sets:from_list([{Node, Owner} || {Node, Owner, _} <- Links]), 477 | Dead = sets:to_list(sets:subtract(Owners, NewOwners)), 478 | [rabbit_event:notify( 479 | node_node_deleted, [{route, Route}]) || {Node, _Owner} <- Dead, 480 | Route <- [{node(), Node}, 481 | {Node, node()}]], 482 | [begin 483 | rabbit_core_metrics:node_node_stats({node(), Node}, Stats), 484 | rabbit_event:notify( 485 | node_node_stats, [{route, {node(), Node}} | Stats]) 486 | end || {Node, _Owner, Stats} <- Links], 487 | State#state{node_owners = NewOwners}. 488 | 489 | update_state(State0) -> 490 | %% Store raw data, the average operation time is calculated during querying 491 | %% from the accumulated total 492 | FHC = get_fhc_stats(), 493 | State0#state{fhc_stats = FHC}. 494 | 495 | get_fhc_stats() -> 496 | dict:to_list(dict:merge(fun(_, V1, V2) -> V1 + V2 end, 497 | dict:from_list(file_handle_cache_stats:get()), 498 | dict:from_list(get_ra_io_metrics()))). 499 | 500 | get_ra_io_metrics() -> 501 | lists:sort(ets:tab2list(ra_io_metrics)). 502 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_ff.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2018-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_ff). 9 | 10 | -rabbit_feature_flag( 11 | {empty_basic_get_metric, 12 | #{desc => "Count AMQP `basic.get` on empty queues in stats", 13 | stability => stable 14 | }}). 15 | 16 | -rabbit_feature_flag( 17 | {drop_unroutable_metric, 18 | #{desc => "Count unroutable publishes to be dropped in stats", 19 | stability => stable 20 | }}). 21 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_format.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_format). 9 | 10 | -export([format/2, ip/1, ipb/1, amqp_table/1, tuple/1]). 11 | -export([parameter/1, now_to_str/1, now_to_str_ms/1, strip_pids/1]). 12 | -export([protocol/1, resource/1, queue/1, queue_state/1, queue_info/1]). 13 | -export([exchange/1, user/1, internal_user/1, binding/1, url/2]). 14 | -export([pack_binding_props/2, tokenise/1]). 15 | -export([to_amqp_table/1, listener/1, web_context/1, properties/1, basic_properties/1]). 16 | -export([record/2, to_basic_properties/1]). 17 | -export([addr/1, port/1]). 18 | -export([format_nulls/1, escape_html_tags/1]). 19 | -export([print/2, print/1]). 20 | 21 | -export([format_queue_stats/1, format_channel_stats/1, 22 | format_consumer_arguments/1, 23 | format_connection_created/1, 24 | format_accept_content/1, format_args/1]). 25 | 26 | -export([strip_queue_pids/1]). 27 | 28 | -export([clean_consumer_details/1, clean_channel_details/1]). 29 | 30 | -export([args_hash/1]). 31 | 32 | -import(rabbit_misc, [pget/2, pget/3, pset/3]). 33 | 34 | -include_lib("rabbit_common/include/rabbit.hrl"). 35 | -include_lib("rabbit_common/include/rabbit_framing.hrl"). 36 | -include_lib("rabbit/include/amqqueue.hrl"). 37 | 38 | %%-------------------------------------------------------------------- 39 | 40 | format(Stats, {[], _}) -> 41 | [Stat || {_Name, Value} = Stat <- Stats, Value =/= unknown]; 42 | format(Stats, {Fs, true}) -> 43 | [Fs(Stat) || {_Name, Value} = Stat <- Stats, Value =/= unknown]; 44 | format(Stats, {Fs, false}) -> 45 | lists:concat([Fs(Stat) || {_Name, Value} = Stat <- Stats, 46 | Value =/= unknown]). 47 | 48 | format_queue_stats({reductions, _}) -> 49 | []; 50 | format_queue_stats({exclusive_consumer_pid, _}) -> 51 | []; 52 | format_queue_stats({single_active_consumer_pid, _}) -> 53 | []; 54 | format_queue_stats({slave_pids, ''}) -> 55 | []; 56 | format_queue_stats({slave_pids, Pids}) -> 57 | [{slave_nodes, [node(Pid) || Pid <- Pids]}]; 58 | format_queue_stats({leader, Leader}) -> 59 | [{node, Leader}]; 60 | format_queue_stats({synchronised_slave_pids, ''}) -> 61 | []; 62 | format_queue_stats({effective_policy_definition, []}) -> 63 | [{effective_policy_definition, #{}}]; 64 | format_queue_stats({synchronised_slave_pids, Pids}) -> 65 | [{synchronised_slave_nodes, [node(Pid) || Pid <- Pids]}]; 66 | format_queue_stats({backing_queue_status, Value}) -> 67 | [{backing_queue_status, properties(Value)}]; 68 | format_queue_stats({idle_since, Value}) -> 69 | [{idle_since, now_to_str(Value)}]; 70 | format_queue_stats({state, Value}) -> 71 | queue_state(Value); 72 | format_queue_stats({disk_reads, _}) -> 73 | []; 74 | format_queue_stats({disk_writes, _}) -> 75 | []; 76 | format_queue_stats(Stat) -> 77 | [Stat]. 78 | 79 | format_channel_stats([{idle_since, Value} | Rest]) -> 80 | [{idle_since, now_to_str(Value)} | Rest]; 81 | format_channel_stats(Stats) -> 82 | Stats. 83 | 84 | %% Conerts an HTTP API request payload value 85 | %% to AMQP 0-9-1 arguments table 86 | format_args({arguments, []}) -> 87 | {arguments, []}; 88 | format_args({arguments, Value}) -> 89 | {arguments, to_amqp_table(Value)}; 90 | format_args(Stat) -> 91 | Stat. 92 | 93 | format_connection_created({host, Value}) -> 94 | {host, addr(Value)}; 95 | format_connection_created({peer_host, Value}) -> 96 | {peer_host, addr(Value)}; 97 | format_connection_created({port, Value}) -> 98 | {port, port(Value)}; 99 | format_connection_created({peer_port, Value}) -> 100 | {peer_port, port(Value)}; 101 | format_connection_created({protocol, Value}) -> 102 | {protocol, protocol(Value)}; 103 | format_connection_created({client_properties, Value}) -> 104 | {client_properties, amqp_table(Value)}; 105 | format_connection_created(Stat) -> 106 | Stat. 107 | 108 | format_exchange_and_queue({policy, Value}) -> 109 | policy(Value); 110 | format_exchange_and_queue({arguments, Value}) -> 111 | [{arguments, amqp_table(Value)}]; 112 | format_exchange_and_queue({name, Value}) -> 113 | resource(Value); 114 | format_exchange_and_queue(Stat) -> 115 | [Stat]. 116 | 117 | format_binding({source, Value}) -> 118 | resource(source, Value); 119 | format_binding({arguments, Value}) -> 120 | [{arguments, amqp_table(Value)}]; 121 | format_binding(Stat) -> 122 | [Stat]. 123 | 124 | format_basic_properties({headers, Value}) -> 125 | {headers, amqp_table(Value)}; 126 | format_basic_properties(Stat) -> 127 | Stat. 128 | 129 | format_accept_content({durable, Value}) -> 130 | {durable, parse_bool(Value)}; 131 | format_accept_content({auto_delete, Value}) -> 132 | {auto_delete, parse_bool(Value)}; 133 | format_accept_content({internal, Value}) -> 134 | {internal, parse_bool(Value)}; 135 | format_accept_content(Stat) -> 136 | Stat. 137 | 138 | print(Fmt, Val) when is_list(Val) -> 139 | list_to_binary(lists:flatten(io_lib:format(Fmt, Val))); 140 | print(Fmt, Val) -> 141 | print(Fmt, [Val]). 142 | 143 | print(Val) when is_list(Val) -> 144 | list_to_binary(lists:flatten(Val)); 145 | print(Val) -> 146 | Val. 147 | 148 | %% TODO - can we remove all these "unknown" cases? Coverage never hits them. 149 | 150 | ip(unknown) -> unknown; 151 | ip(IP) -> list_to_binary(rabbit_misc:ntoa(IP)). 152 | 153 | ipb(unknown) -> unknown; 154 | ipb(IP) -> list_to_binary(rabbit_misc:ntoab(IP)). 155 | 156 | addr(S) when is_list(S); is_atom(S); is_binary(S) -> print("~s", S); 157 | addr(Addr) when is_tuple(Addr) -> ip(Addr). 158 | 159 | port(Port) when is_number(Port) -> Port; 160 | port(Port) -> print("~w", Port). 161 | 162 | properties(unknown) -> unknown; 163 | properties(Table) -> maps:from_list([{Name, tuple(Value)} || 164 | {Name, Value} <- Table]). 165 | 166 | amqp_table(Value) -> rabbit_misc:amqp_table(Value). 167 | 168 | parameter(P) -> pset(value, pget(value, P), P). 169 | 170 | tuple(unknown) -> unknown; 171 | tuple(Tuple) when is_tuple(Tuple) -> [tuple(E) || E <- tuple_to_list(Tuple)]; 172 | tuple(Term) -> Term. 173 | 174 | protocol(unknown) -> 175 | unknown; 176 | protocol(Version = {_Major, _Minor, _Revision}) -> 177 | protocol({'AMQP', Version}); 178 | protocol({Family, Version}) -> 179 | print("~s ~s", [Family, protocol_version(Version)]). 180 | 181 | protocol_version(Arbitrary) 182 | when is_list(Arbitrary) -> Arbitrary; 183 | protocol_version({Major, Minor}) -> io_lib:format("~B-~B", [Major, Minor]); 184 | protocol_version({Major, Minor, 0}) -> protocol_version({Major, Minor}); 185 | protocol_version({Major, Minor, Revision}) -> io_lib:format("~B-~B-~B", 186 | [Major, Minor, Revision]). 187 | 188 | now_to_str(unknown) -> 189 | unknown; 190 | now_to_str(MilliSeconds) -> 191 | BaseDate = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, 192 | {0, 0, 0}}), 193 | Seconds = BaseDate + (MilliSeconds div 1000), 194 | {{Y, M, D}, {H, Min, S}} = calendar:gregorian_seconds_to_datetime(Seconds), 195 | print("~w-~2.2.0w-~2.2.0w ~w:~2.2.0w:~2.2.0w", [Y, M, D, H, Min, S]). 196 | 197 | now_to_str_ms(unknown) -> 198 | unknown; 199 | now_to_str_ms(MilliSeconds) -> 200 | print("~s:~3.3.0w", [now_to_str(MilliSeconds), MilliSeconds rem 1000]). 201 | 202 | resource(unknown) -> unknown; 203 | resource(Res) -> resource(name, Res). 204 | 205 | resource(_, unknown) -> 206 | unknown; 207 | resource(NameAs, #resource{name = Name, virtual_host = VHost}) -> 208 | [{NameAs, Name}, {vhost, VHost}]. 209 | 210 | policy('') -> []; 211 | policy(Policy) -> [{policy, Policy}]. 212 | 213 | internal_user(User) -> 214 | [{name, internal_user:get_username(User)}, 215 | {password_hash, base64:encode(internal_user:get_password_hash(User))}, 216 | {hashing_algorithm, rabbit_auth_backend_internal:hashing_module_for_user( 217 | User)}, 218 | {tags, tags(internal_user:get_tags(User))}, 219 | {limits, internal_user:get_limits(User)}]. 220 | 221 | user(User) -> 222 | [{name, User#user.username}, 223 | {tags, tags(User#user.tags)}]. 224 | 225 | tags(Tags) -> 226 | list_to_binary(string:join([atom_to_list(T) || T <- Tags], ",")). 227 | 228 | listener(#listener{node = Node, protocol = Protocol, 229 | ip_address = IPAddress, port = Port, opts=Opts}) -> 230 | [{node, Node}, 231 | {protocol, Protocol}, 232 | {ip_address, ip(IPAddress)}, 233 | {port, Port}, 234 | {socket_opts, format_socket_opts(Opts)}]. 235 | 236 | web_context(Props0) -> 237 | SslOpts = pget(ssl_opts, Props0, []), 238 | Props = proplists:delete(ssl_opts, Props0), 239 | [{ssl_opts, format_socket_opts(SslOpts)} | Props]. 240 | 241 | format_socket_opts(Opts) -> 242 | format_socket_opts(Opts, []). 243 | 244 | format_socket_opts([], Acc) -> 245 | lists:reverse(Acc); 246 | %% for HTTP API listeners this will be included into 247 | %% socket_opts 248 | format_socket_opts([{ssl_opts, Value} | Tail], Acc) -> 249 | format_socket_opts(Tail, [{ssl_opts, format_socket_opts(Value)} | Acc]); 250 | %% exclude options that have values that are nested 251 | %% data structures or may include functions. They are fairly 252 | %% obscure and not worth reporting via HTTP API. 253 | format_socket_opts([{verify_fun, _Value} | Tail], Acc) -> 254 | format_socket_opts(Tail, Acc); 255 | format_socket_opts([{crl_cache, _Value} | Tail], Acc) -> 256 | format_socket_opts(Tail, Acc); 257 | format_socket_opts([{partial_chain, _Value} | Tail], Acc) -> 258 | format_socket_opts(Tail, Acc); 259 | format_socket_opts([{user_lookup_fun, _Value} | Tail], Acc) -> 260 | format_socket_opts(Tail, Acc); 261 | format_socket_opts([{sni_fun, _Value} | Tail], Acc) -> 262 | format_socket_opts(Tail, Acc); 263 | %% we do not report SNI host details in the UI, 264 | %% so skip this option and avoid some recursive formatting 265 | %% complexity 266 | format_socket_opts([{sni_hosts, _Value} | Tail], Acc) -> 267 | format_socket_opts(Tail, Acc); 268 | format_socket_opts([{reuse_session, _Value} | Tail], Acc) -> 269 | format_socket_opts(Tail, Acc); 270 | %% we do not want to report configured cipher suites, even 271 | %% though formatting them is straightforward 272 | format_socket_opts([{ciphers, _Value} | Tail], Acc) -> 273 | format_socket_opts(Tail, Acc); 274 | %% single atom options, e.g. `binary` 275 | format_socket_opts([Head | Tail], Acc) when is_atom(Head) -> 276 | format_socket_opts(Tail, [{Head, true} | Acc]); 277 | %% verify_fun value is a tuple that includes a function 278 | format_socket_opts([_Head = {verify_fun, _Value} | Tail], Acc) -> 279 | format_socket_opts(Tail, Acc); 280 | format_socket_opts([Head = {Name, Value} | Tail], Acc) when is_list(Value) -> 281 | case io_lib:printable_unicode_list(Value) of 282 | true -> format_socket_opts(Tail, [{Name, unicode:characters_to_binary(Value)} | Acc]); 283 | false -> format_socket_opts(Tail, [Head | Acc]) 284 | end; 285 | format_socket_opts([{Name, Value} | Tail], Acc) when is_tuple(Value) -> 286 | format_socket_opts(Tail, [{Name, tuple_to_list(Value)} | Acc]); 287 | %% exclude functions from JSON encoding 288 | format_socket_opts([_Head = {_Name, Value} | Tail], Acc) when is_function(Value) -> 289 | format_socket_opts(Tail, Acc); 290 | format_socket_opts([Head | Tail], Acc) -> 291 | format_socket_opts(Tail, [Head | Acc]). 292 | 293 | pack_binding_props(<<"">>, []) -> 294 | <<"~">>; 295 | pack_binding_props(Key, []) -> 296 | list_to_binary(quote_binding(Key)); 297 | pack_binding_props(Key, Args) -> 298 | ArgsEnc = args_hash(Args), 299 | list_to_binary(quote_binding(Key) ++ "~" ++ quote_binding(ArgsEnc)). 300 | 301 | quote_binding(Name) -> 302 | re:replace(rabbit_http_util:quote_plus(Name), "~", "%7E", [global]). 303 | 304 | %% Unfortunately string:tokens("foo~~bar", "~"). -> ["foo","bar"], we lose 305 | %% the fact that there's a double ~. 306 | tokenise("") -> 307 | []; 308 | tokenise(Str) -> 309 | Count = string:cspan(Str, "~"), 310 | case length(Str) of 311 | Count -> [Str]; 312 | _ -> [string:sub_string(Str, 1, Count) | 313 | tokenise(string:sub_string(Str, Count + 2))] 314 | end. 315 | 316 | to_amqp_table(V) -> rabbit_misc:to_amqp_table(V). 317 | 318 | url(Fmt, Vals) -> 319 | print(Fmt, [rabbit_http_util:quote_plus(V) || V <- Vals]). 320 | 321 | exchange(X) -> 322 | format(X, {fun format_exchange_and_queue/1, false}). 323 | 324 | %% We get queues using rabbit_amqqueue:list/1 rather than :info_all/1 since 325 | %% the latter wakes up each queue. Therefore we have a record rather than a 326 | %% proplist to deal with. 327 | queue(Q) when ?is_amqqueue(Q) -> 328 | Name = amqqueue:get_name(Q), 329 | Durable = amqqueue:is_durable(Q), 330 | AutoDelete = amqqueue:is_auto_delete(Q), 331 | ExclusiveOwner = amqqueue:get_exclusive_owner(Q), 332 | Arguments = amqqueue:get_arguments(Q), 333 | Pid = amqqueue:get_pid(Q), 334 | State = amqqueue:get_state(Q), 335 | %% TODO: in the future queue types should be registered with their 336 | %% full and short names and this hard-coded translation should not be 337 | %% necessary 338 | Type = case amqqueue:get_type(Q) of 339 | rabbit_classic_queue -> classic; 340 | rabbit_quorum_queue -> quorum; 341 | T -> T 342 | end, 343 | format( 344 | [{name, Name}, 345 | {durable, Durable}, 346 | {auto_delete, AutoDelete}, 347 | {exclusive, is_pid(ExclusiveOwner)}, 348 | {owner_pid, ExclusiveOwner}, 349 | {arguments, Arguments}, 350 | {pid, Pid}, 351 | {type, Type}, 352 | {state, State}] ++ rabbit_amqqueue:format(Q), 353 | {fun format_exchange_and_queue/1, false}). 354 | 355 | queue_info(List) -> 356 | format(List, {fun format_exchange_and_queue/1, false}). 357 | 358 | queue_state({syncing, Msgs}) -> [{state, syncing}, 359 | {sync_messages, Msgs}]; 360 | queue_state({terminated_by, Name}) -> 361 | [{state, terminated}, 362 | {terminated_by, Name}]; 363 | queue_state(Status) -> [{state, Status}]. 364 | 365 | %% We get bindings using rabbit_binding:list_*/1 rather than :info_all/1 since 366 | %% there are no per-exchange / queue / etc variants for the latter. Therefore 367 | %% we have a record rather than a proplist to deal with. 368 | binding(#binding{source = S, 369 | key = Key, 370 | destination = D, 371 | args = Args}) -> 372 | format( 373 | [{source, S}, 374 | {destination, D#resource.name}, 375 | {destination_type, D#resource.kind}, 376 | {routing_key, Key}, 377 | {arguments, Args}, 378 | {properties_key, pack_binding_props(Key, Args)}], 379 | {fun format_binding/1, false}). 380 | 381 | basic_properties(Props = #'P_basic'{}) -> 382 | Res = record(Props, record_info(fields, 'P_basic')), 383 | format(Res, {fun format_basic_properties/1, true}). 384 | 385 | record(Record, Fields) -> 386 | {Res, _Ix} = lists:foldl(fun (K, {L, Ix}) -> 387 | {case element(Ix, Record) of 388 | undefined -> L; 389 | V -> [{K, V}|L] 390 | end, Ix + 1} 391 | end, {[], 2}, Fields), 392 | Res. 393 | 394 | to_basic_properties(Props) when is_map(Props) -> 395 | E = fun err/2, 396 | Fmt = fun (headers, H) -> to_amqp_table(H); 397 | (delivery_mode, V) when is_integer(V) -> V; 398 | (delivery_mode, _V) -> E(not_int,delivery_mode); 399 | (priority, V) when is_integer(V) -> V; 400 | (priority, _V) -> E(not_int, priority); 401 | (timestamp, V) when is_integer(V) -> V; 402 | (timestamp, _V) -> E(not_int, timestamp); 403 | (_, V) when is_binary(V) -> V; 404 | (K, _V) -> E(not_string, K) 405 | end, 406 | {Res, _Ix} = lists:foldl( 407 | fun (K, {P, Ix}) -> 408 | {case maps:get(a2b(K), Props, undefined) of 409 | undefined -> P; 410 | V -> setelement(Ix, P, Fmt(K, V)) 411 | end, Ix + 1} 412 | end, {#'P_basic'{}, 2}, 413 | record_info(fields, 'P_basic')), 414 | Res. 415 | 416 | -spec err(term(), term()) -> no_return(). 417 | err(A, B) -> 418 | throw({error, {A, B}}). 419 | 420 | a2b(A) -> 421 | list_to_binary(atom_to_list(A)). 422 | 423 | strip_queue_pids(Item) -> 424 | strip_queue_pids(Item, []). 425 | 426 | strip_queue_pids([{_, unknown} | T], Acc) -> 427 | strip_queue_pids(T, Acc); 428 | strip_queue_pids([{pid, Pid} | T], Acc0) when is_pid(Pid) -> 429 | Acc = case proplists:is_defined(node, Acc0) of 430 | false -> [{node, node(Pid)} | Acc0]; 431 | true -> Acc0 432 | end, 433 | strip_queue_pids(T, Acc); 434 | strip_queue_pids([{pid, _} | T], Acc) -> 435 | strip_queue_pids(T, Acc); 436 | strip_queue_pids([{owner_pid, _} | T], Acc) -> 437 | strip_queue_pids(T, Acc); 438 | strip_queue_pids([Any | T], Acc) -> 439 | strip_queue_pids(T, [Any | Acc]); 440 | strip_queue_pids([], Acc) -> 441 | Acc. 442 | 443 | %% Items can be connections, channels, consumers or queues, hence remove takes 444 | %% various items. 445 | strip_pids(Item = [T | _]) when is_tuple(T) -> 446 | lists:usort(strip_pids(Item, [])); 447 | 448 | strip_pids(Items) -> [lists:usort(strip_pids(I)) || I <- Items]. 449 | 450 | strip_pids([{_, unknown} | T], Acc) -> 451 | strip_pids(T, Acc); 452 | strip_pids([{pid, Pid} | T], Acc) when is_pid(Pid) -> 453 | strip_pids(T, [{node, node(Pid)} | Acc]); 454 | strip_pids([{pid, _} | T], Acc) -> 455 | strip_pids(T, Acc); 456 | strip_pids([{connection, _} | T], Acc) -> 457 | strip_pids(T, Acc); 458 | strip_pids([{owner_pid, _} | T], Acc) -> 459 | strip_pids(T, Acc); 460 | strip_pids([{channel, _} | T], Acc) -> 461 | strip_pids(T, Acc); 462 | strip_pids([{channel_pid, _} | T], Acc) -> 463 | strip_pids(T, Acc); 464 | strip_pids([{exclusive_consumer_pid, _} | T], Acc) -> 465 | strip_pids(T, Acc); 466 | strip_pids([{slave_pids, ''} | T], Acc) -> 467 | strip_pids(T, Acc); 468 | strip_pids([{slave_pids, Pids} | T], Acc) -> 469 | strip_pids(T, [{slave_nodes, [node(Pid) || Pid <- Pids]} | Acc]); 470 | strip_pids([{synchronised_slave_pids, ''} | T], Acc) -> 471 | strip_pids(T, Acc); 472 | strip_pids([{synchronised_slave_pids, Pids} | T], Acc) -> 473 | strip_pids(T, [{synchronised_slave_nodes, [node(Pid) || Pid <- Pids]} | Acc]); 474 | strip_pids([{K, [P|_] = Nested} | T], Acc) when is_tuple(P) -> % recurse 475 | strip_pids(T, [{K, strip_pids(Nested)} | Acc]); 476 | strip_pids([{K, [L|_] = Nested} | T], Acc) when is_list(L) -> % recurse 477 | strip_pids(T, [{K, strip_pids(Nested)} | Acc]); 478 | strip_pids([Any | T], Acc) -> 479 | strip_pids(T, [Any | Acc]); 480 | strip_pids([], Acc) -> 481 | Acc. 482 | 483 | %% Format for JSON replies. Transforms '' into null 484 | format_nulls(Items) when is_list(Items) -> 485 | [format_null_item(Pair) || Pair <- Items]; 486 | format_nulls(Item) -> 487 | format_null_item(Item). 488 | 489 | format_null_item({Key, ''}) -> 490 | {Key, null}; 491 | format_null_item({Key, Value}) when is_list(Value) -> 492 | {Key, format_nulls(Value)}; 493 | format_null_item({Key, Value}) -> 494 | {Key, Value}; 495 | format_null_item([{_K, _V} | _T] = L) -> 496 | format_nulls(L); 497 | format_null_item(Value) -> 498 | Value. 499 | 500 | 501 | -spec escape_html_tags(string()) -> binary(). 502 | 503 | escape_html_tags(S) -> 504 | escape_html_tags(rabbit_data_coercion:to_list(S), []). 505 | 506 | 507 | -spec escape_html_tags(string(), string()) -> binary(). 508 | 509 | escape_html_tags([], Acc) -> 510 | rabbit_data_coercion:to_binary(lists:reverse(Acc)); 511 | escape_html_tags("<" ++ Rest, Acc) -> 512 | escape_html_tags(Rest, lists:reverse("<", Acc)); 513 | escape_html_tags(">" ++ Rest, Acc) -> 514 | escape_html_tags(Rest, lists:reverse(">", Acc)); 515 | escape_html_tags("&" ++ Rest, Acc) -> 516 | escape_html_tags(Rest, lists:reverse("&", Acc)); 517 | escape_html_tags([C | Rest], Acc) -> 518 | escape_html_tags(Rest, [C | Acc]). 519 | 520 | 521 | -spec clean_consumer_details(proplists:proplist()) -> proplists:proplist(). 522 | clean_consumer_details(Obj) -> 523 | case pget(consumer_details, Obj) of 524 | undefined -> Obj; 525 | Cds -> 526 | Cons = [format_consumer_arguments(clean_channel_details(Con)) || Con <- Cds], 527 | pset(consumer_details, Cons, Obj) 528 | end. 529 | 530 | -spec clean_channel_details(proplists:proplist()) -> proplists:proplist(). 531 | clean_channel_details(Obj) -> 532 | Obj0 = lists:keydelete(channel_pid, 1, Obj), 533 | case pget(channel_details, Obj0) of 534 | undefined -> Obj0; 535 | Chd -> 536 | pset(channel_details, 537 | lists:keydelete(pid, 1, Chd), 538 | Obj0) 539 | end. 540 | 541 | -spec format_consumer_arguments(proplists:proplist()) -> proplists:proplist(). 542 | format_consumer_arguments(Obj) -> 543 | case pget(arguments, Obj) of 544 | undefined -> Obj; 545 | #{} -> Obj; 546 | [] -> pset(arguments, #{}, Obj); 547 | Args -> pset(arguments, amqp_table(Args), Obj) 548 | end. 549 | 550 | 551 | parse_bool(<<"true">>) -> true; 552 | parse_bool(<<"false">>) -> false; 553 | parse_bool(true) -> true; 554 | parse_bool(false) -> false; 555 | parse_bool(undefined) -> undefined; 556 | parse_bool(V) -> throw({error, {not_boolean, V}}). 557 | 558 | args_hash(Args) -> 559 | list_to_binary(rabbit_misc:base64url(<<(erlang:phash2(Args, 1 bsl 32)):32>>)). 560 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_gc.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(rabbit_mgmt_gc). 8 | 9 | -include_lib("rabbit_common/include/rabbit.hrl"). 10 | 11 | -record(state, {timer, 12 | interval 13 | }). 14 | 15 | -spec start_link() -> rabbit_types:ok_pid_or_error(). 16 | 17 | -export([start_link/0]). 18 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 19 | code_change/3]). 20 | 21 | start_link() -> 22 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 23 | 24 | init(_) -> 25 | Interval = rabbit_misc:get_env(rabbitmq_management_agent, metrics_gc_interval, 120000), 26 | {ok, start_timer(#state{interval = Interval})}. 27 | 28 | handle_call(test, _From, State) -> 29 | {reply, ok, State}. 30 | 31 | handle_cast(_Request, State) -> 32 | {noreply, State}. 33 | 34 | handle_info(start_gc, State) -> 35 | gc_connections(), 36 | gc_vhosts(), 37 | gc_channels(), 38 | gc_queues(), 39 | gc_exchanges(), 40 | gc_nodes(), 41 | {noreply, start_timer(State)}. 42 | 43 | terminate(_Reason, #state{timer = TRef}) -> 44 | _ = erlang:cancel_timer(TRef), 45 | ok. 46 | 47 | code_change(_OldVsn, State, _Extra) -> 48 | {ok, State}. 49 | 50 | start_timer(#state{interval = Interval} = St) -> 51 | TRef = erlang:send_after(Interval, self(), start_gc), 52 | St#state{timer = TRef}. 53 | 54 | gc_connections() -> 55 | gc_process(connection_stats_coarse_conn_stats), 56 | gc_process(connection_created_stats), 57 | gc_process(connection_stats). 58 | 59 | gc_vhosts() -> 60 | VHosts = rabbit_vhost:list(), 61 | GbSet = gb_sets:from_list(VHosts), 62 | gc_entity(vhost_stats_coarse_conn_stats, GbSet), 63 | gc_entity(vhost_stats_fine_stats, GbSet), 64 | gc_entity(vhost_msg_stats, GbSet), 65 | gc_entity(vhost_msg_rates, GbSet), 66 | gc_entity(vhost_stats_deliver_stats, GbSet). 67 | 68 | gc_channels() -> 69 | gc_process(channel_created_stats), 70 | gc_process(channel_stats), 71 | gc_process(channel_stats_fine_stats), 72 | gc_process(channel_process_stats), 73 | gc_process(channel_stats_deliver_stats), 74 | ok. 75 | 76 | gc_queues() -> 77 | Queues = rabbit_amqqueue:list_names(), 78 | GbSet = gb_sets:from_list(Queues), 79 | LocalQueues = rabbit_amqqueue:list_local_names(), 80 | LocalGbSet = gb_sets:from_list(LocalQueues), 81 | gc_entity(queue_stats_publish, GbSet), 82 | gc_entity(queue_stats, LocalGbSet), 83 | gc_entity(queue_msg_stats, LocalGbSet), 84 | gc_entity(queue_process_stats, LocalGbSet), 85 | gc_entity(queue_msg_rates, LocalGbSet), 86 | gc_entity(queue_stats_deliver_stats, GbSet), 87 | gc_process_and_entity(channel_queue_stats_deliver_stats_queue_index, GbSet), 88 | gc_process_and_entity(consumer_stats_queue_index, GbSet), 89 | gc_process_and_entity(consumer_stats_channel_index, GbSet), 90 | gc_process_and_entity(consumer_stats, GbSet), 91 | gc_process_and_entity(channel_exchange_stats_fine_stats_channel_index, GbSet), 92 | gc_process_and_entity(channel_queue_stats_deliver_stats, GbSet), 93 | gc_process_and_entity(channel_queue_stats_deliver_stats_channel_index, GbSet), 94 | ExchangeGbSet = gb_sets:from_list(rabbit_exchange:list_names()), 95 | gc_entities(queue_exchange_stats_publish, GbSet, ExchangeGbSet), 96 | gc_entities(queue_exchange_stats_publish_queue_index, GbSet, ExchangeGbSet), 97 | gc_entities(queue_exchange_stats_publish_exchange_index, GbSet, ExchangeGbSet). 98 | 99 | gc_exchanges() -> 100 | Exchanges = rabbit_exchange:list_names(), 101 | GbSet = gb_sets:from_list(Exchanges), 102 | gc_entity(exchange_stats_publish_in, GbSet), 103 | gc_entity(exchange_stats_publish_out, GbSet), 104 | gc_entity(channel_exchange_stats_fine_stats_exchange_index, GbSet), 105 | gc_process_and_entity(channel_exchange_stats_fine_stats, GbSet). 106 | 107 | gc_nodes() -> 108 | Nodes = rabbit_mnesia:cluster_nodes(all), 109 | GbSet = gb_sets:from_list(Nodes), 110 | gc_entity(node_stats, GbSet), 111 | gc_entity(node_coarse_stats, GbSet), 112 | gc_entity(node_persister_stats, GbSet), 113 | gc_entity(node_node_coarse_stats_node_index, GbSet), 114 | gc_entity(node_node_stats, GbSet), 115 | gc_entity(node_node_coarse_stats, GbSet). 116 | 117 | gc_process(Table) -> 118 | ets:foldl(fun({{Pid, _} = Key, _}, none) -> 119 | gc_process(Pid, Table, Key); 120 | ({Pid = Key, _}, none) -> 121 | gc_process(Pid, Table, Key); 122 | ({Pid = Key, _, _}, none) -> 123 | gc_process(Pid, Table, Key); 124 | ({{Pid, _} = Key, _, _, _, _}, none) -> 125 | gc_process(Pid, Table, Key) 126 | end, none, Table). 127 | 128 | gc_process(Pid, Table, Key) -> 129 | case rabbit_misc:is_process_alive(Pid) of 130 | true -> 131 | none; 132 | false -> 133 | ets:delete(Table, Key), 134 | none 135 | end. 136 | 137 | gc_entity(Table, GbSet) -> 138 | ets:foldl(fun({{_, Id} = Key, _}, none) when Table == node_node_stats -> 139 | gc_entity(Id, Table, Key, GbSet); 140 | ({{{_, Id}, _} = Key, _}, none) when Table == node_node_coarse_stats -> 141 | gc_entity(Id, Table, Key, GbSet); 142 | ({{Id, _} = Key, _}, none) -> 143 | gc_entity(Id, Table, Key, GbSet); 144 | ({Id = Key, _}, none) -> 145 | gc_entity(Id, Table, Key, GbSet); 146 | ({{Id, _} = Key, _}, none) -> 147 | gc_entity(Id, Table, Key, GbSet) 148 | end, none, Table). 149 | 150 | gc_entity(Id, Table, Key, GbSet) -> 151 | case gb_sets:is_member(Id, GbSet) of 152 | true -> 153 | none; 154 | false -> 155 | ets:delete(Table, Key), 156 | none 157 | end. 158 | 159 | gc_process_and_entity(Table, GbSet) -> 160 | ets:foldl(fun({{Id, Pid, _} = Key, _}, none) when Table == consumer_stats -> 161 | gc_process_and_entity(Id, Pid, Table, Key, GbSet); 162 | ({Id = Key, {_, Pid, _}} = Object, none) 163 | when Table == consumer_stats_queue_index -> 164 | gc_object(Pid, Table, Object), 165 | gc_entity(Id, Table, Key, GbSet); 166 | ({Pid = Key, {Id, _, _}} = Object, none) 167 | when Table == consumer_stats_channel_index -> 168 | gc_object(Id, Table, Object, GbSet), 169 | gc_process(Pid, Table, Key); 170 | ({Id = Key, {{Pid, _}, _}} = Object, none) 171 | when Table == channel_exchange_stats_fine_stats_exchange_index; 172 | Table == channel_queue_stats_deliver_stats_queue_index -> 173 | gc_object(Pid, Table, Object), 174 | gc_entity(Id, Table, Key, GbSet); 175 | ({Pid = Key, {{_, Id}, _}} = Object, none) 176 | when Table == channel_exchange_stats_fine_stats_channel_index; 177 | Table == channel_queue_stats_deliver_stats_channel_index -> 178 | gc_object(Id, Table, Object, GbSet), 179 | gc_process(Pid, Table, Key); 180 | ({{{Pid, Id}, _} = Key, _}, none) 181 | when Table == channel_queue_stats_deliver_stats; 182 | Table == channel_exchange_stats_fine_stats -> 183 | gc_process_and_entity(Id, Pid, Table, Key, GbSet); 184 | ({{{Pid, Id}, _} = Key, _, _, _, _, _, _, _, _}, none) -> 185 | gc_process_and_entity(Id, Pid, Table, Key, GbSet); 186 | ({{{Pid, Id}, _} = Key, _, _, _, _}, none) -> 187 | gc_process_and_entity(Id, Pid, Table, Key, GbSet) 188 | end, none, Table). 189 | 190 | gc_process_and_entity(Id, Pid, Table, Key, GbSet) -> 191 | case rabbit_misc:is_process_alive(Pid) andalso gb_sets:is_member(Id, GbSet) of 192 | true -> 193 | none; 194 | false -> 195 | ets:delete(Table, Key), 196 | none 197 | end. 198 | 199 | gc_object(Pid, Table, Object) -> 200 | case rabbit_misc:is_process_alive(Pid) of 201 | true -> 202 | none; 203 | false -> 204 | ets:delete_object(Table, Object), 205 | none 206 | end. 207 | 208 | gc_object(Id, Table, Object, GbSet) -> 209 | case gb_sets:is_member(Id, GbSet) of 210 | true -> 211 | none; 212 | false -> 213 | ets:delete_object(Table, Object), 214 | none 215 | end. 216 | 217 | gc_entities(Table, QueueGbSet, ExchangeGbSet) -> 218 | ets:foldl(fun({{{Q, X}, _} = Key, _}, none) 219 | when Table == queue_exchange_stats_publish -> 220 | gc_entity(Q, Table, Key, QueueGbSet), 221 | gc_entity(X, Table, Key, ExchangeGbSet); 222 | ({Q, {{_, X}, _}} = Object, none) 223 | when Table == queue_exchange_stats_publish_queue_index -> 224 | gc_object(X, Table, Object, ExchangeGbSet), 225 | gc_entity(Q, Table, Q, QueueGbSet); 226 | ({X, {{Q, _}, _}} = Object, none) 227 | when Table == queue_exchange_stats_publish_exchange_index -> 228 | gc_object(Q, Table, Object, QueueGbSet), 229 | gc_entity(X, Table, X, ExchangeGbSet) 230 | end, none, Table). 231 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_metrics_gc.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(rabbit_mgmt_metrics_gc). 8 | 9 | -record(state, {basic_i, 10 | detailed_i, 11 | global_i}). 12 | 13 | -include_lib("rabbit_common/include/rabbit.hrl"). 14 | 15 | -spec start_link(atom()) -> rabbit_types:ok_pid_or_error(). 16 | 17 | -export([name/1]). 18 | -export([start_link/1]). 19 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 20 | code_change/3]). 21 | 22 | name(EventType) -> 23 | list_to_atom((atom_to_list(EventType) ++ "_metrics_gc")). 24 | 25 | start_link(EventType) -> 26 | gen_server:start_link({local, name(EventType)}, ?MODULE, [], []). 27 | 28 | init(_) -> 29 | Policies = rabbit_mgmt_agent_config:get_env(sample_retention_policies), 30 | {ok, #state{basic_i = intervals(basic, Policies), 31 | global_i = intervals(global, Policies), 32 | detailed_i = intervals(detailed, Policies)}}. 33 | 34 | handle_call(_Request, _From, State) -> 35 | {noreply, State}. 36 | 37 | handle_cast({event, #event{type = connection_closed, props = Props}}, 38 | State = #state{basic_i = BIntervals}) -> 39 | Pid = pget(pid, Props), 40 | remove_connection(Pid, BIntervals), 41 | {noreply, State}; 42 | handle_cast({event, #event{type = channel_closed, props = Props}}, 43 | State = #state{basic_i = BIntervals}) -> 44 | Pid = pget(pid, Props), 45 | remove_channel(Pid, BIntervals), 46 | {noreply, State}; 47 | handle_cast({event, #event{type = consumer_deleted, props = Props}}, State) -> 48 | remove_consumer(Props), 49 | {noreply, State}; 50 | handle_cast({event, #event{type = exchange_deleted, props = Props}}, 51 | State = #state{basic_i = BIntervals}) -> 52 | Name = pget(name, Props), 53 | remove_exchange(Name, BIntervals), 54 | {noreply, State}; 55 | handle_cast({event, #event{type = queue_deleted, props = Props}}, 56 | State = #state{basic_i = BIntervals}) -> 57 | Name = pget(name, Props), 58 | remove_queue(Name, BIntervals), 59 | {noreply, State}; 60 | handle_cast({event, #event{type = vhost_deleted, props = Props}}, 61 | State = #state{global_i = GIntervals}) -> 62 | Name = pget(name, Props), 63 | remove_vhost(Name, GIntervals), 64 | {noreply, State}; 65 | handle_cast({event, #event{type = node_node_deleted, props = Props}}, State) -> 66 | Name = pget(route, Props), 67 | remove_node_node(Name), 68 | {noreply, State}. 69 | 70 | handle_info(_Msg, State) -> 71 | {noreply, State}. 72 | 73 | terminate(_Reason, _State) -> 74 | ok. 75 | 76 | code_change(_OldVsn, State, _Extra) -> 77 | {ok, State}. 78 | 79 | remove_connection(Id, BIntervals) -> 80 | ets:delete(connection_created_stats, Id), 81 | ets:delete(connection_stats, Id), 82 | delete_samples(connection_stats_coarse_conn_stats, Id, BIntervals), 83 | ok. 84 | 85 | remove_channel(Id, BIntervals) -> 86 | ets:delete(channel_created_stats, Id), 87 | ets:delete(channel_stats, Id), 88 | delete_samples(channel_process_stats, Id, BIntervals), 89 | delete_samples(channel_stats_fine_stats, Id, BIntervals), 90 | delete_samples(channel_stats_deliver_stats, Id, BIntervals), 91 | index_delete(consumer_stats, channel, Id), 92 | index_delete(channel_exchange_stats_fine_stats, channel, Id), 93 | index_delete(channel_queue_stats_deliver_stats, channel, Id), 94 | ok. 95 | 96 | remove_consumer(Props) -> 97 | Id = {pget(queue, Props), pget(channel, Props), pget(consumer_tag, Props)}, 98 | ets:delete(consumer_stats, Id), 99 | cleanup_index(consumer_stats, Id), 100 | ok. 101 | 102 | remove_exchange(Name, BIntervals) -> 103 | delete_samples(exchange_stats_publish_out, Name, BIntervals), 104 | delete_samples(exchange_stats_publish_in, Name, BIntervals), 105 | index_delete(queue_exchange_stats_publish, exchange, Name), 106 | index_delete(channel_exchange_stats_fine_stats, exchange, Name), 107 | ok. 108 | 109 | remove_queue(Name, BIntervals) -> 110 | ets:delete(queue_stats, Name), 111 | delete_samples(queue_stats_publish, Name, BIntervals), 112 | delete_samples(queue_stats_deliver_stats, Name, BIntervals), 113 | delete_samples(queue_process_stats, Name, BIntervals), 114 | delete_samples(queue_msg_stats, Name, BIntervals), 115 | delete_samples(queue_msg_rates, Name, BIntervals), 116 | index_delete(channel_queue_stats_deliver_stats, queue, Name), 117 | index_delete(queue_exchange_stats_publish, queue, Name), 118 | index_delete(consumer_stats, queue, Name), 119 | 120 | ok. 121 | 122 | remove_vhost(Name, GIntervals) -> 123 | delete_samples(vhost_stats_coarse_conn_stats, Name, GIntervals), 124 | delete_samples(vhost_stats_fine_stats, Name, GIntervals), 125 | delete_samples(vhost_stats_deliver_stats, Name, GIntervals), 126 | ok. 127 | 128 | remove_node_node(Name) -> 129 | index_delete(node_node_coarse_stats, node, Name), 130 | ok. 131 | 132 | intervals(Type, Policies) -> 133 | [I || {_, I} <- proplists:get_value(Type, Policies)]. 134 | 135 | delete_samples(Table, Id, Intervals) -> 136 | [ets:delete(Table, {Id, I}) || I <- Intervals], 137 | ok. 138 | 139 | index_delete(Table, Type, Id) -> 140 | IndexTable = rabbit_mgmt_metrics_collector:index_table(Table, Type), 141 | Keys = ets:lookup(IndexTable, Id), 142 | [ begin 143 | ets:delete(Table, Key), 144 | cleanup_index(Table, Key) 145 | end 146 | || {_Index, Key} <- Keys ], 147 | ets:delete(IndexTable, Id), 148 | ok. 149 | 150 | cleanup_index(consumer_stats, {Q, Ch, _} = Key) -> 151 | delete_index(consumer_stats, queue, {Q, Key}), 152 | delete_index(consumer_stats, channel, {Ch, Key}), 153 | ok; 154 | cleanup_index(channel_exchange_stats_fine_stats, {{Ch, Ex}, _} = Key) -> 155 | delete_index(channel_exchange_stats_fine_stats, exchange, {Ex, Key}), 156 | delete_index(channel_exchange_stats_fine_stats, channel, {Ch, Key}), 157 | ok; 158 | cleanup_index(channel_queue_stats_deliver_stats, {{Ch, Q}, _} = Key) -> 159 | delete_index(channel_queue_stats_deliver_stats, queue, {Q, Key}), 160 | delete_index(channel_queue_stats_deliver_stats, channel, {Ch, Key}), 161 | ok; 162 | cleanup_index(queue_exchange_stats_publish, {{Q, Ex}, _} = Key) -> 163 | delete_index(queue_exchange_stats_publish, queue, {Q, Key}), 164 | delete_index(queue_exchange_stats_publish, exchange, {Ex, Key}), 165 | ok; 166 | cleanup_index(node_node_coarse_stats, {{_, Node}, _} = Key) -> 167 | delete_index(node_node_coarse_stats, node, {Node, Key}), 168 | ok; 169 | cleanup_index(_, _) -> ok. 170 | 171 | delete_index(Table, Index, Obj) -> 172 | ets:delete_object(rabbit_mgmt_metrics_collector:index_table(Table, Index), 173 | Obj). 174 | 175 | pget(Key, List) -> rabbit_misc:pget(Key, List, unknown). 176 | -------------------------------------------------------------------------------- /src/rabbit_mgmt_storage.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(rabbit_mgmt_storage). 8 | -behaviour(gen_server2). 9 | -record(state, {}). 10 | 11 | -spec start_link() -> rabbit_types:ok_pid_or_error(). 12 | 13 | -export([start_link/0]). 14 | -export([reset/0, reset_all/0]). 15 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 16 | code_change/3]). 17 | 18 | -include("rabbit_mgmt_metrics.hrl"). 19 | 20 | %% ETS owner 21 | start_link() -> 22 | gen_server2:start_link({local, ?MODULE}, ?MODULE, [], []). 23 | 24 | reset() -> 25 | rabbit_log:warning("Resetting RabbitMQ management storage"), 26 | [ets:delete_all_objects(IndexTable) || IndexTable <- ?INDEX_TABLES], 27 | [ets:delete_all_objects(Table) || {Table, _} <- ?TABLES], 28 | _ = rabbit_mgmt_metrics_collector:reset_all(), 29 | ok. 30 | 31 | reset_all() -> 32 | _ = [rpc:call(Node, rabbit_mgmt_storage, reset, []) 33 | || Node <- rabbit_nodes:all_running()], 34 | ok. 35 | 36 | init(_) -> 37 | _ = [ets:new(IndexTable, [public, bag, named_table]) 38 | || IndexTable <- ?INDEX_TABLES], 39 | _ = [ets:new(Table, [public, Type, named_table]) 40 | || {Table, Type} <- ?TABLES], 41 | _ = ets:new(rabbit_mgmt_db_cache, [public, set, named_table]), 42 | {ok, #state{}}. 43 | 44 | handle_call(_Request, _From, State) -> 45 | {noreply, State}. 46 | 47 | handle_cast(_Msg, State) -> 48 | {noreply, State}. 49 | 50 | handle_info(_Msg, State) -> 51 | {noreply, State}. 52 | 53 | terminate(_Reason, _State) -> 54 | ok. 55 | 56 | code_change(_OldVsn, State, _Extra) -> 57 | {ok, State}. 58 | -------------------------------------------------------------------------------- /test/exometer_slide_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(exometer_slide_SUITE). 9 | 10 | -include_lib("proper/include/proper.hrl"). 11 | -include_lib("eunit/include/eunit.hrl"). 12 | 13 | -compile(export_all). 14 | 15 | all() -> 16 | [ 17 | {group, parallel_tests} 18 | ]. 19 | 20 | groups() -> 21 | [ 22 | {parallel_tests, [parallel], [ 23 | incremental_add_element_basics, 24 | last_two_normalises_old_sample_timestamp, 25 | incremental_last_two_returns_last_two_completed_samples, 26 | incremental_sum, 27 | incremental_sum_stale, 28 | incremental_sum_stale2, 29 | incremental_sum_with_drop, 30 | incremental_sum_with_total, 31 | foldl_realises_partial_sample, 32 | foldl_and_to_list, 33 | foldl_and_to_list_incremental, 34 | optimize, 35 | stale_to_list, 36 | to_list_single_after_drop, 37 | to_list_drop_and_roll, 38 | to_list_with_drop, 39 | to_list_simple, 40 | foldl_with_drop, 41 | sum_single, 42 | to_normalized_list, 43 | to_normalized_list_no_padding, 44 | to_list_in_the_past, 45 | sum_mgmt_352, 46 | sum_mgmt_352_extra, 47 | sum_mgmt_352_peak 48 | ]} 49 | ]. 50 | 51 | %% ------------------------------------------------------------------- 52 | %% Testsuite setup/teardown. 53 | %% ------------------------------------------------------------------- 54 | init_per_suite(Config) -> 55 | Config. 56 | 57 | end_per_suite(_Config) -> 58 | ok. 59 | 60 | init_per_group(_, Config) -> 61 | Config. 62 | 63 | end_per_group(_, _Config) -> 64 | ok. 65 | 66 | init_per_testcase(_, Config) -> 67 | Config. 68 | 69 | end_per_testcase(_, _Config) -> 70 | ok. 71 | 72 | %% ------------------------------------------------------------------- 73 | %% Generators. 74 | %% ------------------------------------------------------------------- 75 | elements_gen() -> 76 | ?LET(Length, oneof([1, 2, 3, 7, 8, 20]), 77 | ?LET(Elements, list(vector(Length, int())), 78 | [erlang:list_to_tuple(E) || E <- Elements])). 79 | 80 | %% ------------------------------------------------------------------- 81 | %% Testcases. 82 | %% ------------------------------------------------------------------- 83 | 84 | %% TODO: turn tests into properties 85 | 86 | incremental_add_element_basics(_Config) -> 87 | Now = exometer_slide:timestamp(), 88 | S0 = exometer_slide:new(Now, 10, [{incremental, true}, 89 | {interval, 100}]), 90 | 91 | [] = exometer_slide:to_list(Now, S0), 92 | % add element before next interval 93 | S1 = exometer_slide:add_element(Now + 10, {1}, S0), 94 | 95 | [] = exometer_slide:to_list(Now + 20, S1), 96 | 97 | %% to_list is not empty, as we take the 'real' total if a full interval has passed 98 | Now100 = Now + 100, 99 | [{Now100, {1}}] = exometer_slide:to_list(Now100, S1), 100 | 101 | Then = Now + 101, 102 | % add element after interval 103 | S2 = exometer_slide:add_element(Then, {1}, S1), 104 | 105 | % contains single element with incremented value 106 | [{Then, {2}}] = exometer_slide:to_list(Then, S2). 107 | 108 | last_two_normalises_old_sample_timestamp(_Config) -> 109 | Now = 0, 110 | S0 = exometer_slide:new(Now, 10, [{incremental, true}, 111 | {interval, 100}]), 112 | 113 | S1 = exometer_slide:add_element(100, {1}, S0), 114 | S2 = exometer_slide:add_element(500, {1}, S1), 115 | 116 | [{500, {2}}, {400, {1}}] = exometer_slide:last_two(S2). 117 | 118 | 119 | incremental_last_two_returns_last_two_completed_samples(_Config) -> 120 | Now = exometer_slide:timestamp(), 121 | S0 = exometer_slide:new(Now, 10, [{incremental, true}, 122 | {interval, 100}]), 123 | 124 | % add two full elements then a partial 125 | Now100 = Now + 100, 126 | Now200 = Now + 200, 127 | S1 = exometer_slide:add_element(Now100, {1}, S0), 128 | S2 = exometer_slide:add_element(Now200, {1}, S1), 129 | S3 = exometer_slide:add_element(Now + 210, {1}, S2), 130 | 131 | [{Now200, {2}}, {Now100, {1}}] = exometer_slide:last_two(S3). 132 | 133 | incremental_sum(_Config) -> 134 | Now = exometer_slide:timestamp(), 135 | S1 = lists:foldl(fun (Next, S) -> 136 | exometer_slide:add_element(Now + Next, {1}, S) 137 | end, 138 | exometer_slide:new(Now, 1000, [{incremental, true}, {interval, 100}]), 139 | lists:seq(100, 1000, 100)), 140 | Now50 = Now - 50, 141 | S2 = lists:foldl(fun (Next, S) -> 142 | exometer_slide:add_element(Now50 + Next, {1}, S) 143 | end, 144 | exometer_slide:new(Now50, 1000, [{incremental, true}, {interval, 100}]), 145 | lists:seq(100, 1000, 100)), 146 | S3 = exometer_slide:sum([S1, S2]), 147 | 148 | 10 = length(exometer_slide:to_list(Now + 1000, S1)), 149 | 10 = length(exometer_slide:to_list(Now + 1000, S2)), 150 | 10 = length(exometer_slide:to_list(Now + 1000, S3)). 151 | 152 | incremental_sum_stale(_Config) -> 153 | Now = 0, 154 | Slide = exometer_slide:new(Now, 25, [{incremental, true}, {interval, 5}]), 155 | 156 | S1 = lists:foldl(fun (Next, S) -> 157 | exometer_slide:add_element(Now + Next, {1}, S) 158 | end, Slide, [1, 8, 15, 21, 27]), 159 | 160 | S2 = lists:foldl(fun (Next, S) -> 161 | exometer_slide:add_element(Now + Next, {1}, S) 162 | end, Slide, [2, 7, 14, 20, 25]), 163 | S3 = exometer_slide:sum([S1, S2]), 164 | [27,22,17,12,7] = lists:reverse([T || {T, _} <- exometer_slide:to_list(27, S3)]), 165 | [10,8,6,4,2] = lists:reverse([V || {_, {V}} <- exometer_slide:to_list(27, S3)]). 166 | 167 | incremental_sum_stale2(_Config) -> 168 | Now = 0, 169 | Slide = exometer_slide:new(Now, 25, [{incremental, true}, 170 | {max_n, 5}, 171 | {interval, 5}]), 172 | 173 | S1 = lists:foldl(fun (Next, S) -> 174 | exometer_slide:add_element(Now + Next, {1}, S) 175 | end, Slide, [5]), 176 | 177 | S2 = lists:foldl(fun (Next, S) -> 178 | exometer_slide:add_element(Now + Next, {1}, S) 179 | end, Slide, [500, 505, 510, 515, 520, 525, 527]), 180 | S3 = exometer_slide:sum([S1, S2], {0}), 181 | [500, 505, 510, 515, 520, 525] = [T || {T, _} <- exometer_slide:to_list(525, S3)], 182 | [7,6,5,4,3,2] = lists:reverse([V || {_, {V}} <- exometer_slide:to_list(525, S3)]). 183 | 184 | incremental_sum_with_drop(_Config) -> 185 | Now = 0, 186 | Slide = exometer_slide:new(Now, 25, [{incremental, true}, 187 | {max_n, 5}, 188 | {interval, 5}]), 189 | 190 | S1 = lists:foldl(fun ({Next, Incr}, S) -> 191 | exometer_slide:add_element(Now + Next, {Incr}, S) 192 | end, Slide, [{1, 1}, {8, 0}, {15, 0}, {21, 1}, {27, 0}]), 193 | 194 | S2 = lists:foldl(fun (Next, S) -> 195 | exometer_slide:add_element(Now + Next, {1}, S) 196 | end, Slide, [2, 7, 14, 20, 25]), 197 | S3 = exometer_slide:sum([S1, S2]), 198 | [27,22,17,12,7] = lists:reverse([T || {T, _} <- exometer_slide:to_list(27, S3)]), 199 | [7,6,4,3,2] = lists:reverse([V || {_, {V}} <- exometer_slide:to_list(27, S3)]). 200 | 201 | incremental_sum_with_total(_Config) -> 202 | Now = 0, 203 | Slide = exometer_slide:new(Now, 50, [{incremental, true}, {interval, 5}]), 204 | 205 | S1 = lists:foldl(fun (Next, S) -> 206 | exometer_slide:add_element(Now + Next, {1}, S) 207 | end, Slide, [5, 10, 15, 20, 25]), 208 | 209 | S2 = lists:foldl(fun (Next, S) -> 210 | exometer_slide:add_element(Now + Next, {1}, S) 211 | end, Slide, [7, 12, 17, 22, 23]), 212 | S3 = exometer_slide:sum([S1, S2]), 213 | {10} = exometer_slide:last(S3), 214 | [25,20,15,10,5] = lists:reverse([T || {T, _} <- exometer_slide:to_list(26, S3)]), 215 | [ 9, 7, 5, 3,1] = lists:reverse([V || {_, {V}} <- exometer_slide:to_list(26, S3)]). 216 | 217 | foldl_realises_partial_sample(_Config) -> 218 | Now = 0, 219 | Slide = exometer_slide:new(Now, 25, [{incremental, true}, {interval, 5}]), 220 | S = lists:foldl(fun (Next, S) -> 221 | exometer_slide:add_element(Now + Next, {1}, S) 222 | end, Slide, [5, 10, 15, 20, 23]), 223 | Fun = fun(last, Acc) -> Acc; 224 | ({TS, {X}}, Acc) -> [{TS, X} | Acc] 225 | end, 226 | 227 | [{25, 5}, {20, 4}, {15, 3}, {10, 2}, {5, 1}] = 228 | exometer_slide:foldl(25, 5, Fun, [], S), 229 | [{20, 4}, {15, 3}, {10, 2}, {5, 1}] = 230 | exometer_slide:foldl(20, 5, Fun, [], S), 231 | % do not realise sample unless Now is at least an interval beyond the last 232 | % full sample 233 | [{20, 4}, {15, 3}, {10, 2}, {5, 1}] = 234 | exometer_slide:foldl(23, 5, Fun, [], S). 235 | 236 | optimize(_Config) -> 237 | Now = 0, 238 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, {max_n, 5}]), 239 | S = lists:foldl(fun (Next, S) -> 240 | exometer_slide:add_element(Now + Next, {Next}, S) 241 | end, Slide, [5, 10, 15, 20, 25, 30, 35]), 242 | OS = exometer_slide:optimize(S), 243 | SRes = exometer_slide:to_list(35, S), 244 | OSRes = exometer_slide:to_list(35, OS), 245 | SRes = OSRes, 246 | ?assert(S =/= OS). 247 | 248 | to_list_with_drop(_Config) -> 249 | Now = 0, 250 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, 251 | {incremental, true}, 252 | {max_n, 5}]), 253 | S = exometer_slide:add_element(30, {1}, Slide), 254 | S2 = exometer_slide:add_element(35, {1}, S), 255 | S3 = exometer_slide:add_element(40, {0}, S2), 256 | S4 = exometer_slide:add_element(45, {0}, S3), 257 | [{30, {1}}, {35, {2}}, {40, {2}}, {45, {2}}] = exometer_slide:to_list(45, S4). 258 | 259 | to_list_simple(_Config) -> 260 | Now = 0, 261 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, 262 | {incremental, true}, 263 | {max_n, 5}]), 264 | S = exometer_slide:add_element(30, {0}, Slide), 265 | S2 = exometer_slide:add_element(35, {0}, S), 266 | [{30, {0}}, {35, {0}}] = exometer_slide:to_list(38, S2). 267 | 268 | foldl_with_drop(_Config) -> 269 | Now = 0, 270 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, 271 | {incremental, true}, 272 | {max_n, 5}]), 273 | S = exometer_slide:add_element(30, {1}, Slide), 274 | S2 = exometer_slide:add_element(35, {1}, S), 275 | S3 = exometer_slide:add_element(40, {0}, S2), 276 | S4 = exometer_slide:add_element(45, {0}, S3), 277 | Fun = fun(last, Acc) -> Acc; 278 | ({TS, {X}}, Acc) -> [{TS, X} | Acc] 279 | end, 280 | [{45, 2}, {40, 2}, {35, 2}, {30, 1}] = 281 | exometer_slide:foldl(45, 30, Fun, [], S4). 282 | 283 | foldl_and_to_list(_Config) -> 284 | Now = 0, 285 | Tests = [ % {input, expected, query range} 286 | {[], 287 | [], 288 | {0, 10}}, 289 | {[{5, 1}], 290 | [{5, {1}}], 291 | {0, 5}}, 292 | {[{10, 1}], 293 | [{10, {1}}], 294 | {0, 10}}, 295 | {[{5, 1}, {10, 2}], 296 | [{10, {2}}, {5, {1}}], 297 | {0, 10}}, 298 | {[{5, 0}, {10, 0}], % drop 1 299 | [{10, {0}}, {5, {0}}], 300 | {0, 10}}, 301 | {[{5, 2}, {10, 1}, {15, 1}], % drop 2 302 | [{15, {1}}, {10, {1}}, {5, {2}}], 303 | {0, 15}}, 304 | {[{10, 0}, {15, 0}, {20, 0}], % drop 305 | [{20, {0}}, {15, {0}}, {10, {0}}], 306 | {0, 20}}, 307 | {[{5, 1}, {10, 5}, {15, 5}, {20, 0}], % drop middle 308 | [{20, {0}}, {15, {5}}, {10, {5}}, {5, {1}}], 309 | {0, 20}}, 310 | {[{5, 1}, {10, 5}, {15, 5}, {20, 1}], % drop middle filtered 311 | [{20, {1}}, {15, {5}}, {10, {5}}], 312 | {10, 20}}, 313 | {[{5, 1}, {10, 2}, {15, 3}, {20, 4}, {25, 4}, {30, 5}], % buffer roll over 314 | [{30, {5}}, {25, {4}}, {20, {4}}, {15, {3}}, {10, {2}}, {5, {1}}], 315 | {5, 30}} 316 | ], 317 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, 318 | {max_n, 5}]), 319 | Fun = fun(last, Acc) -> Acc; 320 | (V, Acc) -> [V | Acc] 321 | end, 322 | [begin 323 | S = lists:foldl(fun ({T, V}, Acc) -> 324 | exometer_slide:add_element(T, {V}, Acc) 325 | end, Slide, Inputs), 326 | Expected = exometer_slide:foldl(To, From, Fun, [], S), 327 | ExpRev = lists:reverse(Expected), 328 | ExpRev = exometer_slide:to_list(To, From, S) 329 | end || {Inputs, Expected, {From, To}} <- Tests]. 330 | 331 | foldl_and_to_list_incremental(_Config) -> 332 | Now = 0, 333 | Tests = [ % {input, expected, query range} 334 | {[], 335 | [], 336 | {0, 10}}, 337 | {[{5, 1}], 338 | [{5, {1}}], 339 | {0, 5}}, 340 | {[{10, 1}], 341 | [{10, {1}}], 342 | {0, 10}}, 343 | {[{5, 1}, {10, 1}], 344 | [{10, {2}}, {5, {1}}], 345 | {0, 10}}, 346 | {[{5, 0}, {10, 0}], % drop 1 347 | [{10, {0}}, {5, {0}}], 348 | {0, 10}}, 349 | {[{5, 1}, {10, 0}, {15, 0}], % drop 2 350 | [{15, {1}}, {10, {1}}, {5, {1}}], 351 | {0, 15}}, 352 | {[{10, 0}, {15, 0}, {20, 0}], % drop 353 | [{20, {0}}, {15, {0}}, {10, {0}}], 354 | {0, 20}}, 355 | {[{5, 1}, {10, 0}, {15, 0}, {20, 1}], % drop middle 356 | [{20, {2}}, {15, {1}}, {10, {1}}, {5, {1}}], 357 | {0, 20}}, 358 | {[{5, 1}, {10, 0}, {15, 0}, {20, 1}], % drop middle filtered 359 | [{20, {2}}, {15, {1}}, {10, {1}}], 360 | {10, 20}}, 361 | {[{5, 1}, {10, 1}, {15, 1}, {20, 1}, {25, 0}, {30, 1}], % buffer roll over 362 | [{30, {5}}, {25, {4}}, {20, {4}}, {15, {3}}, {10, {2}}, {5, {1}}], 363 | {5, 30}} 364 | ], 365 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, 366 | {incremental, true}, 367 | {max_n, 5}]), 368 | Fun = fun(last, Acc) -> Acc; 369 | (V, Acc) -> [V | Acc] 370 | end, 371 | [begin 372 | S = lists:foldl(fun ({T, V}, Acc) -> 373 | exometer_slide:add_element(T, {V}, Acc) 374 | end, Slide, Inputs), 375 | Expected = exometer_slide:foldl(To, From, Fun, [], S), 376 | ExpRev = lists:reverse(Expected), 377 | ExpRev = exometer_slide:to_list(To, From, S) 378 | end || {Inputs, Expected, {From, To}} <- Tests]. 379 | 380 | stale_to_list(_Config) -> 381 | Now = 0, 382 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, {max_n, 5}]), 383 | S = exometer_slide:add_element(50, {1}, Slide), 384 | S2 = exometer_slide:add_element(55, {1}, S), 385 | [] = exometer_slide:to_list(100, S2). 386 | 387 | to_list_single_after_drop(_Config) -> 388 | Now = 0, 389 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, 390 | {incremental, true}, 391 | {max_n, 5}]), 392 | S = exometer_slide:add_element(5, {0}, Slide), 393 | S2 = exometer_slide:add_element(10, {0}, S), 394 | S3 = exometer_slide:add_element(15, {1}, S2), 395 | Res = exometer_slide:to_list(17, S3), 396 | [{5,{0}},{10,{0}},{15,{1}}] = Res. 397 | 398 | 399 | to_list_drop_and_roll(_Config) -> 400 | Now = 0, 401 | Slide = exometer_slide:new(Now, 10, [{interval, 5}, 402 | {incremental, true}, 403 | {max_n, 5}]), 404 | S = exometer_slide:add_element(5, {0}, Slide), 405 | S2 = exometer_slide:add_element(10, {0}, S), 406 | S3 = exometer_slide:add_element(15, {0}, S2), 407 | [{10, {0}}, {15, {0}}] = exometer_slide:to_list(17, S3). 408 | 409 | 410 | sum_single(_Config) -> 411 | Now = 0, 412 | Slide = exometer_slide:new(Now, 25, [{interval, 5}, 413 | {incremental, true}, 414 | {max_n, 5}]), 415 | S = exometer_slide:add_element(Now + 5, {0}, Slide), 416 | S2 = exometer_slide:add_element(Now + 10, {0}, S), 417 | Summed = exometer_slide:sum([S2]), 418 | [_,_] = exometer_slide:to_list(15, Summed). 419 | 420 | 421 | to_normalized_list(_Config) -> 422 | Interval = 5, 423 | Tests = [ % {input, expected, query range} 424 | {[], % zero pad when slide has never seen any samples 425 | [{10, {0}}, {5, {0}}, {0, {0}}], 426 | {0, 10}}, 427 | {[{5, 1}], % zero pad before first known sample 428 | [{5, {1}}, {0, {0}}], 429 | {0, 5}}, 430 | {[{10, 1}, {15, 1}], % zero pad before last know sample 431 | [{15, {2}}, {10, {1}}, {5, {0}}], 432 | {5, 15}}, 433 | {[{5, 1}, {15, 1}], % insert missing sample using previous total 434 | [{15, {2}}, {10, {1}}, {5, {1}}], 435 | {5, 15}}, 436 | % {[{6, 1}, {11, 1}, {16, 1}], % align timestamps with query 437 | % [{15, {3}}, {10, {2}}, {5, {1}}, {0, {0}}], 438 | % {0, 15}}, 439 | {[{5, 1}, {10, 1}, {15, 1}, {20, 1}, {25, 1}, {30, 1}], % outside of max_n 440 | [{30, {6}}, {25, {5}}, {20, {4}}, {15, {3}}, {10, {2}}], % we cannot possibly be expected deduce what 10 should be 441 | {10, 30}}, 442 | {[{5, 1}, {20, 1}, {25, 1}], % as long as the past TS 5 sample still exists we should use to for padding 443 | [{25, {3}}, {20, {2}}, {15, {1}}, {10, {1}}], 444 | {10, 25}}, 445 | {[{5, 1}, {10, 1}], % pad based on total 446 | [{35, {2}}, {30, {2}}], 447 | {30, 35}}, 448 | {[{5, 1}], % make up future values to fill the window 449 | [{10, {1}}, {5, {1}}], 450 | {5, 10}}, 451 | {[{5, 1}, {7, 1}], % realise last sample 452 | [{10, {2}}, {5, {1}}], 453 | {5, 10}} 454 | ], 455 | 456 | Slide = exometer_slide:new(0, 20, [{interval, 5}, 457 | {incremental, true}, 458 | {max_n, 4}]), 459 | [begin 460 | S0 = lists:foldl(fun ({T, V}, Acc) -> 461 | exometer_slide:add_element(T, {V}, Acc) 462 | end, Slide, Inputs), 463 | Expected = exometer_slide:to_normalized_list(To, From, Interval, S0, {0}), 464 | S = exometer_slide:sum([exometer_slide:optimize(S0)], {0}), % also test it post sum 465 | Expected = exometer_slide:to_normalized_list(To, From, Interval, S, {0}) 466 | end || {Inputs, Expected, {From, To}} <- Tests]. 467 | 468 | to_normalized_list_no_padding(_Config) -> 469 | Interval = 5, 470 | Tests = [ % {input, expected, query range} 471 | {[], 472 | [], 473 | {0, 10}}, 474 | {[{5, 1}], 475 | [{5, {1}}], 476 | {0, 5}}, 477 | {[{5, 1}, {15, 1}], 478 | [{15, {2}}, {10, {1}}, {5, {1}}], 479 | {5, 15}}, 480 | {[{10, 1}, {15, 1}], 481 | [{15, {2}}, {10, {1}}], 482 | {5, 15}}, 483 | {[{5, 1}, {20, 1}], % NB as 5 is outside of the query we can't pick the value up 484 | [{20, {2}}], 485 | {10, 20}} 486 | ], 487 | 488 | Slide = exometer_slide:new(0, 20, [{interval, 5}, 489 | {incremental, true}, 490 | {max_n, 4}]), 491 | [begin 492 | S = lists:foldl(fun ({T, V}, Acc) -> 493 | exometer_slide:add_element(T, {V}, Acc) 494 | end, Slide, Inputs), 495 | Expected = exometer_slide:to_normalized_list(To, From, Interval, S, no_pad) 496 | end || {Inputs, Expected, {From, To}} <- Tests]. 497 | 498 | to_list_in_the_past(_Config) -> 499 | Slide = exometer_slide:new(0, 20, [{interval, 5}, 500 | {incremental, true}, 501 | {max_n, 4}]), 502 | % ensure firstTS is way in the past 503 | S0 = exometer_slide:add_element(5, {1}, Slide), 504 | S1 = exometer_slide:add_element(105, {0}, S0), 505 | S = exometer_slide:add_element(110, {0}, S1), % create drop 506 | % query into the past 507 | % this could happen if a node with and incorrect clock joins the cluster 508 | [] = exometer_slide:to_list(50, 10, S). 509 | 510 | sum_mgmt_352(_Config) -> 511 | %% In bug mgmt#352 all the samples returned have the same vale 512 | Slide = sum_mgmt_352_slide(), 513 | Last = 1487689330000, 514 | First = 1487689270000, 515 | Incr = 5000, 516 | Empty = {0}, 517 | Sum = exometer_slide:sum(Last, First, Incr, [Slide], Empty), 518 | Values = sets:to_list(sets:from_list( 519 | [V || {_, V} <- exometer_slide:buffer(Sum)])), 520 | true = (length(Values) == 12), 521 | ok. 522 | 523 | sum_mgmt_352_extra(_Config) -> 524 | %% Testing previous case clause to the one that fixes mgmt_352 525 | %% exometer_slide.erl#L463 526 | %% In the buggy version, all but the last sample are the same 527 | Slide = sum_mgmt_352_slide_extra(), 528 | Last = 1487689330000, 529 | First = 1487689260000, 530 | Incr = 5000, 531 | Empty = {0}, 532 | Sum = exometer_slide:sum(Last, First, Incr, [Slide], Empty), 533 | Values = sets:to_list(sets:from_list( 534 | [V || {_, V} <- exometer_slide:buffer(Sum)])), 535 | true = (length(Values) == 13), 536 | ok. 537 | 538 | sum_mgmt_352_peak(_Config) -> 539 | %% When buf2 contains data, we were returning a too old sample that 540 | %% created a massive rate peak at the beginning of the graph. 541 | %% The sample used was captured during a debug session. 542 | Slide = sum_mgmt_352_slide_peak(), 543 | Last = 1487752040000, 544 | First = 1487751980000, 545 | Incr = 5000, 546 | Empty = {0}, 547 | Sum = exometer_slide:sum(Last, First, Incr, [Slide], Empty), 548 | [{LastV}, {BLastV} | _] = 549 | lists:reverse([V || {_, V} <- exometer_slide:buffer(Sum)]), 550 | Rate = (BLastV - LastV) div 5, 551 | true = (Rate < 20000), 552 | ok. 553 | 554 | %% ------------------------------------------------------------------- 555 | %% Util 556 | %% ------------------------------------------------------------------- 557 | 558 | ele(TS, V) -> {TS, {V}}. 559 | 560 | %% ------------------------------------------------------------------- 561 | %% Data 562 | %% ------------------------------------------------------------------- 563 | sum_mgmt_352_slide() -> 564 | %% Provide slide as is, from a debug session triggering mgmt-352 bug 565 | {slide,610000,45,122,true,5000,1487689328468,1487689106834, 566 | [{1487689328468,{1574200}}, 567 | {1487689323467,{1538800}}, 568 | {1487689318466,{1500800}}, 569 | {1487689313465,{1459138}}, 570 | {1487689308463,{1419200}}, 571 | {1487689303462,{1379600}}, 572 | {1487689298461,{1340000}}, 573 | {1487689293460,{1303400}}, 574 | {1487689288460,{1265600}}, 575 | {1487689283458,{1231400}}, 576 | {1487689278457,{1215800}}, 577 | {1487689273456,{1215200}}, 578 | {1487689262487,drop}, 579 | {1487689257486,{1205600}}], 580 | [], 581 | {1591000}}. 582 | 583 | sum_mgmt_352_slide_extra() -> 584 | {slide,610000,45,122,true,5000,1487689328468,1487689106834, 585 | [{1487689328468,{1574200}}, 586 | {1487689323467,{1538800}}, 587 | {1487689318466,{1500800}}, 588 | {1487689313465,{1459138}}, 589 | {1487689308463,{1419200}}, 590 | {1487689303462,{1379600}}, 591 | {1487689298461,{1340000}}, 592 | {1487689293460,{1303400}}, 593 | {1487689288460,{1265600}}, 594 | {1487689283458,{1231400}}, 595 | {1487689278457,{1215800}}, 596 | {1487689273456,{1215200}}, 597 | {1487689272487,drop}, 598 | {1487689269486,{1205600}}], 599 | [], 600 | {1591000}}. 601 | 602 | sum_mgmt_352_slide_peak() -> 603 | {slide,610000,96,122,true,5000,1487752038481,1487750936863, 604 | [{1487752038481,{11994024}}, 605 | {1487752033480,{11923200}}, 606 | {1487752028476,{11855800}}, 607 | {1487752023474,{11765800}}, 608 | {1487752018473,{11702431}}, 609 | {1487752013472,{11636200}}, 610 | {1487752008360,{11579800}}, 611 | {1487752003355,{11494800}}, 612 | {1487751998188,{11441400}}, 613 | {1487751993184,{11381000}}, 614 | {1487751988180,{11320000}}, 615 | {1487751983178,{11263000}}, 616 | {1487751978177,{11187600}}, 617 | {1487751973172,{11123375}}, 618 | {1487751968167,{11071800}}, 619 | {1487751963166,{11006200}}, 620 | {1487751958162,{10939477}}, 621 | {1487751953161,{10882400}}, 622 | {1487751948140,{10819600}}, 623 | {1487751943138,{10751200}}, 624 | {1487751938134,{10744400}}, 625 | {1487751933129,drop}, 626 | {1487751927807,{10710200}}, 627 | {1487751922803,{10670000}}], 628 | [{1487751553386,{6655800}}, 629 | {1487751548385,{6580365}}, 630 | {1487751543384,{6509358}}], 631 | {11994024}}. 632 | -------------------------------------------------------------------------------- /test/metrics_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(metrics_SUITE). 8 | -compile(export_all). 9 | 10 | -include_lib("common_test/include/ct.hrl"). 11 | -include_lib("amqp_client/include/amqp_client.hrl"). 12 | 13 | all() -> 14 | [ 15 | {group, non_parallel_tests} 16 | ]. 17 | 18 | groups() -> 19 | [ 20 | {non_parallel_tests, [], [ 21 | node, 22 | storage_reset 23 | ]} 24 | ]. 25 | 26 | %% ------------------------------------------------------------------- 27 | %% Testsuite setup/teardown. 28 | %% ------------------------------------------------------------------- 29 | 30 | merge_app_env(Config) -> 31 | _Config1 = rabbit_ct_helpers:merge_app_env(Config, 32 | {rabbit, [ 33 | {collect_statistics, fine}, 34 | {collect_statistics_interval, 500} 35 | ]}). 36 | %% rabbit_ct_helpers:merge_app_env( 37 | %% Config1, {rabbitmq_management_agent, [{sample_retention_policies, 38 | %% [{global, [{605, 500}]}, 39 | %% {basic, [{605, 500}]}, 40 | %% {detailed, [{10, 500}]}] }]}). 41 | 42 | init_per_suite(Config) -> 43 | rabbit_ct_helpers:log_environment(), 44 | Config1 = rabbit_ct_helpers:set_config(Config, [ 45 | {rmq_nodename_suffix, ?MODULE}, 46 | {rmq_nodes_count, 2} 47 | ]), 48 | rabbit_ct_helpers:run_setup_steps(Config1, 49 | [ fun merge_app_env/1 ] ++ 50 | rabbit_ct_broker_helpers:setup_steps() ++ 51 | rabbit_ct_client_helpers:setup_steps()). 52 | 53 | end_per_suite(Config) -> 54 | rabbit_ct_helpers:run_teardown_steps(Config, 55 | rabbit_ct_client_helpers:teardown_steps() ++ 56 | rabbit_ct_broker_helpers:teardown_steps()). 57 | 58 | init_per_group(_, Config) -> 59 | Config. 60 | 61 | end_per_group(_, Config) -> 62 | Config. 63 | 64 | init_per_testcase(Testcase, Config) -> 65 | rabbit_ct_helpers:testcase_started(Config, Testcase). 66 | 67 | end_per_testcase(Testcase, Config) -> 68 | rabbit_ct_helpers:testcase_finished(Config, Testcase). 69 | 70 | 71 | %% ------------------------------------------------------------------- 72 | %% Testcases. 73 | %% ------------------------------------------------------------------- 74 | 75 | read_table_rpc(Config, Table) -> 76 | rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, read_table, [Table]). 77 | 78 | read_table(Table) -> 79 | ets:tab2list(Table). 80 | 81 | force_stats() -> 82 | rabbit_mgmt_external_stats ! emit_update. 83 | 84 | node(Config) -> 85 | [A, B] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename), 86 | % force multipe stats refreshes 87 | [ rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, force_stats, []) 88 | || _ <- lists:seq(0, 10)], 89 | [_] = read_table_rpc(Config, node_persister_metrics), 90 | [_] = read_table_rpc(Config, node_coarse_metrics), 91 | [_] = read_table_rpc(Config, node_metrics), 92 | true = wait_until( 93 | fun() -> 94 | Tab = read_table_rpc(Config, node_node_metrics), 95 | lists:keymember({A, B}, 1, Tab) 96 | end, 10). 97 | 98 | 99 | storage_reset(Config) -> 100 | %% Ensures that core stats are reset, otherwise consume generates negative values 101 | %% Doesn't really test if the reset does anything! 102 | {Ch, Q} = publish_msg(Config), 103 | wait_until(fun() -> 104 | {1, 0, 1} == get_vhost_stats(Config) 105 | end), 106 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_storage, reset, []), 107 | wait_until(fun() -> 108 | {1, 0, 1} == get_vhost_stats(Config) 109 | end), 110 | consume_msg(Ch, Q), 111 | wait_until(fun() -> 112 | {0, 0, 0} == get_vhost_stats(Config) 113 | end), 114 | rabbit_ct_client_helpers:close_channel(Ch). 115 | 116 | %% ------------------------------------------------------------------- 117 | %% Helpers 118 | %% ------------------------------------------------------------------- 119 | 120 | publish_msg(Config) -> 121 | Ch = rabbit_ct_client_helpers:open_channel(Config, 0), 122 | #'queue.declare_ok'{queue = Q} = 123 | amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), 124 | amqp_channel:cast(Ch, #'basic.publish'{routing_key = Q}, 125 | #amqp_msg{props = #'P_basic'{delivery_mode = 2}, 126 | payload = Q}), 127 | {Ch, Q}. 128 | 129 | consume_msg(Ch, Q) -> 130 | amqp_channel:subscribe(Ch, #'basic.consume'{queue = Q, 131 | no_ack = true}, self()), 132 | receive #'basic.consume_ok'{} -> ok 133 | end, 134 | receive {#'basic.deliver'{}, #amqp_msg{payload = Q}} -> 135 | ok 136 | end. 137 | 138 | wait_until(Fun) -> 139 | wait_until(Fun, 120). 140 | 141 | wait_until(_, 0) -> 142 | false; 143 | wait_until(Fun, N) -> 144 | case Fun() of 145 | true -> 146 | true; 147 | false -> 148 | timer:sleep(1000), 149 | wait_until(Fun, N-1) 150 | end. 151 | 152 | get_vhost_stats(Config) -> 153 | Dict = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_data, overview_data, 154 | [none, all ,{no_range, no_range, no_range,no_range}, 155 | [<<"/">>]]), 156 | {ok, {VhostMsgStats, _}} = maps:find(vhost_msg_stats, Dict), 157 | exometer_slide:last(VhostMsgStats). 158 | -------------------------------------------------------------------------------- /test/rabbit_mgmt_slide_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_mgmt_slide_SUITE). 9 | 10 | -include_lib("proper/include/proper.hrl"). 11 | 12 | -compile(export_all). 13 | 14 | all() -> 15 | [ 16 | {group, parallel_tests} 17 | ]. 18 | 19 | groups() -> 20 | [ 21 | {parallel_tests, [parallel], [ 22 | last_two_test, 23 | last_two_incremental_test, 24 | sum_test, 25 | sum_incremental_test 26 | ]} 27 | ]. 28 | 29 | %% ------------------------------------------------------------------- 30 | %% Testsuite setup/teardown. 31 | %% ------------------------------------------------------------------- 32 | init_per_suite(Config) -> 33 | rabbit_ct_helpers:log_environment(), 34 | Config. 35 | 36 | end_per_suite(_Config) -> 37 | ok. 38 | 39 | init_per_group(_, Config) -> 40 | Config. 41 | 42 | end_per_group(_, _Config) -> 43 | ok. 44 | 45 | init_per_testcase(_, Config) -> 46 | Config. 47 | 48 | end_per_testcase(_, _Config) -> 49 | ok. 50 | 51 | %% ------------------------------------------------------------------- 52 | %% Generators. 53 | %% ------------------------------------------------------------------- 54 | elements_gen() -> 55 | ?LET(Length, oneof([1, 2, 3, 7, 8, 20]), 56 | ?LET(Elements, list(vector(Length, int())), 57 | [erlang:list_to_tuple(E) || E <- Elements])). 58 | 59 | %% ------------------------------------------------------------------- 60 | %% Testcases. 61 | %% ------------------------------------------------------------------- 62 | last_two_test(_Config) -> 63 | rabbit_ct_proper_helpers:run_proper(fun prop_last_two/0, [], 100). 64 | 65 | prop_last_two() -> 66 | ?FORALL( 67 | Elements, elements_gen(), 68 | begin 69 | Interval = 1, 70 | Incremental = false, 71 | {_LastTS, Slide} = new_slide(Interval, Incremental, Elements), 72 | Expected = last_two(Elements), 73 | ValuesOnly = [V || {_Timestamp, V} <- exometer_slide:last_two(Slide)], 74 | ?WHENFAIL(io:format("Last two values obtained: ~p~nExpected: ~p~n" 75 | "Slide: ~p~n", [ValuesOnly, Expected, Slide]), 76 | Expected == ValuesOnly) 77 | end). 78 | 79 | last_two_incremental_test(_Config) -> 80 | rabbit_ct_proper_helpers:run_proper(fun prop_last_two_incremental/0, [], 100). 81 | 82 | prop_last_two_incremental() -> 83 | ?FORALL( 84 | Elements, non_empty(elements_gen()), 85 | begin 86 | Interval = 1, 87 | Incremental = true, 88 | {_LastTS, Slide} = new_slide(Interval, Incremental, Elements), 89 | [{_Timestamp, Values} | _] = exometer_slide:last_two(Slide), 90 | Expected = add_elements(Elements), 91 | ?WHENFAIL(io:format("Expected a total of: ~p~nGot: ~p~n" 92 | "Slide: ~p~n", [Expected, Values, Slide]), 93 | Values == Expected) 94 | end). 95 | 96 | sum_incremental_test(_Config) -> 97 | rabbit_ct_proper_helpers:run_proper(fun prop_sum/1, [true], 100). 98 | 99 | sum_test(_Config) -> 100 | rabbit_ct_proper_helpers:run_proper(fun prop_sum/1, [false], 100). 101 | 102 | prop_sum(Inc) -> 103 | ?FORALL( 104 | {Elements, Number}, {non_empty(elements_gen()), ?SUCHTHAT(I, int(), I > 0)}, 105 | begin 106 | Interval = 1, 107 | {LastTS, Slide} = new_slide(Interval, Inc, Elements), 108 | %% Add the same so the timestamp matches. As the timestamps are handled 109 | %% internally, we cannot guarantee on which interval they go otherwise 110 | %% (unless we manually manipulate the slide content). 111 | Sum = exometer_slide:sum([Slide || _ <- lists:seq(1, Number)]), 112 | Values = [V || {_TS, V} <- exometer_slide:to_list(LastTS + 1, Sum)], 113 | Expected = expected_sum(Slide, LastTS + 1, Number, Interval, Inc), 114 | ?WHENFAIL(io:format("Expected: ~p~nGot: ~p~nSlide:~p~n", 115 | [Expected, Values, Slide]), 116 | Values == Expected) 117 | end). 118 | 119 | expected_sum(Slide, Now, Number, _Int, false) -> 120 | [sum_n_times(V, Number) || {_TS, V} <- exometer_slide:to_list(Now, Slide)]; 121 | expected_sum(Slide, Now, Number, Int, true) -> 122 | [{TSfirst, First} = F | Rest] = All = exometer_slide:to_list(Now, Slide), 123 | {TSlast, _Last} = case Rest of 124 | [] -> 125 | F; 126 | _ -> 127 | lists:last(Rest) 128 | end, 129 | Seq = lists:seq(TSfirst, TSlast, Int), 130 | {Expected, _} = lists:foldl(fun(TS0, {Acc, Previous}) -> 131 | Actual = proplists:get_value(TS0, All, Previous), 132 | {[sum_n_times(Actual, Number) | Acc], Actual} 133 | end, {[], First}, Seq), 134 | lists:reverse(Expected). 135 | %% ------------------------------------------------------------------- 136 | %% Helpers 137 | %% ------------------------------------------------------------------- 138 | new_slide(Interval, Incremental, Elements) -> 139 | new_slide(Interval, Interval, Incremental, Elements). 140 | 141 | new_slide(PublishInterval, Interval, Incremental, Elements) -> 142 | Now = 0, 143 | Slide = exometer_slide:new(Now, 60 * 1000, [{interval, Interval}, 144 | {incremental, Incremental}]), 145 | lists:foldl( 146 | fun(E, {TS0, Acc}) -> 147 | TS1 = TS0 + PublishInterval, 148 | {TS1, exometer_slide:add_element(TS1, E, Acc)} 149 | end, {Now, Slide}, Elements). 150 | 151 | last_two(Elements) when length(Elements) >= 2 -> 152 | [F, S | _] = lists:reverse(Elements), 153 | [F, S]; 154 | last_two(Elements) -> 155 | Elements. 156 | 157 | add_elements([H | T]) -> 158 | add_elements(T, H). 159 | 160 | add_elements([], Acc) -> 161 | Acc; 162 | add_elements([Tuple | T], Acc) -> 163 | add_elements(T, sum(Tuple, Acc)). 164 | 165 | sum(T1, T2) -> 166 | list_to_tuple(lists:zipwith(fun(A, B) -> A + B end, tuple_to_list(T1), tuple_to_list(T2))). 167 | 168 | sum_n_times(V, N) -> 169 | sum_n_times(V, V, N - 1). 170 | 171 | sum_n_times(_V, Acc, 0) -> 172 | Acc; 173 | sum_n_times(V, Acc, N) -> 174 | sum_n_times(V, sum(V, Acc), N-1). 175 | --------------------------------------------------------------------------------