diff --git a/ptrans_deporder/.gitignore b/ptrans_deporder/.gitignore new file mode 100644 index 0000000..f1c4554 --- /dev/null +++ b/ptrans_deporder/.gitignore @@ -0,0 +1,19 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ diff --git a/ptrans_deporder/LICENSE b/ptrans_deporder/LICENSE new file mode 100644 index 0000000..f0412c7 --- /dev/null +++ b/ptrans_deporder/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021, Fred Hebert . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/ptrans_deporder/README.md b/ptrans_deporder/README.md new file mode 100644 index 0000000..c8d4e39 --- /dev/null +++ b/ptrans_deporder/README.md @@ -0,0 +1,9 @@ +ptrans_deporder +===== + +An OTP library + +Build +----- + + $ rebar3 compile diff --git a/ptrans_deporder/_checkouts/a_dep/.gitignore b/ptrans_deporder/_checkouts/a_dep/.gitignore new file mode 100644 index 0000000..f1c4554 --- /dev/null +++ b/ptrans_deporder/_checkouts/a_dep/.gitignore @@ -0,0 +1,19 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ diff --git a/ptrans_deporder/_checkouts/a_dep/LICENSE b/ptrans_deporder/_checkouts/a_dep/LICENSE new file mode 100644 index 0000000..f0412c7 --- /dev/null +++ b/ptrans_deporder/_checkouts/a_dep/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021, Fred Hebert . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/ptrans_deporder/_checkouts/a_dep/README.md b/ptrans_deporder/_checkouts/a_dep/README.md new file mode 100644 index 0000000..ac2c27d --- /dev/null +++ b/ptrans_deporder/_checkouts/a_dep/README.md @@ -0,0 +1,9 @@ +top_dep +===== + +An OTP library + +Build +----- + + $ rebar3 compile diff --git a/ptrans_deporder/_checkouts/a_dep/rebar.config b/ptrans_deporder/_checkouts/a_dep/rebar.config new file mode 100644 index 0000000..0a2fc62 --- /dev/null +++ b/ptrans_deporder/_checkouts/a_dep/rebar.config @@ -0,0 +1,4 @@ +{erl_opts, [debug_info]}. +{deps, [ + b_dep +]}. diff --git a/ptrans_deporder/_checkouts/a_dep/src/a_dep.app.src b/ptrans_deporder/_checkouts/a_dep/src/a_dep.app.src new file mode 100644 index 0000000..ac884f0 --- /dev/null +++ b/ptrans_deporder/_checkouts/a_dep/src/a_dep.app.src @@ -0,0 +1,14 @@ +{application, a_dep, + [{description, "An OTP library"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/ptrans_deporder/_checkouts/a_dep/src/a_dep.erl b/ptrans_deporder/_checkouts/a_dep/src/a_dep.erl new file mode 100644 index 0000000..d46e8c0 --- /dev/null +++ b/ptrans_deporder/_checkouts/a_dep/src/a_dep.erl @@ -0,0 +1,5 @@ +-module(a_dep). + +-compile([{parse_transform, b_dep}]). + + diff --git a/ptrans_deporder/_checkouts/b_dep/.gitignore b/ptrans_deporder/_checkouts/b_dep/.gitignore new file mode 100644 index 0000000..f1c4554 --- /dev/null +++ b/ptrans_deporder/_checkouts/b_dep/.gitignore @@ -0,0 +1,19 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ diff --git a/ptrans_deporder/_checkouts/b_dep/rebar.config b/ptrans_deporder/_checkouts/b_dep/rebar.config new file mode 100644 index 0000000..657e9c3 --- /dev/null +++ b/ptrans_deporder/_checkouts/b_dep/rebar.config @@ -0,0 +1,4 @@ +{erl_opts, [debug_info]}. +{deps, [ + parse_trans +]}. diff --git a/ptrans_deporder/_checkouts/b_dep/src/b_dep.app.src b/ptrans_deporder/_checkouts/b_dep/src/b_dep.app.src new file mode 100644 index 0000000..e9cd14c --- /dev/null +++ b/ptrans_deporder/_checkouts/b_dep/src/b_dep.app.src @@ -0,0 +1,14 @@ +{application, b_dep, + [{description, "An OTP library"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/ptrans_deporder/_checkouts/b_dep/src/b_dep.erl b/ptrans_deporder/_checkouts/b_dep/src/b_dep.erl new file mode 100644 index 0000000..b483c5c --- /dev/null +++ b/ptrans_deporder/_checkouts/b_dep/src/b_dep.erl @@ -0,0 +1,11 @@ +-module(b_dep). +-export([parse_transform/2]). + +-spec parse_transform(term(), term()) -> term(). +parse_transform(Forms, _Options) -> + try parse_trans:module_info() of + _ -> Forms + catch + % ignore version errors + error:T when T =/= undef -> Forms + end. diff --git a/ptrans_deporder/_checkouts/exometer_core/LICENSE b/ptrans_deporder/_checkouts/exometer_core/LICENSE new file mode 100644 index 0000000..e87a115 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/LICENSE @@ -0,0 +1,363 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + diff --git a/ptrans_deporder/_checkouts/exometer_core/README.md b/ptrans_deporder/_checkouts/exometer_core/README.md new file mode 100644 index 0000000..37566a3 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/README.md @@ -0,0 +1,752 @@ + + +# Exometer Core - Erlang instrumentation package, core services # + +Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. + +__Version:__ Apr 17 2015 14:08:02 + +__Authors:__ Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)), Magnus Feuer ([`magnus.feuer@feuerlabs.com`](mailto:magnus.feuer@feuerlabs.com)). + +[![Build Status](https://travis-ci.org/Feuerlabs/exometer_core.png?branch=master)](https://travis-ci.org/Feuerlabs/exometer_core) + +The Exometer Core package allows for easy and efficient instrumentation of +Erlang code, allowing crucial data on system performance to be +exported to a wide variety of monitoring systems. + +Exometer Core comes with a set of pre-defined monitor components, and can +be expanded with custom components to handle new types of Metrics, as +well as integration with additional external systems such as +databases, load balancers, etc. + +This document gives a high level overview of the Exometer system. For +details, please see the documentation for individual modules, starting +with `exometer`. + +Note the section on [Dependency Management](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Dependency_Management) for how to deal with +optional packages, both users and developers. + + +### Table of Content ### + + +1. [Concept and definitions](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Concept_and_definitions) + 1. [Metric](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Metric) + 2. [Data Point](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Data_Point) + 3. [Metric Type](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Metric_Type) + 4. [Entry Callback](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Entry_Callback) + 5. [Probe](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Probe) + 6. [Caching](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Caching) + 7. [Subscriptions and Reporters](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Subscriptions_and_Reporters) +2. [Built-in entries and probes](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Built-in_entries_and_probes) + 1. [counter (exometer native)](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#counter_(exometer_native)) + 2. [fast_counter (exometer native)](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#fast_counter_(exometer_native)) + 3. [gauge (exometer native)](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#gauge_(exometer_native)) + 4. [exometer_histogram (probe)](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#exometer_histogram_(probe)) + 5. [exometer_uniform (probe)](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#exometer_uniform_(probe)) + 6. [exometer_spiral (probe)](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#exometer_spiral_(probe)) + 7. [exometer_folsom [entry]](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#exometer_folsom_[entry]) + 8. [exometer_function [entry]](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#exometer_function_[entry]) +3. [Built in Reporters](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Built_in_Reporters) + 1. [exometer_report_tty](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#exometer_report_tty) +4. [Instrumenting Erlang code](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Instrumenting_Erlang_code) + 1. [Exometer Core Start](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Exometer_Core_Start) + 2. [Creating metrics](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Creating_metrics) + 3. [Deleting metrics](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Deleting_metrics) + 4. [Setting metric values](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Setting_metric_values) + 5. [Retrieving metric values](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Retrieving_metric_values) + 6. [Setting up subscriptions](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Setting_up_subscriptions) + 7. [Set metric options](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Set_metric_options) +5. [Configuring Exometer Core](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_Exometer_Core) + 1. [Configuring type - entry maps](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_type_-_entry_maps) + 2. [Configuring statically defined entries](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_statically_defined_entries) + 3. [Configuring static subscriptions](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_static_subscriptions) + 4. [Configuring reporter plugins](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_reporter_plugins) +6. [Creating custom exometer entries](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Creating_custom_exometer_entries) +7. [Creating custom probes](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Creating_custom_probes) +8. [Creating custom reporter plugins](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Creating_custom_reporter_plugins) +9. [Dependency management](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Dependency_management) + + +### Concepts and Definitions ### + +Exometer Core introduces a number of concepts and definitions used +throughout the documentation and the code. + +![Overview](/doc/exometer_overview.png?raw=true) + + +#### Metric #### + +A metric is a specific measurement sampled inside an Erlang system and +then reported to the Exometer Core system. An example metric would be +"transactions_per_second", or "memory_usage". + +Metrics are identified by a list of terms, such as given below: + +`[ xml_front_end, parser, file_size ]` + +A metric is created through a call by the code to be instrumented to +`exometer:new()`. Once created, the metric can be updated through +`exometer:update()`, or on its own initiative through the +`exometer_probe:sample` behavior implementation. + + +#### Data Point #### + +Each metric can consist of multiple data points, where each point has +a specific value. + +A typical example of data points would be a +`transactions_per_second` (tps) metric, usually stored as a +histogram covering the last couple of minutes of tps samples. Such a +histogram would host multiple values, such as `min`, `max`, +`median`, `mean`, `50_percentile`, `75_percentile`, +etc. + +It is up to the type of the metric, and the data probe backing that +type (see below), to specify which data points are available under the +given metric. + + +#### Metric Type #### + +The type of a metric, specified when the metric is created through +`exometer:new()`, determines which `exometer_entry` +callback to use. + +The link between the type and the entry to use is configured +through the `exometer_admin` module, and its associated exometer +defaults configuration data. + +The metric type, in other words, is only used to map a metric to a +configurable `exometer_entry` callback. + + +#### Entry Callback #### + +An exometer entry callback will receive values reported to a metric through the +`exometer:update()` call and compile it into one or more data points. +The entry callback can either be a counter (implemented natively +in `exometer`), or a more complex statistical analysis such +as a uniform distribution or a regular histogram. + +The various outputs from these entries are reported as data points +under the given metric. + +An entry can also interface external analytics packages. +`exometer_folsom`, for example, integrates with the +`folsom_metrics` package found at [`https://github.com/boundary/folsom`](https://github.com/boundary/folsom). + + +#### Probe #### + +Probes are a further specialization of exometer entries that run in +their own Erlang processes and have their own state (like a +gen_server). A probe is implemented through the `exometer_probe` +behavior. + +A probe can be used if independent monitoring is needed of, +for example, `/proc` trees, network interfaces, and other subsystems +that need periodic sampling. In these cases, the +`exometer_probe:probe_sample()` call is invoked regularly by exometer, +in the probe's own process, in order to extract data from +the given subsystem and add it to the metric's data points. + + +#### Caching #### + +Metric and data point values are read with the `exometer:get_value()` +function. In the case of counters, this operation is very fast. With probes, +the call results in a synchronous dialog with the probe process, and the +cost of serving the request depends on the probe implementation and the +nature of the metric being served. + +If the cost of reading the value is so high that calling the function often +would result in prohibitive load, it is possible to cache the value. This is +done either explicitly from the probe itself (by calling +`exometer_cache:write()`), or by specifying the option `{cache, Lifetime}` +for the entry. If an entry has a non-zero cache lifetime specified, the +`get_value()` call will try fetching the cached value before calling the +actual entry and automatically caching the result. + +Note that if `{cache, Lifetime}` is not specified, `exometer:get_value()` +will neither read nor write to the cache. It is possible for the probe +to periodically cache a value regardless of how the cache lifetime is set, +and the probe may also explicitly read from the cache if it isn't done +automatically. + + +#### Subscriptions and Reporters #### + +The subscription concept, managed by `exometer_report` allows metrics +and their data points to be sampled at given intervals and delivered +to one or more recipients, which can be either an arbitrary process +or a Reporter plugin. + +Each subscription ties a specific metric-datapoint pair to a reporter +and an interval (given in milliseconds). The reporter system will, at +the given interval, send the current value of the data point to the +subscribing reporter. The subscription, with all its parameters, +is setup through a call to `exometer_report:subscribe()`. + +In the case of processes, subscribed-to values will be delivered as a +message. Modules, which implement the `exometer_report` callback +behavior, will receive the plugins as a callbacks within the +`exometer_report` process. + +Subscriptions can either be setup at runtime, through +`exometer_report:subscribe()` calls, or statically through the +`exometer_report` configuration data. + + +### Built-in entries and probes ### + + +There are a number of built-in entries and probes shipped +with the Exometer Core package, as described below: + + +#### counter (exometer native) #### + + +The counter is implemented directly in `exometer` to provide simple +counters. A call to `exometer:update()` will add the provided value +to the counter. + +The counter can be reset to zero through `exometer:reset()`. + +The available data points under a metric using the counter entry +are `value` and `ms_since_reset`. + + +#### fast_counter (exometer native) #### + +A fast counter implements the counter functionality, through the +`trace_info` system, yielding a speed increase of about 3.5 in +comparison to the regular counter. + +The tradeoff is that running tracing and/or debugging may interfere +with the counter functionality. + +A call to `exometer:update()` will add the provided value to the +counter. + +The counter can be reset to zero through `exometer:reset()`. + +The available data points under a metric using the fast_counter +entry are `value` and `ms_since_reset`. + + +#### gauge (exometer native) #### + +The gauge is implemented directly in `exometer` to provide simple +gauges. A call to `exometer:update()` will set the gauge's value +to the provided value. That is, the value of the gauge entry is +always the most recently provided value. + +The gauge can be reset to zero through `exometer:reset()`. + +The available data points under a metric using the gauge entry +are `value` and `ms_since_reset`. + + +#### exometer_histogram (probe) #### + +The histogram probe stores a given number of updates, provided through +`exometer:update()`, in a histogram. The histogram maintains a log +derived from all values received during a configurable time span and +provides min, max, median, mean, and percentile analysis data points +for the stored data. + +In order to save memory, the histogram is divided into equal-sized +time slots, where each slot spans a settable interval. All values +received during a time slot will be averaged into a single value to be +stored in the histogram once the time slot expires. The averaging +function (which can be replaced by the caller), allows for +high-frequency update metrics to have their resolution traded against +resource consumption. + + +#### exometer_uniform (probe) #### + +The uniform probe provides a uniform sample over a pool of values +provided through `exometer:update()`. When the pool reaches its configurable +max size, existing values will be replaced at random to make space for +new values. Much like `exometer_histogram`, the uniform probe +provides min, max, median, mean, and percentile analysis data points +for the stored data. + + +#### exometer_spiral (probe) #### + +The spiral probe maintains the total sum of all values stored in its +histogram. The histogram has a configurable time span, all values +provided to the probe, through `exometer:update()`, within that time +span will be summed up and reported. If, for example, the histogram +covers 60 seconds, the spiral probe will report the sum of all +values reported during the last minute. + +The grand total of all values received during the lifetime of the +probe is also available. + + +#### exometer_folsom [entry] #### + +The folsom entry integrates with the folsom metrics package provided +by the boundary repo at github. Updated values sent to the folsom entry +can be forwarded to folsom's counter, histogram, duration, meter, +and spiral. + +Folsom integration is provided as a backup. New code using Exometer Core +should use the native probes that duplicate folsom. + + +#### exometer_function [entry] #### + +The function entry allows for a simple caller-supplied function to be +invoked in order to retrieve non-exometer data. The +`exometer_function:get_value()` function will invoke a +`Module:Function(DataPoints)` call, where `Module` and +`Function` are provided by the caller. + +The function entry provides an easy way of integrating an external +system without having to write a complete entry. + + +### Built in Reporters ### + +Exometer Core ships with some built-in reporters which can be used to forward +updated metrics and their data points to external systems. They can also +serve as templates for custom-developed reporters. + + +#### exometer_report_tty #### + +The `exometer_report_tty` reporter is mainly intended for experimentation. +It outputs reports directly to the tty. + + +### Instrumenting Erlang code ### + +The code using Exometer Core needs to be instrumented in order to setup and +use metrics reporting. + + +#### Exometer Core Start #### + +The system using Exometer Core must start the `exometer` application +prior to using it: + +```erlang + +application:start(lager), +application:start(exometer_core). +``` + +Note that dependent applications need to be started first. On newer OTP versions +(R61B or later), you can use `application:ensure_all_started(exometer)`. + +For testing, you can also use [`exometer:start/0`](https://github.com/Feuerlabs/exometer_core/blob/master/doc/exometer.md#start-0). + +If you make use of e.g. folsom metrics, you also need to start `folsom`. +Exometer Core will not do that automatically, nor does it contain an +application dependency for it. + +See [Configuring Exometer Core](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_Exometer_Core) for details on configuration data +format. + + +#### Creating metrics #### + +A metric, can be created throuh a call to + +```erlang + +exometer:new(Name, Type) +``` + +`Name` is a list of atoms, uniquely identifying the metric created. +The type of the metric, specified by `Type` will be mapped +to an exometer entry through the table maintained by +`exometer_admin` Please see the [Configuring type - entry +maps](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_type_-_entry_maps) for details. + +The resolved entry to use will determine the data points available +under the given metric. + + +#### Deleting metrics #### + +A metric previously created with `exometer:new()` can be deleted by +`exometer:delete()`. + +All subscriptions to the deleted metrics will be cancelled. + + +#### Setting metric values #### + +A created metric can have its value updated through the +`exometer:update()` function: + +```erlang + +exometer:update(Name, Value) +``` + +The `Name` parameter is the same atom list provided to a previous +`exometer:new()` call. The `Value` is an arbitrarty element that is +forwarded to the `exometer:update()` function of the entry/probe that the +metric is mapped to. + +The receiving entry/probe will process the provided value and modify +its data points accordingly. + + +#### Retrieving metric values #### + +Exometer-using code can at any time retrieve the data point values +associated with a previously created metric. In order to find out which +data points are available for a metric, the following call can be used: + +```erlang + +exometer:info(Name, datapoints) +``` + +The `Name` parameter is the same atom list provided to a previous +`exometer:new()` call. The call will return a list of data point +atoms that can then be provided to `exometer:get_value()` to +retrieve their actual value: + +```erlang + +exometer:get_value(Name, DataPoint) +``` + +The `Name` paramer identifies the metric, and `DataPoints` +identifies the data points (returned from the previous `info()` call) +to retrieve the value for. + +If no DataPoints are provided, the values of a default list of data points, +determined by the backing entry / probe, will be returned. + + +#### Setting up subscriptions #### + +A subscription can either be statically configured, or dynamically +setup from within the code using Exometer Core. For details on statically +configured subscriptions, please see [Configuring static subscriptions](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_static_subscriptions). + +A dynamic subscription can be setup with the following call: + +```erlang + +exometer_report:subscribe(Recipient, Metric, DataPoint, Inteval) +``` + +`Recipient` is the name of a reporter. + + +#### Set metric options #### + + +Each created metric can have options setup for it through the following call: + +```erlang + +exometer:setopts(Name, Options) +``` + +The `Name` paramer identifies the metric to set the options for, and +Options is a proplist (`[{ Key, Value },...]`) with the options to be +set. + +Exometer Core looks up the the backing entry that hosts the metric with +the given Name, and will invoke the entry\'s `setopts/4` function to set +the actual options. Please see the `setopts/4` function for the various +entries for details. + + +### Configuring Exometer Core ### + +Exometer Core defaults can be changed either through OTP application environment +variables or through the use of Basho's `cuttlefish` +([`https://github.com/basho/cuttlefish`](https://github.com/basho/cuttlefish)). + +__Note:__ Exometer Core will check both the `exometer` and the `exometer_core` +application environments. The `exometer` environment overrides the +`exometer_core` environment. However, if only `exometer_core` is used, any +`exometer` environment will simply be ignored. This is because of the +application controller: environment data is not loaded until the application +in question is loaded. + + +#### Configuring type - entry maps #### + +The dynamic method of configuring defaults for `exometer` entries is: + +```erlang + +exometer_admin:set_default(NamePattern, Type, Default) +``` + +Where `NamePattern` is a list of terms describing what is essentially +a name prefix with optional wildcards (`'_'`). A pattern that +matches any legal name is `['_']`. + +`Type` is an atom defining a type of metric. The types already known to +`exometer`, `counter`, `fast_counter`, `ticker`, `uniform`, `histogram`, +`spiral`, `netlink`, and `probe` may be redefined, but other types can be +described as well. + +`Default` is either an `#exometer_entry{}` record (unlikely), or a list of +`{Key, Value}` options, where the keys correspond to `#exometer_entry` record +attribute names. The following attributes make sense to preset: + +```erlang + +{module, atom()} % the callback module +{status, enabled | disabled} % operational status of the entry +{cache, non_neg_integer()} % cache lifetime (ms) +{options, [{atom(), any()}]} % entry-specific options +``` + +Below is an example, from `exometer_core/priv/app.config`: + +```erlang + +{exometer, [ + {defaults, [ + {['_'], function , [{module, exometer_function}]}, + {['_'], counter , [{module, exometer}]}, + {['_'], histogram, [{module, exometer_histogram}]}, + {['_'], spiral , [{module, exometer_spiral}]}, + {['_'], duration , [{module, exometer_folsom}]}, + {['_'], meter , [{module, exometer_folsom}]}, + {['_'], gauge , [{module, exometer_folsom}]} + ]} +]} +``` + +In systems that use CuttleFish, the file `exometer/priv/exometer.schema` +contains a schema for default settings. The setup corresponding to the above +defaults would be as follows: + +```ini + +exometer.template.function.module = exometer_function +exometer.template.counter.module = exometer +exometer.template.histogram.module = exometer_histogram +exometer.template.spiral.module = exometer_spiral +exometer.template.duration.module = exometer_folsom +exometer.template.meter.module = exometer_folsom +exometer.template.gauge.module = exometer_folsom +``` + + +#### Configuring statically defined entries #### + +Using the `exometer` environment variable `predefined`, entries can be added +at application startup. The variable should have one of the following values: + +* `{script, File}` - `File` will be processed using `file:script/2`. The return + value (the result of the last expression in the script) should be a list of`{Name, Type, Options}` tuples. + +* `{apply, M, F, A}` - The result of `apply(M, F, A)` should be `{ok, L}` where`L` is a list of `{Name, Type, Options}` tuples. + +* `L`, where L is a list of `{Name, Type, Options}` tuples or extended +instructions (see below). + +The list of instructions may include: + +* `{delete, Name}` - deletes `Name` from the exometer registry. + +* `{select_delete, Pattern}` - applies a select pattern and +deletes all matching entries. + +* `{re_register, {Name, Type, Options}}` - redefines an entry if present, +otherwise creates it. + +Exometer Core will also scan all loaded applications for the environment +variables `exometer_defaults` and `exometer_predefined`, and process +as above. If an application is loaded and started after exometer has started, +it may call the function `exometer:register_application()` or +`exometer:register_application(App)`. This function will do nothing if +exometer isn't already running, and otherwise process the `exometer_defaults` +and `exometer_predefined` variables as above. The function can also be +called during upgrade, as it will re-apply the settings each time. + + +#### Configuring static subscriptions #### + + +Static subscriptions, which are automatically setup at exometer +startup without having to invoke `exometer_report:subscribe()`, are +configured through the report sub section under exometer. + +Below is an example, from `exometer/priv/app.config`: + +```erlang + +{exometer, [ + {report, [ + {subscribers, [ + {exometer_report_collectd, [db, cache, hits], mean, 2000, true}, + {exometer_report_collectd, [db, cache, hits], max, 5000, false} + ]} + ]} +]} +``` + +The `report` section configures static subscriptions and reporter +plugins. See [Configuring reporter plugins](https://github.com/Feuerlabs/exometer_core/blob/master/doc/README.md#Configuring_reporter_plugins) for details on +how to configure individual plugins. + +The `subscribers` sub-section contains all static subscriptions to be +setup att exometer applications start. Each tuple in the prop list +should be of one of the following formats: + +* `{Reporter, Metric, DataPoint, Interval}` + +* `{Reporter, Metric, DataPoint, Interval, RetryFailedMetrics}` + +* `{Reporter, Metric, DataPoint, Interval, RetryFailedMetrics, Extra}` + +* `{apply, {M, F, A}}` + +* `{select, {MatchPattern, DataPoint, Interval [, Retry [, Extra] ]}}` + +In the case of `{apply, M, F, A}`, the result of `apply(M, F, A)` must +be a list of `subscribers` tuples. + +In the case of `{select, Expr}`, a list of metrics is fetched using +`exometer:select(MatchPattern)`, where the result must be on the form +`{Key, Type, Status}` (i.e. what corresponds to `'$_'`). +The rest of the items will be applied to each of the matching entries. + +The meaning of the above tuple elements is: + ++ `Reporter :: module()`
Specifies the reporter plugin module, such as`exometer_report_collectd` that is to receive updated metric's data +points. + ++ `Metric :: [atoms()]`
Specifies the path to a metric previously created with an`exometer:new()` call. + ++ `DataPoint` :: atom() | [atom()]'
Specifies the data point within the given metric to send to the + receiver. The data point must match one of the data points returned by`exometer:info(Name, datapoints)` for the given metrics name. + ++ `Interval` :: integer()' (milliseconds)
Specifies the interval, in milliseconds, between each update of the +given metric's data point. At the given interval, the data point will +be samples, and the result will be sent to the receiver. + ++ `RetryFailedMetrics :: boolean()`
Specifies if the metric should be continued to be reported + even if it is not found during a reporting cycle. This would be + the case if a metric is not created by the time it is reported for + the first time. If the metric will be created at a later time, + this value should be set to true. Set this value to false if all + attempts to report the metric should stop if when is not found. + The default value is `true`. + ++ `Extra :: any()`
Provides a means to pass along extra information for a given + subscription. An example is the `syntax` option for the SNMP reporter, + in which case `Extra` needs to be a property list. + +Example configuration in sys.config, using the `{select, Expr}` pattern: + +```erlang + +[ + {exometer, [ + {predefined, + [{[a,1], counter, []}, + {[a,2], counter, []}, + {[b,1], counter, []}, + {[c,1], counter, []}]}, + {report, + [ + {reporters, + [{exometer_report_tty, []}]}, + {subscribers, + [{select, {[{ {[a,'_'],'_','_'}, [], ['$_']}], + exometer_report_tty, value, 1000}}]} + ]} + ]} +]. + +``` + +This will activate a subscription on `[a,1]` and `[a,2]` in the +`exometer_report_tty` reporter, firing once per second. + + +#### Configuring reporter plugins #### + + +The various reporter plugins to be loaded by exometer are configured +in the `report` section under `reporters` + +Each reporter has an entry named after its module, and the content of +that entry is dependent on the reporter itself. The following chapters +specifies the configuration parameters for the reporters shipped with +exometer. + + +### Creating custom exometer entries ### + + +Please see @see exometer_entry documentation for details. + + +### Creating custom probes ### + + +Please see @see exometer_probe documentation for details. + + +### Creating custom reporter plugins ### + + +Please see @see exometer_report documentation for details. + + +#### Customizing rebar.config #### + +The OS environment variables `EXOMETER_CORE_CONFIG_PREPROCESS` and +`EXOMETER_CORE_CONFIG_POSTPROCESS` can be used to insert a script, similar to +`rebar.config.script` in the processing flow of the exometer build. +As the names imply, the script given by `EXOMETER_CONFIG_CONFIG_PREPROCESS` +(if any) will be run before exometer does any processing of its own, and the +`EXOMETER_CORE_CONFIG_POSTPROCESS` script (if any) will be run after all other +processing is complete. + +## Modules ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
exo_montest
exometer
exometer_admin
exometer_alias
exometer_cache
exometer_cpu
exometer_duration
exometer_entry
exometer_folsom
exometer_folsom_monitor
exometer_function
exometer_histogram
exometer_igor
exometer_info
exometer_probe
exometer_proc
exometer_report
exometer_report_lager
exometer_report_logger
exometer_report_tty
exometer_shallowtree
exometer_slide
exometer_slot_slide
exometer_spiral
exometer_uniform
exometer_util
+ diff --git a/ptrans_deporder/_checkouts/exometer_core/ebin/exometer_core.app b/ptrans_deporder/_checkouts/exometer_core/ebin/exometer_core.app new file mode 100644 index 0000000..e8ff2e2 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/ebin/exometer_core.app @@ -0,0 +1,23 @@ +{application,exometer_core, + [{registered,[]}, + {description,"Code instrumentation and metrics collection package."}, + {vsn,"1.5.2"}, + {applications,[kernel,stdlib,hut,setup,folsom]}, + {mod,{exometer_core_app,[]}}, + {start_phases,[{start_reporters,[]},{preset_defaults,[]}]}, + {env,[]}, + {maintainers,["Feuerlabs","Heinz N. Gies"]}, + {licenses,["MPL"]}, + {links,[{"Github", + "https://github.com/Feuerlabs/exometer_core"}]}, + {modules,[exo_montest,exometer,exometer_admin,exometer_alias, + exometer_cache,exometer_core_app,exometer_core_sup, + exometer_cpu,exometer_duration,exometer_entry, + exometer_folsom,exometer_folsom_monitor, + exometer_function,exometer_global,exometer_histogram, + exometer_igor,exometer_info,exometer_probe, + exometer_proc,exometer_report,exometer_report_logger, + exometer_report_logger_sup,exometer_report_tty, + exometer_shallowtree,exometer_slide, + exometer_slot_slide,exometer_spiral,exometer_uniform, + exometer_util]}]}. diff --git a/ptrans_deporder/_checkouts/exometer_core/hex_metadata.config b/ptrans_deporder/_checkouts/exometer_core/hex_metadata.config new file mode 100644 index 0000000..f0159cb --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/hex_metadata.config @@ -0,0 +1,45 @@ +{<<"name">>,<<"exometer_core">>}. +{<<"version">>,<<"1.5.2">>}. +{<<"requirements">>, + #{<<"folsom">> => + #{<<"app">> => <<"folsom">>,<<"optional">> => false, + <<"requirement">> => <<"~>0.8.5">>}, + <<"hut">> => + #{<<"app">> => <<"hut">>,<<"optional">> => false, + <<"requirement">> => <<"~>1.2.0">>}, + <<"lager">> => + #{<<"app">> => <<"lager">>,<<"optional">> => false, + <<"requirement">> => <<"~>3.5.0">>}, + <<"parse_trans">> => + #{<<"app">> => <<"parse_trans">>,<<"optional">> => false, + <<"requirement">> => <<"~>3.0.0">>}, + <<"setup">> => + #{<<"app">> => <<"setup">>,<<"optional">> => false, + <<"requirement">> => <<"~>1.8.2">>}}}. +{<<"app">>,<<"exometer_core">>}. +{<<"maintainers">>,[<<"Feuerlabs">>,<<"Heinz N. Gies">>]}. +{<<"precompiled">>,false}. +{<<"description">>,<<"Code instrumentation and metrics collection package.">>}. +{<<"files">>, + [<<"src/exometer_core.app.src">>,<<"LICENSE">>,<<"README.md">>, + <<"include/exometer.hrl">>,<<"priv/app.config">>, + <<"priv/remove_deps.script">>,<<"rebar.config">>,<<"rebar.config.script">>, + <<"rebar.lock">>,<<"src/exo_montest.erl">>,<<"src/exometer.erl">>, + <<"src/exometer_admin.erl">>,<<"src/exometer_alias.erl">>, + <<"src/exometer_cache.erl">>,<<"src/exometer_core_app.erl">>, + <<"src/exometer_core_sup.erl">>,<<"src/exometer_cpu.erl">>, + <<"src/exometer_duration.erl">>,<<"src/exometer_entry.erl">>, + <<"src/exometer_folsom.erl">>,<<"src/exometer_folsom_monitor.erl">>, + <<"src/exometer_function.erl">>,<<"src/exometer_global.erl">>, + <<"src/exometer_histogram.erl">>,<<"src/exometer_igor.erl">>, + <<"src/exometer_info.erl">>,<<"src/exometer_probe.erl">>, + <<"src/exometer_proc.erl">>,<<"src/exometer_report.erl">>, + <<"src/exometer_report_logger.erl">>, + <<"src/exometer_report_logger_sup.erl">>,<<"src/exometer_report_tty.erl">>, + <<"src/exometer_shallowtree.erl">>,<<"src/exometer_slide.erl">>, + <<"src/exometer_slot_slide.erl">>,<<"src/exometer_spiral.erl">>, + <<"src/exometer_uniform.erl">>,<<"src/exometer_util.erl">>]}. +{<<"licenses">>,[<<"MPL">>]}. +{<<"links">>, + [{<<"Github">>,<<"https://github.com/Feuerlabs/exometer_core">>}]}. +{<<"build_tools">>,[<<"rebar3">>]}. diff --git a/ptrans_deporder/_checkouts/exometer_core/include/exometer.hrl b/ptrans_deporder/_checkouts/exometer_core/include/exometer.hrl new file mode 100644 index 0000000..eac8541 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/include/exometer.hrl @@ -0,0 +1,38 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-define(EXOMETER_SHARED, exometer_shared). +-define(EXOMETER_ENTRIES, exometer_entries). +-define(EXOMETER_SUBS, exometer_subscriptions). +-define(EXOMETER_REPORTERS, exometer_reporters). + +-record(exometer_event, { + time = exometer_util:timestamp(), + from, + event + }). + +-record(exometer_entry, { + name, + type, + behaviour = undefined, + module = exometer, + status = 1, % enabled, no event flags + cache = 0, + value, + timestamp, + options = [], + ref + }). + +%% Used to redirect lookup from the scheduler-specific tables to the shared +-record(exometer_shared, { + name + }). diff --git a/ptrans_deporder/_checkouts/exometer_core/priv/app.config b/ptrans_deporder/_checkouts/exometer_core/priv/app.config new file mode 100644 index 0000000..8c29ffa --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/priv/app.config @@ -0,0 +1,43 @@ +%% -*- erlang -*- +[ + {exometer, + [ + {defaults, + [ + {['_'], function , [{module, exometer_function}]}, + {['_'], counter , [{module, exometer}]}, + {['_'], histogram, [{module, exometer_histogram}]}, + {['_'], spiral , [{module, exometer_spiral}]}, + {['_'], duration , [{module, exometer_folsom}]}, + {['_'], meter , [{module, exometer_folsom}]}, + {['_'], gauge , [{module, exometer_folsom}]} + ]}, + {report, + [ + {subscribers, + [ + {exometer_report_tty, [a,b,2], value, 2000, true} + %%{exometer_report_collectd, [a,b,2], value, 2000, true} + ]}, + {reporters, + [ + {exometer_report_tty, []}, + {exometer_report_collectd, + [ + %% Set the refresh interval to collectds Interval value. + {reconnect_interval, 2}, + {refresh_interval, 20}, + {read_timeout, 5000}, + {hostname, "testhost"}, + {path, "/var/run/collectd-unixsock"}, + {plugin_name, "testname"}, + {type_map, + [ + {[a,b,c, max], "gauge"}, + {[a,b,d, value], "gauge"} + ]} + ]} + ]} + ]} + ]} +]. diff --git a/ptrans_deporder/_checkouts/exometer_core/priv/remove_deps.script b/ptrans_deporder/_checkouts/exometer_core/priv/remove_deps.script new file mode 100644 index 0000000..7e62122 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/priv/remove_deps.script @@ -0,0 +1,18 @@ +%% -*- erlang -*- +%% +%% Assumes the following bound variables: +%% CONFIG - a rebar.config options list +%% DEPS :: [atom()] - a list of deps to remove +case lists:keyfind(deps, 1, CONFIG) of + {_, Deps0} -> + Deps1 = lists:filter( + fun(D) when is_atom(D) -> + not lists:member(D, DEPS); + (D) when is_tuple(D) -> + not lists:member(element(1,D), DEPS) + end, Deps0), + lists:keyreplace(deps, 1, CONFIG, {deps, Deps1}); + false -> + CONFIG +end. + diff --git a/ptrans_deporder/_checkouts/exometer_core/rebar.config b/ptrans_deporder/_checkouts/exometer_core/rebar.config new file mode 100644 index 0000000..141c7d6 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/rebar.config @@ -0,0 +1,60 @@ +%% -*- erlang -*- +{erl_first_files, + [ + "src/exometer_igor.erl", + "src/exometer_util.erl", + "src/exometer_entry.erl", + "src/exometer_probe.erl" + ]}. + +{deps, + [ + {lager, "~>3.5.0"}, + {parse_trans, "~>3.0.0"}, + {folsom, "~>0.8.5"}, + {setup, "~>1.8.2"}, + {hut, "~>1.2.0"} + ]}. + +{profiles, + [ + {test, [{deps, [{meck, {git, "git://github.com/eproxus/meck.git", {tag,"0.8.4"}}}]}]}, + {docs, [{deps, [edown]}]} + ]}. + +{profiles, + [{docs, [{deps, [edown]}]}]}. + +{erl_opts, + [ + debug_info, + fail_on_warning, + {platform_define, "^((1[8|9])|2)", rand_module}, + {verbosity, trace} + ]}. + +{sub_dirs, ["src"]}. + +{edoc_opts, + [ + %{doclet, edown_doclet}, + {app_default, "http://www.erlang.org/doc/man"}, + {doc_path, []}, + {top_level_readme, + {"./README.md", + "https://github.com/Feuerlabs/exometer_core", "master"}} + ]}. + +{xref_checks, + [ + undefined_function_calls, + undefined_functions, + locals_not_used, + deprecated_functions_calls, + deprecated_functions + ]}. + +{cover_enabled, true}. +{cover_print_enabled, true}. + +{clean_files, ["test/app1/ebin/*.beam"]}. diff --git a/ptrans_deporder/_checkouts/exometer_core/rebar.config.script b/ptrans_deporder/_checkouts/exometer_core/rebar.config.script new file mode 100644 index 0000000..6892cdf --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/rebar.config.script @@ -0,0 +1,46 @@ +%% -*- erlang -*- +%%---- BEGIN COPYRIGHT ------------------------------------------------------- +%% +%% Copyright (C) 2014 Feuerlabs Inc. All rights reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%%---- END COPYRIGHT --------------------------------------------------------- + +Script = fun(D,S,Vs) -> + Scr = filename:join(D, S), + case file:script(Scr, orddict:store('SCRIPT', Scr, Vs)) of + {ok, Res} -> + Res; + {error,_} = Err -> + io:fwrite("Error evaluating script ~s~n", [S]), + Err + end + end. +CONFIG0 = case os:getenv("EXOMETER_CORE_CONFIG_PREPROCESS") of + false -> CONFIG; + [] -> CONFIG; + PPScr -> Script(filename:dirname(PPScr), + filename:basename(PPScr), + [{'CONFIG', CONFIG}]) + end. +CFG1 = case os:getenv("REBAR_DEPS") of + false -> + CONFIG0; + [] -> + CONFIG0; + Dir -> + lists:keystore(deps_dir, 1, CONFIG0, {deps_dir, Dir}) + end. + +Priv = filename:join(filename:dirname(SCRIPT), "priv"). + +case os:getenv("EXOMETER_CORE_CONFIG_POSTPROCESS") of + false -> CFG1; + [] -> CFG1; + PoPScr -> Script(filename:dirname(PoPScr), + filename:basename(PoPScr), + [{'CONFIG', CFG1}]) +end. diff --git a/ptrans_deporder/_checkouts/exometer_core/rebar.lock b/ptrans_deporder/_checkouts/exometer_core/rebar.lock new file mode 100644 index 0000000..f5290f4 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/rebar.lock @@ -0,0 +1,18 @@ +{"1.1.0", +[{<<"bear">>,{pkg,<<"bear">>,<<"0.8.5">>},1}, + {<<"folsom">>,{pkg,<<"folsom">>,<<"0.8.5">>},0}, + {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1}, + {<<"hut">>,{pkg,<<"hut">>,<<"1.2.0">>},0}, + {<<"lager">>,{pkg,<<"lager">>,<<"3.5.1">>},0}, + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.0.0">>},0}, + {<<"setup">>,{pkg,<<"setup">>,<<"1.8.2">>},0}]}. +[ +{pkg_hash,[ + {<<"bear">>, <<"E95FCA1627CD9E15BAF93CE0A52AFF16917BAF325F0EE65B88CD715376CD2344">>}, + {<<"folsom">>, <<"94A027B56FE84FEED264F9B33CB4C6AC9A801FAD84B87DBDA0836CE83C3B8D69">>}, + {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, + {<<"hut">>, <<"0089DF0FAA2827C605BBADA88153F24FFF5EA7A4BE32ECF0250A7FDC2719CAFB">>}, + {<<"lager">>, <<"63897A61AF646C59BB928FEE9756CE8BDD02D5A1A2F3551D4A5E38386C2CC071">>}, + {<<"parse_trans">>, <<"9E96B1C9C3A0DF54E7B76F8F685D38BFA1EB21B31E042B1D1A5A70258E4DB1E3">>}, + {<<"setup">>, <<"14E66C480EA51D938527247C1D92C3EA5298826CF7FA7769C936BAD29E2C040E">>}]} +]. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exo_montest.erl b/ptrans_deporder/_checkouts/exometer_core/src/exo_montest.erl new file mode 100644 index 0000000..0641c26 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exo_montest.erl @@ -0,0 +1,82 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% +%% @doc Demo module for `exometer_folsom_monitor' behaviours. +%% +%% This module simply +%% @end +-module(exo_montest). +-behaviour(exometer_folsom_monitor). +-behaviour(exometer_entry). + +-export([copy_folsom/3]). +-export([behaviour/0, + delete/3, + get_datapoints/3, + get_value/4, + new/3, + reset/3, + sample/3, + setopts/3, + update/4]). + +behaviour() -> + entry. + +copy_folsom(Name, Type, Opts) when is_tuple(Name) -> + {tuple_to_list(Name), ad_hoc, [{folsom_name, Name}, + {module, ?MODULE}, + {type, Type} + | options(Type, Opts)]}; +copy_folsom(_, _, _) -> + false. + +new(N, _, Opts) -> + {ok, {proplists:get_value(type, Opts, unknown), + proplists:get_value(folsom_name, Opts, N)}}. + +update(_, Value, counter, {_, Name}) -> + folsom_metrics:notify_existing_metric(Name, {inc,Value}, counter); +update(_, Value, Type, {_, Name}) -> + folsom_metrics:notify_existing_metric(Name, Value, Type). + +reset(_, _, _) -> + {error, unsupported}. + +get_value(_, Type, {_, Name}, DPs) -> + exometer_folsom:get_value(Name, Type, [], DPs). + +sample(_, _, _) -> + {error, unsupported}. + +setopts(_, _, _) -> + ok. + +delete(_, _, _) -> + {error, unsupported}. + +get_datapoints(Name, Type, _) -> + exometer_folsom:get_datapoints(Name, Type, []). + +options(history, [Size]) -> + [{size, Size}]; +options(histogram, [SampleType, SampleSize, Alpha]) -> + [{sample_type, SampleType}, + {sample_size, SampleSize}, + {alpha, Alpha}]; +options(duration , [SampleType, SampleSize, Alpha]) -> + [{sample_type, SampleType}, + {sample_size, SampleSize}, + {alpha, Alpha}]; +options(meter_reader, []) -> []; +options(spiral , []) -> []; +options(meter , []) -> []; +options(gauge , []) -> []; +options(counter , []) -> []. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer.erl new file mode 100644 index 0000000..6bc15fe --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer.erl @@ -0,0 +1,1237 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + + +%% @doc API and behaviour for metrics instances +%% +%%

Predefined templates

+%% +%% It is possible to define a set of defaults for exometer. +%% +%% Example: Putting the following in a sys.config file, +%%
+%% {exometer, [
+%%          {defaults,
+%%           [{['_'], function    , [{module, exometer_function}]},
+%%            {['_'], counter     , [{module, exometer}]},
+%%            {['_'], fast_counter, [{module, exometer}]},
+%%            {['_'], gauge       , [{module, exometer}]},
+%%            {['_'], histogram   , [{module, exometer_histogram}]},
+%%            {['_'], spiral      , [{module, exometer_spiral}]},
+%%            {['_'], duration    , [{module, exometer_folsom}]},
+%%            {['_'], meter       , [{module, exometer_folsom}]},
+%%           ]}
+%%         ]}
+%% 
+%% will define global defaults for the given metric types. The format is +%% `{NamePattern, Type, Options}' +%% +%% The options can be overridden by options given in the `new()' command. +%% +%% `NamePattern' is similar to that used in {@link find_entries/1}. +%% For more information, see {@link exometer_admin:set_default/3}. +%% @end +-module(exometer). + +-export( + [ + new/2, new/3, + re_register/3, ensure/3, + repair/1, + propose/3, + update/2, update_or_create/2, update_or_create/4, + get_value/1, get_value/2, get_values/1, + sample/1, + delete/1, + reset/1, + setopts/2, + find_entries/1, + select/1, select/2, select_cont/1, select_count/1, + aggregate/2, + info/1, info/2, + register_application/0, + register_application/1 + ]). + +-export([global_status/1]). + +-export([create_entry/1]). % called only from exometer_admin.erl + +%% Convenience function for testing +-export([start/0, stop/0]). + +-export_type([name/0, type/0, options/0, status/0, behaviour/0, + entry/0, datapoint/0]). + +-compile(inline). + +-include("exometer.hrl"). + +-type name() :: list(). +-type type() :: atom() + | {function, M :: atom(), F :: atom()} + | {function, M :: atom(), F :: atom(), ArgSpec :: list(), + Type :: atom(), DataPoints :: list()} + | {Type :: atom(), Arg :: any()}. +-type status() :: enabled | disabled. +-type options() :: [{atom(), any()}]. +-type value() :: any(). +-type error() :: {error, any()}. +-type behaviour() :: probe | entry. +-type entry() :: #exometer_entry{}. +-type datapoint() :: atom() | integer(). + +-define(IS_ENABLED(St), St==enabled orelse St band 2#1 == 1). +-define(IS_DISABLED(St), St==disabled orelse St band 2#1 =/= 1). + +-define(EVENT_ENABLED(St), St band 2#10 == 2#10). + +%% @doc Start exometer and dependent apps (for testing). +start() -> + {ok,_} = exometer_util:ensure_all_started(exometer_core), + ok. + +%% @doc Stop exometer and dependent apps (for testing). +stop() -> + application:stop(exometer_core). + +-spec new(name(), type()) -> ok. +%% @equiv new(Name, Type, []) +new(Name, Type) -> + new(Name, Type, []). + +-spec new(name(), type(), options()) -> ok. +%% @doc Create a new metrics entry. +%% +%% `Name' must be a list of terms (e.g. atoms). `Type' must be either one +%% of the built-in types, or match a predefined template. +%% +%% `Options' will be passed to the entry, but the framework will recognize +%% the following options: +%% +%% * `{cache, Lifetime}' - Cache the results of {@link get_value/1} for +%% the given number of milliseconds. Subsequent calls to {@link get_value/1} +%% will get the cached value, if found. Default is `0', which means no +%% caching will be performed. +%% +%% * `{status, enabled | disabled}' - Default is `enabled'. If the metric +%% is `disabled', calls to {@link get_value/1} will return `{ok, disabled}', +%% and calls to {@link update/2} and {@link sample/1} will return `ok' but +%% will do nothing. +%% +%% * `{snmp, [{DataPoint, ReportInterval}]}' - defines a link to SNMP reporting, +%% where the given data points are sampled at the given intervals, converted +%% to SNMP PDUs and transmitted via the `exometer_report_snmp' reporter. +%% +%% * `{snmp_syntax, [{DataPoint | {default}, SYNTAX}]}' - specifies a custom +%% SNMP type for a given data point. `SYNTAX' needs to be a binary or a string, +%% and corresponds to the SYNTAX definition in the generated SNMP MIB. +%% +%% * `{aliases, [{DataPoint, Alias}]}' - maps aliases to datapoints. +%% See {@link exometer_alias:new/2}. +%% +%% * {'--', Keys} removes option keys from the applied template. +%% This can be used to clean up the options list when overriding the defaults +%% for a given namespace (if the default definition contains options that are +%% not applicable, or would even cause problems with the current entry.) +%% +%% For example, the default value for an exometer counter is `"Counter32"', which +%% expands to `SYNTAX Counter32' in the corresponding MIB object definition. If +%% a 64-bit counter (not supported by SNMPv1) is desired instead, the option +%% `{snmp_syntax, [{value, "Counter64"}]}' can be added to the counter entry +%% (note that `value' in this case is the name of the data point representing +%% the counter value). +%% +%% @end +new(Name, Type, Opts) when is_list(Name), is_list(Opts) -> + exometer_admin:new_entry(Name, Type, Opts). + +-spec propose(name(), type(), options()) -> exometer_info:pp() | error(). +%% @doc Propose a new exometer entry (no entry actually created). +%% +%% This function analyzes a proposed entry definition, applying templates +%% and processing options in the same way as {@link new/3}, but not actually +%% creating the entry. The return value, if successful, corresponds to +%% `exometer_info:pp(Entry)'. +%% @end +propose(Name, Type, Opts) when is_list(Name), is_list(Opts) -> + exometer_admin:propose(Name, Type, Opts). + +-spec re_register(name(), type(), options()) -> ok. +%% @doc Create a new metrics entry, overwrite any old entry. +%% +%% This function behaves as {@link new/3}, but will not fail if an entry +%% with the same name already exists. Instead, the old entry will be replaced +%% by the new. +%% @end +re_register(Name, Type, Opts) when is_list(Name), is_list(Opts) -> + exometer_admin:re_register_entry(Name, Type, Opts). + +-spec repair(name()) -> ok. +%% @doc Delete and re-create an entry. +%% +%% This function can be tried if a metric (e.g. a complex probe) has become +%% 'stuck' or otherwise isn't functioning properly. It fetches the stored +%% meta-data and then deletes and re-creates the metric. +%% @end +repair(Name) -> + exometer_admin:repair_entry(Name). + +-spec ensure(name(), type(), options()) -> ok | error(). +%% @doc Ensure that metric exists and is of given type. +%% +%% This function is similar to re-register, but doesn't actually re-register +%% a metric if it already exists. If a matching entry is found, a check is +%% performed to verify that it is of the correct type. If it isn't, an +%% error tuple is returned. +%% @end +ensure(Name, Type, Opts) when is_list(Name), is_list(Opts) -> + exometer_admin:ensure(Name, Type, Opts). + + +-spec update(name(), value()) -> ok | error(). +%% @doc Update the given metric with `Value'. +%% +%% The exact semantics of an update will vary depending on metric type. +%% For exometer's built-in counters, the counter instance on the current +%% scheduler will be incremented. For a plugin metric (e.g. a probe), the +%% corresponding callback module will be called. For a disabled metric, +%% `ok' will be returned without any other action being taken. +%% @end +update(Name, Value) -> + case exometer_global:status() of + enabled -> update_(Name, Value); + _ -> ok + end. + +update_(Name, Value) when is_list(Name) -> + case ets:lookup(Table = exometer_util:table(), Name) of + [#exometer_entry{status = Status} = E] + when ?IS_ENABLED(Status) -> + case E of + #exometer_entry{module = ?MODULE, type = counter} -> + ets:update_counter( + Table, Name, {#exometer_entry.value, Value}); + #exometer_entry{module = ?MODULE, + type = fast_counter, ref = {M, F}} -> + fast_incr(Value, M, F); + #exometer_entry{module = ?MODULE, type = gauge} -> + ets:update_element( + ?EXOMETER_ENTRIES, + Name, [{#exometer_entry.value, Value}]); + #exometer_entry{behaviour = probe, + type = T, ref = Pid} -> + exometer_probe:update(Name, Value, T, Pid); + #exometer_entry{module = M, behaviour = entry, + type = Type, ref = Ref} -> + M:update(Name, Value, Type, Ref) + end, + update_ok(Status, E, Value); + [] -> + {error, not_found}; + _ -> + ok + end. + +update_ok(St, #exometer_entry{name = Name}, Value) + when St band 2#10 =:= 2#10 -> + try begin + exometer_event ! {updated, Name, Value}, + ok + end + catch + _:_ -> ok + end; +update_ok(_, _, _) -> + ok. + + +-spec update_or_create(Name::name(), Value::value()) -> ok | error(). +%% @doc Update existing metric, or create+update according to template. +%% +%% If the metric exists, it is updated (see {@link update/2}). If it doesn't, +%% exometer searches for a template matching `Name', picks the best +%% match and creates a new entry based on the template +%% (see {@link exometer_admin:set_default/3}). Note that fully wild-carded +%% templates (i.e. ['_']) are ignored. +%% @end +update_or_create(Name, Value) -> + case update(Name, Value) of + {error, not_found} -> + case exometer_admin:auto_create_entry(Name) of + ok -> + update(Name, Value); + Error -> + Error + end; + ok -> + ok + end. + +update_or_create(Name, Value, Type, Opts) -> + case update(Name, Value) of + {error, not_found} -> + ensure(Name, Type, Opts), + update(Name, Value); + ok -> + ok + end. + +fast_incr(N, M, F) when N > 0 -> + M:F(), + fast_incr(N-1, M, F); +fast_incr(0, _, _) -> + ok. + + +%% @doc Fetch the current value of the metric. +%% +%% For a built-in counter, the value returned is the sum of all counter +%% instances (one per scheduler). For plugin metrics, the callback module is +%% responsible for providing the value. If the metric has a specified +%% (non-zero) cache lifetime, and a value resides in the cache, the cached +%% value will be returned. +%% @end +-spec get_value(name()) -> {ok, value()} | {error, not_found}. + +get_value(Name) when is_list(Name) -> + get_value(Name, default). + +-spec get_value(name(), datapoint() | [datapoint()]) -> + {ok, value()} | {error, not_found}. + +get_value(Name, DataPoint) when is_list(Name), is_integer(DataPoint) -> + get_value(Name, [DataPoint]); +get_value(Name, DataPoint) when is_list(Name), is_atom(DataPoint), + DataPoint=/=default -> + get_value(Name, [DataPoint]); + +%% Also covers DataPoints = default +get_value(Name, DataPoints) when is_list(Name) -> + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{} = E] -> + {ok, get_value_(E, DataPoints)}; + _ -> + {error, not_found} + end. + +%% If the entry is disabled, just err out. +get_value_(#exometer_entry{ status = Status }, _DataPoints) + when ?IS_DISABLED(Status) -> + disabled; + +%% If the value is cached, see if we can find it. +%% In the default case, call again with resolved data points. +get_value_(#exometer_entry{cache = Cache } = E, + DataPoints) when Cache =/= 0 -> + get_cached_value_(E, DataPoints); + +get_value_(#exometer_entry{ module = ?MODULE, + type = Type} = E, default) + when Type == counter; Type == fast_counter; Type == gauge -> + get_value_(E, exometer_util:get_datapoints(E)); + +get_value_(#exometer_entry{ module = ?MODULE, + type = counter} = E, DataPoints0) -> + DataPoints = datapoints(DataPoints0, E), + [get_ctr_datapoint(E, D) || D <- DataPoints]; + +get_value_(#exometer_entry{ module = ?MODULE, + type = gauge, name = Name}, DataPoints0) -> + [E] = ets:lookup(?EXOMETER_ENTRIES, Name), + DataPoints = datapoints(DataPoints0, E), + [get_gauge_datapoint(E, D) || D <- DataPoints]; + +get_value_(#exometer_entry{module = ?MODULE, + type = fast_counter} = E, DataPoints0) -> + DataPoints = datapoints(DataPoints0, E), + [get_fctr_datapoint(E, D) || D <- DataPoints ]; + +get_value_(#exometer_entry{behaviour = entry, + module = Mod, + name = Name, + type = Type, + ref = Ref} = E, DataPoints0) -> + Mod:get_value(Name, Type, Ref, datapoints(DataPoints0, E)); + +get_value_(#exometer_entry{behaviour = probe, + name = Name, + type = Type, + ref = Ref} = E, DataPoints0) -> + + exometer_probe:get_value(Name, Type, Ref, datapoints(DataPoints0, E)). + + +get_cached_value_(E, default) -> + get_cached_value_(E, exometer_util:get_datapoints(E)); + +get_cached_value_(#exometer_entry{name = Name, + cache = CacheTTL } = E, + DataPoints) -> + + %% Dig through all the data points and check for cache hit. + %% Store all cached KV pairs, and all keys that must be + %% read and cached. + { Cached, Uncached } = + lists:foldr(fun(DataPoint, {Cached1, Uncached1}) -> + case exometer_cache:read(Name, DataPoint) of + not_found -> + { Cached1, [DataPoint | Uncached1] }; + {_, Value } -> + { [{ DataPoint, Value } | Cached1], Uncached1 } + end + end, {[],[]}, DataPoints), + + %% Go through all cache misses and retreive their actual values. + case get_value_(E#exometer_entry { cache = 0 }, Uncached) of + {error, unavailable} = Result -> + %% a function entry returns this in exception case. + %% see `exometer_function:get_value/4` + Result; + Result -> + %% Update the cache with all the shiny new values retrieved. + [ exometer_cache:write(Name, DataPoint1, Value1, CacheTTL) + || { DataPoint1, Value1 } <- Result], + All = Result ++ Cached, + [{_,_} = lists:keyfind(DP, 1, All) || DP <- DataPoints] + end. + + + +-spec delete(name()) -> ok | error(). +%% @doc Delete the metric +delete(Name) when is_list(Name) -> + exometer_admin:delete_entry(Name). + +-spec sample(name()) -> ok | error(). +%% @doc Tells the metric (mainly probes) to take a sample. +%% +%% Probes often take care of data sampling using a configured sample +%% interval. This function provides a way to explicitly tell a probe to +%% take a sample. The operation is asynchronous. For other metrics, the +%% operation likely has no effect, and will return `ok'. +%% @end +sample(Name) when is_list(Name) -> + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> + case E of + #exometer_entry{module = ?MODULE, type = counter} -> ok; + #exometer_entry{module = ?MODULE, type = fast_counter} -> ok; + #exometer_entry{behaviour = probe, + type = Type, + ref = Ref} -> + exometer_probe:sample(Name, Type, Ref), + ok; + #exometer_entry{behaviour = entry, + module = M, type = Type, ref = Ref} -> + M:sample(Name, Type, Ref) + end; + [_] -> disabled; + [] -> + {error, not_found} + end. + + +-spec reset(name()) -> ok | error(). +%% @doc Reset the metric. +%% +%% For a built-in counter, the value of the counter is set to zero. For other +%% types of metric, the callback module will define exactly what happens +%% when a reset() is requested. A timestamp (`os:timestamp()') is saved in +%% the exometer entry, which can be recalled using {@link info/2}, and will +%% indicate the time that has passed since the metric was last reset. +%% @end +reset(Name) -> + case exometer_global:status() of + enabled -> reset_(Name); + _ -> ok + end. + +reset_(Name) when is_list(Name) -> + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> + case E of + #exometer_entry{module = ?MODULE, type = counter} -> + TS = exometer_util:timestamp(), + [ets:update_element(T, Name, [{#exometer_entry.value, 0}, + {#exometer_entry.timestamp, TS}]) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + #exometer_entry{module = ?MODULE, type = fast_counter, + ref = {M, F}} -> + TS = exometer_util:timestamp(), + set_call_count(M, F, true), + [ets:update_element(T, Name, [{#exometer_entry.timestamp, TS}]) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + #exometer_entry{module = ?MODULE, type = gauge} -> + TS = exometer_util:timestamp(), + [ets:update_element(T, Name, [{#exometer_entry.value, 0}, + {#exometer_entry.timestamp, TS}]) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + #exometer_entry{behaviour = probe, type = Type, ref = Ref} -> + [exometer_cache:delete(Name, DataPoint) || + DataPoint <- exometer_util:get_datapoints(E)], + exometer_probe:reset(Name, Type, Ref), + ok; + #exometer_entry{behaviour = entry, + module = M, type = Type, ref = Ref} -> + [exometer_cache:delete(Name, DataPoint) || + DataPoint <- exometer_util:get_datapoints(E)], + M:reset(Name, Type, Ref) + end; + [] -> + {error, not_found}; + _ -> + ok + end. + + +-spec setopts(name(), options()) -> ok | error(). +%% @doc Change options for the metric. +%% +%% Valid options are whatever the metric type supports, plus: +%% +%% * `{cache, Lifetime}' - The cache lifetime (0 for no caching). +%% +%% * `{status, enabled | disabled}' - the operational status of the metric. +%% +%% +%% Note that if the metric is disabled, setopts/2 will fail unless the options +%% list contains `{status, enabled}', which will enable the metric and cause +%% other options to be processed. +%% @end +setopts(Name, Options) when is_list(Name), is_list(Options) -> + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{module = ?MODULE, + type = Type, + status = Status} = E] -> + if ?IS_DISABLED(Status) -> + case lists:keyfind(status, 1, Options) of + {_, enabled} -> + case Type of + fast_counter -> setopts_fctr(E, Options); + counter -> setopts_ctr(E, Options); + gauge -> setopts_gauge(E, Options) + end, + reporter_setopts(E, Options, enabled); + _ -> + {error, disabled} + end; + ?IS_ENABLED(Status) -> + case lists:keyfind(status, 1, Options) of + {_, disabled} -> + {_, Elems} = process_setopts(E, Options), + update_entry_elems(Name, Elems), + reporter_setopts(E, Options, disabled); + R when R==false; R=={status,disabled} -> + case Type of + fast_counter -> setopts_fctr(E, Options); + counter -> setopts_ctr(E, Options); + gauge -> setopts_gauge(E, Options) + end, + reporter_setopts(E, Options, enabled) + end + end; + [#exometer_entry{status = Status} = E] when ?IS_ENABLED(Status) -> + NewStatus = proplists:get_value(status, Options, enabled), + {_, Elems} = process_setopts(E, Options), + update_entry_elems(Name, Elems), + module_setopts(E, Options, NewStatus); + + [#exometer_entry{status = Status} = E] when ?IS_DISABLED(Status) -> + case lists:keyfind(status, 1, Options) of + {_, enabled} -> + {_, Elems} = process_setopts(E, Options), + update_entry_elems(Name, Elems), + module_setopts(E, Options, enabled); + false -> + {error, disabled} + end; + [] -> + {error, not_found} + end. + +module_setopts(#exometer_entry{behaviour = probe, + name = Name, + status = St0} = E, Options, NewStatus) -> + reporter_setopts(E, Options, NewStatus), + case NewStatus of + disabled when ?IS_ENABLED(St0) -> + exometer_cache:delete_name(Name), + exometer_probe:stop_probe(E), + update_entry_elems(Name, [{#exometer_entry.ref, undefined}]); + enabled when ?IS_DISABLED(St0) -> + %% Previously, probes were not stopped when disabled + OldRef = E#exometer_entry.ref, + case is_pid(OldRef) andalso is_process_alive(OldRef) of + true -> ok; + false -> + {ok, Ref} = exometer_probe:start_probe(E), + update_entry_elems(Name, [{#exometer_entry.ref, Ref}]) + end; + _ -> + exometer_probe:setopts(E, Options, NewStatus) + end, + ok; + +module_setopts(#exometer_entry{behaviour = entry, + module=M} = E, Options, NewStatus) -> + case [O || {K, _} = O <- Options, + not lists:member(K, [status, cache, ref])] of + [] -> + ok; + [_|_] = UserOpts -> + case M:setopts(E, UserOpts, NewStatus) of + ok -> + reporter_setopts(E, Options, NewStatus), + ok; + {error,_} = Error -> + Error + end + end. + +reporter_setopts(#exometer_entry{} = E, Options, Status) -> + exometer_report:setopts(E, Options, Status). + +setopts_fctr(#exometer_entry{name = Name, + ref = OldRef, + status = OldStatus} = E, Options) -> + {#exometer_entry{status = NewStatus}, Elems} = + process_setopts(E, Options), + Ref = case lists:keyfind(function, 1, Options) of + {_, {M, F} = NewRef} when is_atom(M), is_atom(F), + M =/= '_', F =/= '_' -> NewRef; + false -> OldRef + end, + if Ref =/= OldRef -> + set_call_count(OldRef, false), + set_call_count(Ref, NewStatus == enabled); + true -> + if OldStatus =/= NewStatus -> + set_call_count(Ref, NewStatus == enabled); + true -> + %% Setting call_count again will reset the counter + %% so don't do it unnecessarily + ok + end + end, + Elems1 = add_elem(ref, Ref, Elems), + update_entry_elems(Name, Elems1), + ok. + +setopts_ctr(#exometer_entry{name = Name} = E, Options) -> + {_, Elems} = process_setopts(E, Options), + update_entry_elems(Name, Elems), + ok. + +setopts_gauge(E, Options) -> + setopts_ctr(E, Options). % same logic as for counter + +%% cache(0, _, Value) -> +%% Value; +%% cache(TTL, Name, Value) when TTL > 0 -> +%% exometer_cache:write(Name, Value, TTL), +%% Value. + +update_entry_elems(Name, Elems) -> + [ets:update_element(T, Name, Elems) || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok. + +-type info() :: name | type | module | value | cache + | status | timestamp | options | ref | datapoints | entry. +-spec info(name(), info()) -> any(). +%% @doc Retrieves information about a metric. +%% +%% Supported info items: +%% +%% * `name' - The name of the metric +%% * `type' - The type of the metric +%% * `module' - The callback module used +%% * `value' - The result of `get_value(Name)' +%% * `cache' - The cache lifetime +%% * `status' - Operational status: `enabled' or `disabled' +%% * `timestamp' - When the metric was last reset/initiated +%% * `datapoints' - Data points available for retrieval with get_value() +%% * `options' - Options passed to the metric at creation (or via setopts()) +%% * `ref' - Instance-specific reference; usually a pid (probe) or undefined +%% @end +info(#exometer_entry{status = Status} = E, Item) -> + info_(E, Item, info_status(Status)); +info(Name, Item) -> + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{status = Status0} = E] -> + Status = info_status(Status0), + info_(E, Item, Status); + _ -> + undefined + end. + +info_(E, Item, Status) -> + case {Item, Status} of + {name, _} -> E#exometer_entry.name; + {type, _} -> E#exometer_entry.type; + {module, _} -> E#exometer_entry.module; + {value, enabled} -> get_value_(E, exometer_util:get_datapoints(E)); + {cache, enabled} -> E#exometer_entry.cache; + {status, _} -> Status; + {timestamp, enabled} -> E#exometer_entry.timestamp; + {options, _} -> E#exometer_entry.options; + {ref, _} -> E#exometer_entry.ref; + {entry, _} -> E; + {datapoints, enabled} -> exometer_util:get_datapoints(E); + _ -> undefined + end. + +info_status(S) when ?IS_ENABLED(S) -> + enabled; +info_status(_) -> + disabled. + +datapoints(default, _E) -> + default; +datapoints(D, _) when is_atom(D) -> + [D]; +datapoints(D, _) when is_integer(D) -> + [D]; +datapoints(D, _) when is_list(D) -> + D. + +-spec info(name()) -> [{info(), any()}]. +%% @doc Returns a list of info items for Metric, see {@link info/2}. +info(Name) -> + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{status = Status0} = E] -> + Status = info_status(Status0), + Flds = record_info(fields, exometer_entry), + lists:map(fun(Item) -> {Item, info_(E, Item, Status)} end, Flds); + _ -> + undefined + end. + +-spec find_entries([any() | '_']) -> [{name(), type(), status()}]. +%% @doc Find metrics based on a name prefix pattern. +%% +%% This function will find and return metrics whose name matches the given +%% prefix. For example `[kvdb, kvdb_conf, Table]' would match any metrics +%% tied to the given table in the `kvdb_conf' database. +%% +%% It is possible to insert wildcards: +%% [kvdb, kvdb_conf, '_', write] would match +%% `write'-related metrics in all tables of the `kvdb_conf' database. +%% +%% The format of the returned metrics is `[{Name, Type, Status}]'. +%% @end +find_entries(Path) -> + Pat = Path ++ '_', + ets:select(?EXOMETER_ENTRIES, + [ { #exometer_entry{name = Pat, _ = '_'}, [], + [{{ {element, #exometer_entry.name, '$_'}, + {element, #exometer_entry.type, '$_'}, + status_body_pattern() + }}] } ]). + +get_values(Path) -> + Entries = find_entries(Path), + lists:foldr( + fun({Name, _Type, enabled}, Acc) -> + case get_value(Name) of + {ok, V} -> + [{Name, V}|Acc]; + {error,not_found} -> + Acc + end + end, [], Entries). + +-spec select(ets:match_spec()) -> list(). +%% @doc Perform an `ets:select()' on the set of metrics. +%% +%% This function operates on a virtual structure representing the metrics, +%% but otherwise works as a normal `select()'. The representation of the +%% metrics is `{Name, Type, Status}'. +%% @end +select(Pattern) -> + ets:select(?EXOMETER_ENTRIES, [pattern(P) || P <- Pattern]). + +-spec select_count(ets:match_spec()) -> non_neg_integer(). +%% @doc Corresponds to {@link ets:select_count/1}. +%% @end +select_count(Pattern) -> + ets:select_count(?EXOMETER_ENTRIES, [pattern(P) || P <- Pattern]). + +-spec select(ets:match_spec(), pos_integer() | infinity) -> {list(), _Cont}. +%% @doc Perform an `ets:select()' with a Limit on the set of metrics. +%% +%% This function is equivalent to {@link select/1}, but also takes a limit. +%% After `Limit' number of matches, the function returns the matches and a +%% continuation, which can be passed to {@link select_cont/1}. +%% @end +select(Pattern, Limit) -> + ets:select(?EXOMETER_ENTRIES, [pattern(P) || P <- Pattern], Limit). + +-spec select_cont('$end_of_table' | tuple()) -> + '$end_of_table' + | {[{name(), type(), status()}], _Cont}. +%% @equiv ets:select(Cont) +%% +select_cont('$end_of_table') -> '$end_of_table'; +select_cont(Cont) -> + ets:select(Cont). + +-spec aggregate(ets:match_spec(), [datapoint()]) -> list(). +%% @doc Aggregate datapoints of matching entries. +%% +%% This function selects metric entries based on the given match spec, and +%% summarizes the given datapoint values. +%% +%% Note that the match body of the match spec will be overwritten, to produce +%% only the value for each entry matching the head and guard pattern(s). +%% +%% The function can for example be used inside a function metric: +%% +%%
 exometer:start().
+%% ok
+%% 2> exometer:new([g,1], gauge, []).
+%% ok
+%% 3> exometer:new([g,2], gauge, []).
+%% ok
+%% 4> exometer:new([g,3], gauge, []).
+%% ok
+%% 5> [exometer:update(N,V) || {N,V} <- [{[g,1],3}, {[g,2],4}, {[g,3],5}]].
+%% [ok,ok,ok]
+%% 6> exometer:new([g], {function,exometer,aggregate,
+%%                       [ [{{[g,'_'],'_','_'},[],[true]}], [value] ],
+%%                       value, [value]}, []).
+%% ok
+%% 7> exometer:get_value([g], [value]).
+%% {ok,[{value,12}]}
+%% ]]>
+%% @end +aggregate(Pattern, DataPoints) -> + case aggr_select(Pattern) of + [] -> []; + Found -> + aggregate(Found, DataPoints, orddict:new()) + end. + +aggr_select(Pattern) -> + select([setelement(3,P,[{element,1,'$_'}]) || P <- Pattern]). + +aggregate([N|Ns], DPs, Acc) when is_list(N) -> + case get_value(N, DPs) of + {ok, Vals} -> + aggregate(Ns, DPs, aggr_acc(Vals, Acc)); + _ -> + aggregate(Ns, DPs, Acc) + end; +aggregate([], _, Acc) -> + Acc. + +aggr_acc([{D,V}|T], Acc) -> + if is_integer(V) -> + aggr_acc(T, orddict:update(D, fun(Val) -> + Val + V + end, V, Acc)); + true -> + aggr_acc(T, Acc) + end; +aggr_acc([], Acc) -> + Acc. + +global_status(St) when St==enabled; St==disabled -> + Prev = exometer_global:status(), + if St =:= Prev -> ok; + true -> + parse_trans_mod:transform_module( + exometer_global, fun(Forms,_) -> pt(Forms, St) end, []) + end, + Prev. + +pt(Forms, St) -> + parse_trans:plain_transform( + fun(F) -> + plain_pt(F, St) + end, Forms). + +plain_pt({function, L, status, 0, [_]}, St) -> + {function, L, status, 0, + [{clause, L, [], [], [{atom, L, St}]}]}; +plain_pt(_, _) -> + continue. + + +%% Perform variable replacement in the ets select pattern. +%% We want to project the entries as a set of {Name, Type, Status} tuples. +%% This means we need to perform variable substitution in both guards and +%% match bodies. Note that the 'status' attribute is now a bit map, but we +%% want the disabled|enabled 'status' to be presented (and also to be +%% matchable). +pattern({'_', Gs, Prod}) -> + {'_', repl(Gs, g_subst(['$_'])), repl(Prod, p_subst(['$_']))}; +pattern({KP, Gs, Prod}) when is_atom(KP) -> + {KP, repl(Gs, g_subst([KP,'$_'])), repl(Prod, p_subst([KP,'$_']))}; +pattern({{N,T,S}, Gs, Prod}) -> + %% Remember the match head variables, at least if they are simple + %% dollar variables, so that we can perform substitution on them + %% later on. + Tail = match_tail(N,T,S), + {S1, Gs1} = repl_status(S, repl(Gs, g_subst(['$_'|Tail]))), + {#exometer_entry{name = N, type = T, status = S1, _ = '_'}, + Gs1, repl(Prod, p_subst(['$_'|Tail]))}. + +%% The "named variables" are for now undocumented. +repl('$name' ,_) -> {element, #exometer_entry.name, '$_'}; +repl('$type' ,_) -> {element, #exometer_entry.type, '$_'}; +repl('$options' ,_) -> {element, #exometer_entry.options, '$_'}; +repl('$status' ,_) -> status_body_pattern(); +repl('$status_bits',_) -> {element, #exometer_entry.status, '$_'}; +repl('$ref' ,_) -> {element, #exometer_entry.ref, '$_'}; +repl('$behaviour' ,_) -> {element, #exometer_entry.behaviour, '$_'}; +repl('$cache' ,_) -> {element, #exometer_entry.cache, '$_'}; +repl('$timestamp' ,_) -> {element, #exometer_entry.timestamp, '$_'}; +repl(P, Subst) when is_atom(P) -> + %% Check if P is one of the match variables we've saved. + case lists:keyfind(P, 1, Subst) of + {_, Repl} -> Repl; + false -> P + end; +repl(T, Subst) when is_tuple(T) -> + list_to_tuple(repl(tuple_to_list(T), Subst)); +repl([H|T], Subst) -> + [repl(H, Subst)|repl(T, Subst)]; +repl(X, _) -> + X. + +repl_status(S, Gs) when S==enabled; S==disabled -> + {'_', [{'=:=', status_body_pattern(), S}|Gs]}; +repl_status(S, Gs) -> + {S, Gs}. + + +g_subst(Ks) -> + [g_subst_(K) || K <- Ks]. + +%% Potentially save Name, Type and Status match head patterns +match_tail(N,T,S) -> + case is_dollar(N) of true -> + [{N, {element,#exometer_entry.name,'$_'}}|match_tail(T,S)]; + false -> match_tail(T, S) + end. +match_tail(T,S) -> + case is_dollar(T) of true -> + [{T, {element,#exometer_entry.type,'$_'}}|match_tail(S)]; + false -> match_tail(S) + end. +match_tail(S) -> + case is_dollar(S) of true -> + [{S, status_element_pattern(S)}]; + false -> [] + end. + +is_dollar(A) when is_atom(A) -> + case atom_to_list(A) of + [$$ | Rest] -> + try _ = list_to_integer(Rest), true + catch error:_ -> false + end; + _ -> false + end; +is_dollar(_) -> false. + + +g_subst_({_,_}=X) -> X; +g_subst_(K) when is_atom(K) -> + {K, {{{element,#exometer_entry.name,'$_'}, + {element,#exometer_entry.type,'$_'}, + status_element_pattern({element,#exometer_entry.status,'$_'})}}}. + + +%% The status attribute: bit 1 indicates enabled (1) or disabled (0) +status_element_pattern(S) -> + %% S is the match head pattern corresponding to the status bits + {element, {'+',{'band',S,1},1}, {{disabled,enabled}}}. + +status_body_pattern() -> + {element, + {'+',{'band', + {element,#exometer_entry.status,'$_'},1},1}, + {{disabled,enabled}}}. + +p_subst(Ks) -> + [p_subst_(K) || K <- Ks]. +p_subst_({_,_}=X) -> X; +p_subst_(K) when is_atom(K) -> + {K, {{{element,#exometer_entry.name,'$_'}, + {element,#exometer_entry.type,'$_'}, + status_body_pattern()}}}. + +%% This function returns a list of elements for ets:update_element/3, +%% to be used for updating the #exometer_entry{} instances. +%% The options attribute is always updated, replacing old matching +%% options in the record. +process_setopts(#exometer_entry{ + name = Name, ref = Ref, type = Type, + module = M, options = OldOpts} = Entry, Options) -> + case M =/= exometer andalso + erlang:function_exported(M, preprocess_setopts, 5) of + true -> + Options1 = M:preprocess_setopts(Name, Options, Type, Ref, OldOpts), + process_setopts_(Entry, Options1); + false -> + process_setopts_(Entry, Options) + end. + +process_setopts_(#exometer_entry{options = OldOpts} = Entry, Options) -> + {E1, Elems} = + lists:foldr( + fun + ({cache, Val}, + {#exometer_entry{cache = Cache0} = E, Elems} = Acc) -> + if is_integer(Val), Val >= 0 -> + if Val =/= Cache0 -> + {E#exometer_entry{cache = Val}, + add_elem(cache, Val, Elems)}; + true -> + Acc + end; + true -> error({illegal, {cache, Val}}) + end; + ({status, Status}, {#exometer_entry{status = Status0} = E, Elems} = Acc) -> + case is_status_change(Status, Status0) of + {true, Status1} -> + {E#exometer_entry{status = Status1}, + add_elem(status, Status1, Elems)}; + false -> + Acc + end; + ({update_event, UE}, + {#exometer_entry{status = Status0} = E, Elems} = Acc) + when is_boolean(UE) -> + case (exometer_util:test_event_flag(update, Status0) == UE) of + true -> Acc; + false -> + %% value changed + if UE -> + Status = exometer_util:set_event_flag( + update, E#exometer_entry.status), + {E#exometer_entry{status = Status}, + add_elem(status, Status, Elems)}; + true -> + Status = exometer_util:clear_event_flag( + update, E#exometer_entry.status), + {E#exometer_entry{status = Status}, + add_elem(status, Status, Elems)} + end + end; + ({ref,R}, {E, Elems}) -> + {E#exometer_entry{ref = R}, add_elem(ref, R, Elems)}; + ({_,_}, Acc) -> + Acc + end, {Entry, []}, Options), + {E1, [{#exometer_entry.options, update_opts(Options, OldOpts)}|Elems]}. + +is_status_change(enabled, St) -> + if ?IS_ENABLED(St) -> false; + true -> {true, exometer_util:set_status(enabled, St)} + end; +is_status_change(disabled, St) -> + if ?IS_DISABLED(St) -> false; + true -> {true, exometer_util:set_status(disabled, St)} + end. + + +add_elem(K, V, Elems) -> + P = pos(K), + lists:keystore(P, 1, Elems, {P, V}). + +pos(cache) -> #exometer_entry.cache; +pos(status) -> #exometer_entry.status; +pos(ref) -> #exometer_entry.ref. + +update_opts(New, Old) -> + type_arg_first(lists:foldl( + fun({K,_} = Opt, Acc) -> + lists:keystore(K, 1, Acc, Opt) + end, Old, New)). + +type_arg_first([{arg,_}|_] = Opts) -> + Opts; + +type_arg_first(Opts) -> + case lists:keyfind(arg, 1, Opts) of + false -> + Opts; + Arg -> + [Arg|Opts -- [Arg]] + end. + + + +%% Retrieve individual data points for the counter maintained by +%% the exometer record itself. +get_ctr_datapoint(#exometer_entry{name = Name}, value) -> + {value, counter_sum(Name)}; +get_ctr_datapoint(#exometer_entry{name = Name}, value16) -> + {value16, counter_sum(Name) rem 16#FFFF}; +get_ctr_datapoint(#exometer_entry{name = Name}, value32) -> + {value32, counter_sum(Name) rem 16#FFFFFFFF}; +get_ctr_datapoint(#exometer_entry{name = Name}, value64) -> + {value64, counter_sum(Name) rem 16#FFFFFFFFFFFFFFFF}; +get_ctr_datapoint(#exometer_entry{timestamp = TS}, ms_since_reset) -> + {ms_since_reset, exometer_util:timestamp() - TS}; +get_ctr_datapoint(#exometer_entry{}, Undefined) -> + {Undefined, undefined}. + +counter_sum(Name) -> + lists:sum([ets:lookup_element(T, Name, #exometer_entry.value) + || T <- exometer_util:tables()]). + +get_gauge_datapoint(#exometer_entry{value = Value}, value) -> + {value, Value}; +get_gauge_datapoint(#exometer_entry{timestamp = TS}, ms_since_reset) -> + {ms_since_reset, exometer_util:timestamp() - TS}; +get_gauge_datapoint(#exometer_entry{}, Undefined) -> + {Undefined, undefined}. + +get_fctr_datapoint(#exometer_entry{ref = Ref}, value) -> + case Ref of + {M, F} -> + {call_count, Res} = erlang:trace_info({M, F, 0}, call_count), + case Res of + C when is_integer(C) -> + {value, C}; + _ -> + {value, 0} + end; + _ -> {value, 0} + end; +get_fctr_datapoint(#exometer_entry{timestamp = TS }, ms_since_reset) -> + {ms_since_reset, exometer_util:timestamp() - TS }; +get_fctr_datapoint(#exometer_entry{ }, Undefined) -> + {Undefined, undefined}. + + +create_entry(#exometer_entry{module = exometer, + type = Type} = E) when Type == counter; + Type == gauge -> + E1 = E#exometer_entry{value = 0, timestamp = exometer_util:timestamp()}, + insert_aliases(E1), + [ets:insert(T, E1) || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + +create_entry(#exometer_entry{module = exometer, + status = Status, + type = fast_counter, options = Opts} = E) -> + case lists:keyfind(function, 1, Opts) of + false -> + error({required, function}); + {_, {M,F}} when is_atom(M), M =/= '_', + is_atom(F), M =/= '_' -> + code:ensure_loaded(M), % module must be loaded for trace_pattern + E1 = E#exometer_entry{ref = {M, F}, value = 0, + timestamp = exometer_util:timestamp()}, + set_call_count(M, F, ?IS_ENABLED(Status)), + insert_aliases(E1), + [ets:insert(T, E1) || + T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + Other -> + error({badarg, {function, Other}}) + end; + + +create_entry(#exometer_entry{module = Module, + type = Type, + name = Name, + status = Status, + options = Opts} = E) -> + case + case Module:behaviour() of + probe -> + if ?IS_ENABLED(Status) -> + {probe, exometer_probe:new( + Name, Type, [{ arg, Module} | Opts ])}; + ?IS_DISABLED(Status) -> + %% Don't start probe if disabled + {probe, ok} + end; + entry -> + {entry, Module:new(Name, Type, Opts) }; + + Other -> Other + end + of + {Behaviour, ok }-> + insert_aliases(E), + [ets:insert(T, E#exometer_entry { behaviour = Behaviour }) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + {Behaviour, {ok, Ref}} -> + insert_aliases(E), + [ets:insert(T, E#exometer_entry{ref=Ref, behaviour=Behaviour}) + || T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + Other1 -> + Other1 + end; +create_entry(_Other) -> + {error, unknown_argument}. + +insert_aliases(#exometer_entry{name = Name, options = Options}) -> + case lists:keyfind(aliases, 1, Options) of + {_, Aliases} -> + case lists:all(fun({DP, Alias}) -> + (is_atom(DP) orelse is_integer(DP)) + andalso (is_atom(Alias) orelse is_binary(Alias)); + (_) -> false + end, Aliases) of + true -> + [exometer_alias:new(Alias, Name, DP) || {DP, Alias} <- Aliases], + ok; + false -> + erlang:error({invalid_aliases, Aliases}) + end; + false -> + ok + end. + +set_call_count({M, F}, Bool) -> + set_call_count(M, F, Bool). + +set_call_count(M, F, Bool) when is_atom(M), is_atom(F), is_boolean(Bool) -> + erlang:trace_pattern({M, F, 0}, Bool, [call_count]). + +-spec register_application() -> ok | error(). +%% @equiv register_application(current_application()) +%% +register_application() -> + case application:get_application() of + {ok, App} -> + register_application(App); + Other -> + {error, Other} + end. + +-spec register_application(_Application::atom()) -> ok | error(). +%% @doc Registers statically defined entries with exometer. +%% +%% This function can be used e.g. as a start phase hook or during upgrade. +%% It will check for the environment variables `exometer_defaults' and +%% `exometer_predefined' in `Application', and apply them as if it had +%% when exometer was first started. If the function is called again, +%% the settings are re-applied. This can be used e.g. during upgrade, +%% in order to change statically defined settings. +%% +%% If exometer is not running, the function does nothing. +%% @end +register_application(App) -> + exometer_admin:register_application(App). diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_admin.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_admin.erl new file mode 100644 index 0000000..8d34293 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_admin.erl @@ -0,0 +1,727 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-module(exometer_admin). + +-behaviour(gen_server). + +-export( + [ + init/1, + start_link/0, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). + +-export( + [ + new_entry/3, + propose/3, + re_register_entry/3, + repair_entry/1, + delete_entry/1, + ensure/3, + auto_create_entry/1 + ]). + +-export( + [ + set_default/3, + preset_defaults/0, + load_defaults/0, + load_predefined/0, + register_application/1, + normalize_name/1 + ]). +-export([find_auto_template/1, prefixes/1, make_patterns/2]). + +-export([monitor/2, monitor/3, demonitor/1]). + +-compile({no_auto_import, [monitor/3]}). +-include_lib("hut/include/hut.hrl"). +-include("exometer.hrl"). + +-record(st, {}). + +-spec set_default([atom()], atom(), #exometer_entry{} | [{atom(),any()}]) -> + true. + +%% @doc Sets a default definition for a metric type, possibly using wildcards. +%% +%% Names are lists of atoms, where '_' is a wildcard. For example, +%% [a, b, c, '_'] matches all children and grandchildren of +%% `[a, b, c]', whereas `[a, b, c, d]' specifies a single name. +%% +%% The longest match will be selected, unless an exact match is found. +%% The definition can be given either as an `#exometer_entry{}' record, or +%% a list of `{Key, Value}' tuples, where each `Key' matches an attribute +%% of the `#exometer_entry{}' record. +%% @end +set_default(NamePattern0, Type, #exometer_entry{} = E) + when is_list(NamePattern0) -> + NamePattern = lists:map(fun('_') -> ''; + ('' ) -> error({not_allowed, ''}); + (X ) -> X + end, NamePattern0), + ets:insert(?EXOMETER_SHARED, + E#exometer_entry{name = {default,Type,NamePattern}, + type = Type}); +set_default(NamePattern, Type, Opts) when is_list(NamePattern) -> + set_default(NamePattern, Type, opts_to_rec(Type, Opts)). + +preset_defaults() -> + load_defaults(), + load_predefined(). + +load_defaults() -> + E = exometer_util:get_env(defaults, []), + do_load_defaults(env, get_predef(E)), + [do_load_defaults(Src, get_predef(D)) + || {Src,D} <- setup:find_env_vars(exometer_defaults)], + ok. + +load_predefined() -> + E = exometer_util:get_env(predefined, []), + do_load_predef(env, get_predef(E)), + [do_load_predef(Src, get_predef(P)) + || {Src, P} <- setup:find_env_vars(exometer_predefined)], + ok. + +register_application(App) -> + %% Ignore if exometer is not running + case whereis(exometer_admin) of + undefined -> ok; + _ -> + case setup:get_env(App, exometer_defaults) of + {ok, E} -> + do_load_defaults(App, get_predef(E)); + undefined -> + ok + end, + case setup:get_env(App, exometer_predefined) of + {ok, P} -> + do_load_predef(App, get_predef(P)); + undefined -> + ok + end + end. + +get_predef({script, F} ) -> ok(file:script(F, []), F); +get_predef({consult,F} ) -> ok(file:consult(F), F); +get_predef({apply, M, F, A}) -> ok(apply(M, F, A), {M,F,A}); +get_predef(L) when is_list(L) -> L. + +do_load_defaults(Src, L) when is_list(L) -> + lists:foreach( + fun({NamePattern, Type, Spec}) -> + try set_default(NamePattern, Type, Spec) + catch + error:E -> + ?log(error, "Defaults(~p): ERROR: ~p~n", [Src, E]) + end + end, L). + +do_load_predef(Src, L) when is_list(L) -> + lists:foreach( + fun({Name, Type, Options}) -> + new_entry(Name, Type, Options); + ({delete, Key}) -> + predef_delete_entry(Key, Src); + ({re_register, {Name, Type, Options}}) -> + re_register_entry(Name, Type, Options); + ({select_delete, Pat}) -> + Found = exometer:select(Pat), + lists:foreach( + fun({K,_,_}) -> + predef_delete_entry(K, Src); + (Other) -> + ?log(error, "Predef(~p): ~p~n", + [Src, {bad_pattern,Other}]) + end, Found); + ({aliases, Aliases}) -> + lists:foreach( + fun({Alias, Entry, DP}) -> + exometer_alias:new(Alias, Entry, DP) + end, Aliases) + end, L). + +predef_delete_entry(Key, Src) -> + case delete_entry(Key) of + ok -> ok; + Error -> + ?log(error, "Predef(~p): ~p~n", [Src, Error]) + end. + +ok({ok, Res}, _) -> Res; +ok({error, E}, I) -> + erlang:error({E, I}). + +new_entry(Name, Type, Opts) -> + %% { arg, { function, M, F }} + %% { arg, { function, M, F }} + {Type1, Opt1} = check_type_arg(Type, Opts), + case gen_server:call(?MODULE, {new_entry, Name, Type1, Opt1, false}) of + {error, Reason} -> + error(Reason); + ok -> + ok + end. + +propose(Name, Type, Opts) -> + {Type1, Opt1} = check_type_arg(Type, Opts), + gen_server:call(?MODULE, {propose, Name, Type1, Opt1}). + +report_new_entry(#exometer_entry{} = E) -> + exometer_report:new_entry(E). + +re_register_entry(Name, Type, Opts) -> + {Type1, Opts1} = check_type_arg(Type, Opts), + case gen_server:call(?MODULE, {new_entry, Name, Type1, Opts1, true}) of + {error, Reason} -> + error(Reason); + ok -> + ok + end. + +repair_entry(Name) -> + case gen_server:call(?MODULE, {repair_entry, Name}) of + {error, Reason} -> + error(Reason); + ok -> + ok + end. + +delete_entry(Name) -> + gen_server:call(?MODULE, {delete_entry, Name}). + +ensure(Name, Type, Opts) -> + {Type1, Opt1} = check_type_arg(Type, Opts), + case gen_server:call(?MODULE, {ensure, Name, Type1, Opt1}) of + {error, Reason} -> + error(Reason); + ok -> + ok + end. + +auto_create_entry(Name) -> + gen_server:call(?MODULE, {auto_create, Name}). + +check_type_arg({function, M, F}, Opts) -> + {function, [{arg, {M, F}} | Opts]}; + +check_type_arg({function, Mod, Fun, ArgSpec, Type, DataPoints}, Opts) -> + {function, [{arg, {Mod, Fun, ArgSpec, Type, DataPoints}} | Opts]}; + +check_type_arg({T, Arg}, Opts) -> + {T, [{arg, Arg} | Opts]}; + +check_type_arg(Type, Opts) -> + {Type, Opts}. + +monitor(Name, Pid) when is_pid(Pid) -> + monitor(Name, Pid, delete). + +monitor(Name, Pid, OnError) when is_pid(Pid) -> + gen_server:cast(?MODULE, {monitor, Name, Pid, OnError}). + +demonitor(Pid) when is_pid(Pid) -> + gen_server:cast(?MODULE, {demonitor, Pid}). + +opts_to_rec(Type, Opts0) -> + Opts = case lists:keymember(module, 1, Opts0) of + true -> Opts0; + false -> [{module, module(Type)}|Opts0] + end, + Flds = record_info(fields, exometer_entry), + lists:foldr(fun({K,V}, Acc) -> + setelement(pos(K, Flds), Acc, V) + end, #exometer_entry{options = Opts0}, Opts). + +pos(K, L) -> pos(K, L, 2). + +pos(K, [K|_], P) -> P; +pos(K, [_|T], P) -> pos(K, T, P+1); +pos(K, [] , _) -> error({unknown_option, K}). + +normalize_name(N) when is_tuple(N) -> + tuple_to_list(N); +normalize_name(N) when is_list(N) -> + N. + +start_link() -> + create_ets_tabs(), + gen_server:start_link({local,?MODULE}, ?MODULE, [], []). + +init(_) -> + {ok, #st{}}. + +handle_call({new_entry, Name, Type, Opts, AllowExisting} = _Req, _From, S) -> + try + #exometer_entry{options = NewOpts} = E0 = + lookup_definition(Name, Type, Opts), + + case {ets:member(exometer_util:table(), Name), AllowExisting} of + {true, false} -> + {reply, {error, exists}, S}; + _Other -> + ?log(debug, "_Other = ~p~n", [_Other]), + E1 = process_opts(E0, NewOpts), + Res = try exometer:create_entry(E1), + exometer_report:new_entry(E1) + catch + error:Error1 -> + ?log(debug, + "ERROR create_entry(~p) :- ~p~n~p", + [E1, Error1, erlang:get_stacktrace()]), + erlang:error(Error1) + end, + {reply, Res, S} + end + catch + error:Error -> + ?log(error, "~p -*-> error:~p~n~p~n", + [_Req, Error, erlang:get_stacktrace()]), + {reply, {error, Error}, S} + end; +handle_call({repair_entry, Name}, _From, S) -> + try + #exometer_entry{} = E = exometer:info(Name, entry), + delete_entry_(Name), + exometer:create_entry(E), + {reply, ok, S} + catch + error:Error -> + {reply, {error, Error}, S} + end; +handle_call({propose, Name, Type, Opts}, _From, S) -> + try + #exometer_entry{options = NewOpts} = E0 = + lookup_definition(Name, Type, Opts), + E1 = process_opts(E0, NewOpts), + {reply, exometer_info:pp(E1), S} + catch + error:Error -> + {reply, {error, Error}, S} + end; +handle_call({delete_entry, Name}, _From, S) -> + {reply, delete_entry_(Name), S}; +handle_call({ensure, Name, Type, Opts}, _From, S) -> + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{type = Type}] -> + {reply, ok, S}; + [#exometer_entry{type = _OtherType}] -> + {reply, {error, type_mismatch}, S}; + [] -> + #exometer_entry{options = OptsTemplate} = E0 = + lookup_definition(Name, Type, Opts), + E1 = process_opts(E0, OptsTemplate ++ Opts), + Res = exometer:create_entry(E1), + report_new_entry(E1), + {reply, Res, S} + end; +handle_call({auto_create, Name}, _From, S) -> + case find_auto_template(Name) of + false -> + {reply, {error, no_template}, S}; + #exometer_entry{options = Opts} = E -> + E1 = process_opts(E#exometer_entry{name = Name}, Opts), + Res = exometer:create_entry(E1), + report_new_entry(E1), + {reply, Res, S} + end; +handle_call(_, _, S) -> + {reply, error, S}. + +handle_cast({monitor, Name, Pid, OnError}, S) -> + Ref = erlang:monitor(process, Pid), + put(Ref, {Name, OnError}), + put(Pid, Ref), + {noreply, S}; +handle_cast({demonitor, Pid}, S) -> + case get(Pid) of + undefined -> + {noreply, S}; + Ref -> + erase(Pid), + erase(Ref), + try erlang:demonitor(Ref) catch error:_ -> ok end, + {noreply, S} + end; +handle_cast(_, S) -> + {noreply, S}. + +handle_info({'DOWN', Ref, _, Pid, _}, S) -> + case get(Ref) of + undefined -> + {noreply, S}; + {Name, OnError} when is_atom(Name); is_list(Name) -> + erase(Ref), + erase(Pid), + on_error(Name, OnError), + {noreply, S}; + Name when is_atom(Name) -> + %% BW compat + erase(Ref), + erase(Pid), + {noreply, S}; + Name when is_list(Name) -> + %% BW compat + erase(Ref), + erase(Pid), + on_error(Name, delete), + {noreply, S} + end; +handle_info(_, S) -> + {noreply, S}. + +terminate(_, _) -> + ok. + +code_change(_, S, _) -> + case ets:info(?EXOMETER_REPORTERS, name) of + undefined -> create_reporter_tabs(); + _ -> ok + end, + {ok, S}. + +create_reporter_tabs() -> + Heir = {heir, whereis(exometer_sup), []}, + ets:new(?EXOMETER_REPORTERS, [public, named_table, set, + {keypos, 2}, Heir]), + ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set, + {keypos, 2}, Heir]). + + +create_ets_tabs() -> + case ets:info(?EXOMETER_SHARED, name) of + undefined -> + [ets:new(T, [public, named_table, set, {keypos,2}]) + || T <- tables()], + ets:new(?EXOMETER_SHARED, [public, named_table, ordered_set, + {keypos, 2}]), + ets:new(?EXOMETER_ENTRIES, [public, named_table, ordered_set, + {keypos, 2}]), + ets:new(?EXOMETER_REPORTERS, [public, named_table, set, + {keypos, 2}]), + ets:new(?EXOMETER_SUBS, [public, named_table, ordered_set, + {keypos, 2}]); + _ -> + true + end. + +tables() -> + exometer_util:tables(). + + +%% ==== + +on_error(Name, {restart, {M, F, A}}) -> + try call_restart(M, F, A) of + {ok, Ref} -> + if is_list(Name) -> + [ets:update_element(T, Name, [{#exometer_entry.ref, Ref}]) + || T <- exometer_util:tables()]; + true -> ok + end, + ok; + disable -> + try_disable_entry_(Name); + delete -> + try_delete_entry_(Name); + Other -> + restart_failed(Name, Other) + catch error:R -> + restart_failed(Name, {error, R}) + end, + ok; +on_error(Name, delete) -> + try_delete_entry_(Name); +on_error(_Proc, _OnError) -> + %% Not good, but will do for now. + ?log(debug, "Unrecognized OnError: ~p (~p)~n", [_OnError, _Proc]), + ok. + +call_restart(M, F, A) -> + apply(M, F, A). + +restart_failed(Name, Error) -> + ?log(debug, "Restart failed ~p: ~p~n", [Name, Error]), + if is_list(Name) -> + try_delete_entry_(Name); + true -> + ok + end. + +lookup_definition(Name, Type, Opts) -> + check_aliases(lookup_definition_(Name, Type, Opts)). + +lookup_definition_(Name, ad_hoc, Opts) -> + case [K || K <- [module, type], not lists:keymember(K, 1, Opts)] of + [] -> + {E0, Opts1} = + lists:foldr( + fun({module, M}, {E,Os}) -> + {E#exometer_entry{module = M}, Os}; + ({type, T}, {E, Os}) -> + case T of + {Type, Arg} -> + {E#exometer_entry{type = Type}, + [{arg, Arg}|Os]}; + _ when is_atom(T) -> + {E#exometer_entry{type = T}, Os} + end; + (O, {E, Os}) -> + {E, [O|Os]} + end, {#exometer_entry{name = Name}, []}, Opts), + E0#exometer_entry{options = Opts1}; + [_|_] = Missing -> + error({required, Missing}) + end; +lookup_definition_(Name, Type, Opts) -> + E = case ets:prev(?EXOMETER_SHARED, {default, Type, <<>>}) of + {default, Type, N} = D0 when N==[''], N==Name -> + case ets:lookup(?EXOMETER_SHARED, D0) of + [#exometer_entry{} = Def] -> + Def; + [] -> + default_definition_(Name, Type) + end; + {default, OtherType, _} when Type=/=OtherType -> + exometer_default(Name, Type); + '$end_of_table' -> + exometer_default(Name, Type); + _ -> + default_definition_(Name, Type) + end, + merge_opts(Opts, E). + +merge_opts(Opts, #exometer_entry{options = DefOpts} = E) -> + Opts1 = lists:foldl(fun({'--', Keys}, Acc) -> + case is_list(Keys) of + true -> + lists:foldl( + fun(K,Acc1) -> + lists:keydelete(K, 1, Acc1) + end, Acc, Keys); + false -> + error({invalid_option,'--'}) + end; + ({K,V}, Acc) -> + lists:keystore(K, 1, Acc, {K,V}) + end, DefOpts, Opts), + E#exometer_entry{options = Opts1}. + +check_aliases(#exometer_entry{name = N, options = Opts} = E) -> + case lists:keyfind(aliases, 1, Opts) of + {_, Aliases} -> + exometer_alias:check_map([{N, Aliases}]); + _ -> + ok + end, + E. + +default_definition_(Name, Type) -> + case search_default(Name, Type) of + #exometer_entry{} = E -> + E#exometer_entry{name = Name}; + false -> + exometer_default(Name, Type) + end. + +exometer_default(Name, Type) -> + #exometer_entry{name = Name, type = Type, module = module(Type)}. + +module(counter ) -> exometer; +module(gauge) -> exometer; +module(fast_counter) -> exometer; +module(uniform) -> exometer_uniform; +module(duration) -> exometer_duration; +module(histogram) -> exometer_histogram; +module(spiral ) -> exometer_spiral; +module(netlink ) -> exometer_netlink; +module(cpu ) -> exometer_cpu; +module(function ) -> exometer_function; +module(meter ) -> exometer_folsom. + +search_default(Name, Type) -> + case ets:lookup(?EXOMETER_SHARED, {default,Type,Name}) of + [] -> + case ets:select_reverse( + ?EXOMETER_SHARED, make_patterns(Type, Name), 1) of + {[#exometer_entry{} = E],_} -> + E#exometer_entry{name = Name}; + '$end_of_table' -> + false + end; + [#exometer_entry{} = E] -> + E + end. + +sort_defaults(L) -> + lists:sort(fun comp_templates/2, + [E || #exometer_entry{type = T} = E <- L, + T =/= function andalso T =/= cpu]). + +comp_templates(#exometer_entry{name = {default, _, A}, type = Ta}, + #exometer_entry{name = {default, _, B}, type = Tb}) -> + comp_names(A, B, Ta, Tb). + + +comp_names([H |A], [H |B], Ta, Tb) -> comp_names(A, B, Ta, Tb); +comp_names([''|_], [_ |_], _, _) -> false; +comp_names([_ |_], [''|_], _, _) -> true; +comp_names([], [_ |_], _, _) -> false; +comp_names([_ |_], [] , _, _) -> true; +comp_names([], [] , A, B) -> comp_types(A, B). + +comp_types(histogram, _) -> true; +comp_types(counter, B) when B=/=histogram -> true; +comp_types(gauge , B) when B=/=histogram, B=/=counter -> true; +comp_types(spiral , B) when B=/=histogram, B=/=counter, B=/=gauge -> true; +comp_types(A, B) -> A =< B. + +-spec find_auto_template(exometer:name()) -> #exometer_entry{} | false. +%% @doc Convenience function for testing which template will apply to +%% `Name'. See {@link set_default/2} and {@link exometer:update_or_create/2}. +%% @end +find_auto_template(Name) -> + case sort_defaults(ets:select(?EXOMETER_SHARED, + make_patterns('_', Name))) of + [] -> false; + [#exometer_entry{name = {default,_,['']}}|_] -> false; + [#exometer_entry{} = E|_] -> E + end. + +make_patterns(Type, Name) when is_list(Name) -> + Prefixes = prefixes(Name), + [{ #exometer_entry{name = {default,Type,[V || {_,V} <- Pfx]}, _ = '_'}, + [{'or',{'=:=',V,X},{'=:=',V,''}} || {X,V} <- Pfx], ['$_'] } + || Pfx <- Prefixes]. + +prefixes(L) -> + Vars = vars(), + prefixes(L,Vars,[],[]). + +vars() -> + ['$1','$2','$3','$4','$5','$6','$7','$8','$9', + '$10','$11','$12','$13','$14','$15','$16']. + +prefixes([H|T],[V|Vs],Acc,Ps) -> + P1 = [{H,V}|Acc], + prefixes(T,Vs,P1,[lists:reverse(P1)|Ps]); +prefixes([],_,_,Ps) -> + Ps. + +%% make_patterns([H|T], Type, Acc) -> +%% Acc1 = Acc ++ [H], +%% ID = Acc1 ++ [''], +%% [{ #exometer_entry{name = {default, Type, ID}, _ = '_'}, [], ['$_'] } +%% | make_patterns(T, Type, Acc1)]; +%% make_patterns([], Type, _) -> +%% [{ #exometer_entry{name = {default, Type, ['']}, _ = '_'}, [], ['$_'] }]. + + +%% This function is called when creating an #exometer_entry{} record. +%% All options are passed unchanged to the callback module, but some +%% are acted upon by the framework: namely 'cache' and 'status'. +process_opts(Entry, Options) -> + lists:foldr( + fun + %% Some future exometer_entry-level option + %% ({something, Val}, Entry1) -> + %% Entry1#exometer_entry { something = Val }; + %% Unknown option, pass on to exometer entry options list, replacing + %% any earlier versions of the same option. + ({cache, Val}, E) -> + if is_integer(Val), Val >= 0 -> + E#exometer_entry{cache = Val}; + true -> + error({illegal, {cache, Val}}) + end; + ({status, Status}, #exometer_entry{} = E) -> + if Status==enabled; Status==disabled -> + Status1 = exometer_util:set_status( + Status, Entry#exometer_entry.status), + E#exometer_entry{status = Status1}; + true -> + error({illegal, {status, Status}}) + end; + ({update_event, UE}, #exometer_entry{} = E) when is_boolean(UE) -> + if UE -> + Status = exometer_util:set_event_flag( + update, E#exometer_entry.status), + E#exometer_entry{status = Status}; + true -> + Status = exometer_util:clear_event_flag( + update, E#exometer_entry.status), + E#exometer_entry{status = Status} + end; + ({_Opt, _Val}, #exometer_entry{} = Entry1) -> + Entry1 + end, Entry#exometer_entry{options = Options}, Options). + +try_disable_entry_(Name) when is_list(Name) -> + try exometer:setopts(Name, [{status, disabled}]) + catch + error:Err -> + ?log(debug, "Couldn't disable ~p: ~p~n", [Name, Err]), + try_delete_entry_(Name) + end; +try_disable_entry_(_Name) -> + ok. + +try_delete_entry_(Name) -> + try delete_entry_(Name) + catch + error:R -> + ?log(debug, "Couldn't delete ~p: ~p~n", [Name, R]), + ok + end. + +delete_entry_(Name) -> + exometer_cache:delete_name(Name), + case ets:lookup(exometer_util:table(), Name) of + [#exometer_entry{module = exometer, type = Type}] when Type==counter; + Type==gauge -> + [ets:delete(T, Name) || + T <- [?EXOMETER_ENTRIES|exometer_util:tables()]], + ok; + [#exometer_entry{module = exometer, type = fast_counter, + ref = {M, F}}] -> + try + exometer_util:set_call_count(M, F, false) + after + [ets:delete(T, Name) || + T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] + end, + ok; + [#exometer_entry{behaviour = probe, + type = Type, ref = Ref}] -> + try + exometer_probe:delete(Name, Type, Ref) + after + [ets:delete(T, Name) || + T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] + end, + ok; + [#exometer_entry{module= Mod, behaviour = entry, + type = Type, ref = Ref}] -> + try Mod:delete(Name, Type, Ref) + after + [ets:delete(T, Name) || + T <- [?EXOMETER_ENTRIES|exometer_util:tables()]] + end, + ok; + [] -> + {error, not_found} + end. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_alias.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_alias.erl new file mode 100644 index 0000000..bb15071 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_alias.erl @@ -0,0 +1,679 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +%% @doc API and behaviour for metrics instances +%% This module implements an alias registry for exometer metrics. +%% An alias can be either an atom or a binary, and maps to an +%% entry+datapoint pair. The registry is an ordered set with binary keys, +%% enabling straight lookup, prefix match/fold and regexp fold. +%% +%% The purpose of the registry is to support mapping of 'legacy names' +%% to exometer metrics, where the legacy names don't conform to the +%% exometer naming standard. +%% @end +-module(exometer_alias). +-behaviour(gen_server). + +-export([new/3, + load/1, + unload/1, + check_map/1, + delete/1, + update/2, + resolve/1, + reverse_map/2, + get_value/1, + prefix_match/1, + prefix_foldl/3, + prefix_foldr/3, + regexp_foldl/3, + regexp_foldr/3]). + +-export([start_link/0, + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-define(TAB, ?MODULE). +-define(COMPILED_RE(P), is_tuple(P), element(1, P) == re_pattern). + +-record(alias, {key, alias, entry, dp}). +-record(st, {}). + +-type alias() :: atom() | binary(). +-type name() :: exometer:name(). +-type dp() :: exometer:datapoint(). +-type regexp() :: iodata() | re:mp(). +-type acc() :: any(). +-type fold_fun() :: fun((alias(), name(), dp(), acc()) -> acc()). +-type reason() :: any(). + +-type stat_map() :: [{name(), [{dp(), alias()}]}]. + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-spec new(alias(), name(), dp()) -> ok | {error, reason()}. +%% @doc Create a new alias. +%% +%% This function maps an alias to an entry name and datapoint. +%% Each alias maps to exactly one entry+datapoint pair. The entry does +%% not need to exist when the alias is registered. +%% +%% The function raises an exception if the arguments are of the wrong +%% type, and returns `{error, exists}' if the alias has already been +%% registered. +%% @end +new(Alias, Entry, DP) -> + case valid_arg(Alias, Entry, DP) of + true -> + gen_server:call(?MODULE, {new, Alias, Entry, DP}); + false -> + {error, invalid} + end. + +-spec load(fun(() -> stat_map())) -> ok. +%% @doc Load a list of mappings between entry+datapoint pairs and aliases. +%% +%% This operation will overwrite any aliases with the same name that +%% already exist. The argument is a fun (zero arity) that returns a list of +%% `{EntryName, [{DataPoint, Alias}]}' tuples. +%% @end +load(Fun) when is_function(Fun, 0) -> + gen_server:call(?MODULE, {load, Fun}). + +-spec unload(fun(() -> stat_map())) -> ok. +%% @doc Unload a list of mappings. +%% +%% A mapping will only be deleted if the given alias+entry+datapoint matches +%% what is in the registry. The argument is of the same type as for +%% {@link load/1}. +%% @end +unload(Fun) when is_function(Fun, 0) -> + gen_server:call(?MODULE, {unload, Fun}). + +-spec delete(alias()) -> ok. +%% @doc Delete an alias, if it exists in the registry. +%% +%% This function will delete an alias if it exists in the registry. It will +%% return `ok' signaling that after completion, the alias is no longer in +%% the registry. +%% @end +delete(Alias) -> + gen_server:call(?MODULE, {delete, Alias}). + +-spec resolve(alias()) -> {name(), dp()} | error. +%% @doc Look up an alias in the registry and return corresponding mapping. +%% +%% This function returns `{EntryName, Datapoint}' corresponding to the given +%% alias, or `error' if no corresponding mapping exists. +%% @end +resolve(Alias) -> + Key = to_key(Alias), + case ets_lookup(Key) of + [#alias{entry = Entry, dp = DP}] -> + {Entry, DP}; + [] -> + error + end. + +-spec reverse_map(name() | '_', dp() | '_') -> [{alias(),name(),dp()}]. +%% @doc List all aliases mapped to the given entry+datapoint pair(s). +%% +%% Match spec-style wildcards can be used for `Name' and/or `Datapoint'. +%% @end +reverse_map(Name, Datapoint) -> + ets:select( + ?TAB, [{#alias{entry = Name, dp = Datapoint, _ = '_'}, [], + [{{{element, #alias.alias, '$_'}, + {element, #alias.entry, '$_'}, + {element, #alias.dp, '$_'}}}]}]). + +-spec get_value(alias()) -> {ok, any()} | {error, any()}. +%% @doc Resolve the given alias and return corresponding metric and value. +%% +%% The function returns `{ok, Value}' or `{error, not_found}' depending on +%% whether there is a 'live' mapping (i.e. the entry refered to by the alias +%% also exists.) +%% @end +get_value(Alias) -> + case resolve(Alias) of + {Entry, DP} -> + case exometer:get_value(Entry, [DP]) of + {ok, [{_, Value}]} -> + {ok, Value}; + Error -> + Error + end; + error -> + {error, not_found} + end. + +-spec update(alias(), any()) -> ok | {error, any()}. +%% @doc Resolves the given alias and updates the corresponding entry (if any). +%% +%% This function can be seen as a wrapper to {@link exometer:update/2}. +%% Although the alias maps to a given datapoint, the entry itself is updated, +%% so any alias mapping to the same entry can be used with the same result. +%% @end +update(Alias, Value) -> + case resolve(Alias) of + {Entry, _} -> + exometer:update(Entry, Value); + error -> + {error, not_found} + end. + +-spec prefix_match(binary()) -> [{alias(), name(), dp()}]. +%% @doc List all aliases matching the given prefix. +%% +%% Even if the alias is an atom, prefix matching will be performed. +%% Note that the referenced entries may not yet be created. +%% @end +prefix_match(Pattern) when is_binary(Pattern) -> + prefix_foldr(Pattern, fun just_acc/4, []). + +-spec prefix_foldl(binary(), fold_fun(), acc()) -> acc(). +%% @doc Fold (ascending order) over the aliases matching `Prefix'. +%% +%% The fold function is called with `F(Alias, Entry, Datapoint)'. +%% Note that the referenced entry may not yet be created. +%% @end +prefix_foldl(Prefix, F, Acc) -> + case ets_lookup(Prefix) of + [] -> + prefix_foldl(ets_next(Prefix), Prefix, byte_size(Prefix), + F, Acc); + [#alias{key = Key}] -> + prefix_foldl(Key, Prefix, byte_size(Prefix), F, Acc) + end. + +prefix_foldl('$end_of_table', _, _, _, Acc) -> + Acc; +prefix_foldl(Key, Pattern, Sz, F, Acc) -> + case Key of + <> -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + prefix_foldl(ets_next(Key), + Pattern, Sz, F, + F(Alias, E, DP, Acc)); + _ -> + prefix_foldl(ets_next(Key), Pattern, Sz, F, Acc) + end; + _ -> + Acc + end. + +-spec prefix_foldr(binary(), fold_fun(), acc()) -> acc(). +%% @doc Fold (descending order) over the aliases matching `Prefix'. +%% +%% The fold function is called with `F(Alias, Entry, Datapoint)'. +%% Note that the referenced entry may not yet be created. +%% @end +prefix_foldr(Pattern, F, Acc) -> + case ets_lookup(Pattern) of + [] -> + prefix_foldr(ets_next(Pattern), Pattern, byte_size(Pattern), + F, Acc); + [#alias{key = Key}] -> + prefix_foldr(Key, Pattern, byte_size(Pattern), F, Acc) + end. + +prefix_foldr('$end_of_table', _, _, _, Acc) -> + Acc; +prefix_foldr(Key, Pattern, Sz, F, Acc) -> + case Key of + <> -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + F(Alias, E, DP, prefix_foldr(ets_next(Key), + Pattern, Sz, F, Acc)); + _ -> + prefix_foldr(ets_next(Key), Pattern, Sz, F, Acc) + end; + _ -> + Acc + end. + +-spec regexp_foldl(regexp(), fold_fun(), acc()) -> acc(). +%% @doc Fold (ascending order) over the aliases matching `Regexp'. +%% +%% The fold function is called with `F(Alias, Entry, Datapoint)'. +%% Note that the referenced entry may not yet be created. +%% +%% In order to avoid scanning the whole registry, a prefix is extracted +%% from the regular expression. For a non-empty prefix, make sure to anchor +%% the regular expression to the beginning of the name (e.g. `"^my_stats.*"'). +%% @end +regexp_foldl(Regexp, F, Acc) when ?COMPILED_RE(Regexp) -> + regexp_foldl(ets_first(), <<>>, 0, Regexp, F, Acc); +regexp_foldl(Regexp, F, Acc) -> + Prefix = regexp_prefix(Regexp), + case ets_lookup(Prefix) of + [] -> + regexp_foldl(ets_next(Prefix), Prefix, byte_size(Prefix), + re_compile(Regexp), F, Acc); + [#alias{key = Key}] -> + regexp_foldl(Key, Prefix, byte_size(Prefix), + re_compile(Regexp), F, Acc) + end. + +regexp_foldl('$end_of_table', _, _, _, _, Acc) -> + Acc; +regexp_foldl(Key, Prefix, Sz, Pattern, F, Acc) -> + case Key of + <> -> + case re:run(Key, Pattern) of + {match, _} -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + regexp_foldl(ets_next(Key), Prefix, Sz, + Pattern, F, F(Alias, E, DP, Acc)); + _ -> + regexp_foldl(ets_next(Key), + Prefix, Sz, Pattern, F, Acc) + end; + nomatch -> + regexp_foldl(ets_next(Key), + Prefix, Sz, Pattern, F, Acc) + end; + _ -> + Acc + end. + +-spec regexp_foldr(regexp(), fold_fun(), acc()) -> acc(). +%% @doc Fold (descending order) over the aliases matching `Regexp'. +%% +%% The fold function is called with `F(Alias, Entry, Datapoint)'. +%% Note that the referenced entry may not yet be created. +%% +%% In order to avoid scanning the whole registry, a prefix is extracted +%% from the regular expression. For a non-empty prefix, make sure to anchor +%% the regular expression to the beginning of the name (e.g. `"^my_stats.*"'). +%% @end +regexp_foldr(Pattern, F, Acc) when ?COMPILED_RE(Pattern) -> + regexp_foldr(ets_first(), <<>>, 0, Pattern, F, Acc); +regexp_foldr(Pattern, F, Acc) -> + Prefix = regexp_prefix(Pattern), + case ets_lookup(Prefix) of + [] -> + regexp_foldr(ets_next(Prefix), Prefix, byte_size(Prefix), + re_compile(Pattern), + F, Acc); + [#alias{key = Key}] -> + regexp_foldr(Key, Prefix, byte_size(Prefix), + re_compile(Pattern), F, Acc) + end. + +regexp_foldr('$end_of_table', _, _, _, _, Acc) -> + Acc; +regexp_foldr(Key, Prefix, Sz, Pattern, F, Acc) -> + case Key of + <> -> + case re:run(Key, Pattern) of + {match, _} -> + case ets_lookup(Key) of + [#alias{alias = Alias, entry = E, dp = DP}] -> + F(Alias, E, DP, regexp_foldr(ets_next(Key), + Prefix, Sz, Pattern, + F, Acc)); + _ -> + regexp_foldr(ets_next(Key), Prefix, Sz, + Pattern, F, Acc) + end; + nomatch -> + regexp_foldr(ets_next(Key), Prefix, Sz, + Pattern, F, Acc) + end; + _ -> + Acc + end. + +just_acc(Alias, Entry, DP, Acc) -> + [{Alias, Entry, DP}|Acc]. + +start_link() -> + Tab = maybe_create_ets(), + case gen_server:start_link({local, ?MODULE}, ?MODULE, [], []) of + {ok, Pid} = Res -> + ets:give_away(Tab, Pid, give_away), + Res; + Other -> + Other + end. + +%% @private +init(_) -> + {ok, #st{}}. + +%% @private +handle_call({new, Alias, Entry, DP}, _, St) -> + Key = to_key(Alias), + Res = case ets:member(?TAB, Key) of + true -> + {error, exists}; + false -> + ets:insert(?TAB, #alias{key = Key, alias = Alias, + entry = Entry, dp = DP}), + ok + end, + {reply, Res, St}; +handle_call({load, F}, _, St) -> + Res = try do_load(F) + catch + error:R -> {error, R} + end, + {reply, Res, St}; +handle_call({unload, F}, _, St) -> + Res = try do_unload(F) + catch + error:R -> {error, R} + end, + {reply, Res, St}; +handle_call({delete, Alias}, _, St) -> + Key = to_key(Alias), + ets:delete(?TAB, Key), + {reply, ok, St}; +handle_call(_, _, St) -> + {error, badarg, St}. + +%% @private +handle_cast(_, St) -> + {noreply, St}. + +%% @private +handle_info(_, St) -> + {noreply, St}. + +%% @private +terminate(_, _) -> + ok. + +%% @private +code_change(_, St, _) -> + {ok, St}. + +%% Private + +maybe_create_ets() -> + case ets:info(?TAB, name) of + undefined -> + ets:new(?TAB, [ordered_set, named_table, public, + {keypos, #alias.key}, {heir, self(), failover}]); + _ -> + ?TAB + end. + +ets_lookup(Key) -> ets:lookup(?TAB, Key). +ets_first() -> ets:first(?TAB). +ets_next(Key) -> ets:next(?TAB, Key). + +to_key(A) when is_atom(A) -> + atom_to_binary(A, latin1); +to_key(A) when is_binary(A) -> + A. + +do_load(F) -> + Map = F(), + check_map(Map), + lists:foreach( + fun({Entry, DPs}) when is_list(Entry), is_list(DPs) -> + lists:foreach( + fun({DP, Alias}) when is_atom(DP), is_atom(Alias); + is_atom(DP), is_binary(Alias) -> + Key = to_key(Alias), + true = ets:insert_new(?TAB, #alias{key = Key, + alias = Alias, + entry = Entry, + dp = DP}) + end, DPs) + end, Map). + +check_map(Map) -> + case lists:foldl( + fun(F, {M1,Es}) -> + F(M1, Es) + end, {Map, []}, [fun check_args/2, + fun check_duplicates/2, + fun check_existing/2]) of + {Map1, []} -> + Map1; + {_, Errors} -> + error({map_error, Errors}) + end. + +check_args(Map, Es) -> + Check = deep_fold( + fun({Alias, Entry, DP}, D) -> + case valid_arg(Alias, Entry, DP) of + true -> D; + false -> + orddict:append(Alias, {Entry, DP}, D) + end; + (Other, D) -> + orddict:append(unrecognized, Other, D) + end, orddict:new(), Map), + maybe_add_errors(Check, invalid, Map, Es). + +check_duplicates(Map, Es) -> + Check = deep_fold( + fun({Alias, Entry, DP}, D) -> + dict:append(Alias, {Entry,DP}, D); + (_, D) -> + D + end, dict:new(), Map), + %% We have duplicates if the value of any dict item is a list of length > 1. + Dups = dict:fold(fun(K, [_,_|_] = V, Acc) -> [{K, V}|Acc]; + (_, _, Acc) -> Acc + end, [], Check), + maybe_add_errors(Dups, duplicate_aliases, Map, Es). + +check_existing(Map, Es) -> + Check = deep_fold( + fun({Alias, Entry, DP}, Acc) -> + %% Accept identical entries + case resolve(Alias) of + {Entry, DP} -> Acc; + error -> Acc; + {OtherEntry, OtherDP} -> + orddict:append(Alias, {OtherEntry, OtherDP}, Acc) + end; + (_, D) -> + D + end, orddict:new(), Map), + maybe_add_errors(Check, existing_aliases, Map, Es). + +valid_arg(Alias, Entry, DP) + when is_list(Entry), is_atom(DP), is_atom(Alias); + is_list(Entry), is_atom(DP), is_binary(Alias); + is_list(Entry), is_integer(DP), is_atom(Alias); + is_list(Entry), is_integer(DP), is_binary(Alias) -> true; +valid_arg(_, _, _) -> + false. + + +maybe_add_errors([], _, Map, Es) -> {Map, Es}; +maybe_add_errors([_|_] = NewErrors, Kind, Map, Es) -> + {Map, [{Kind, NewErrors}|Es]}. + +deep_fold(F, Acc, Map) -> + lists:foldl( + fun({Entry, DPs}, Acc1) -> + lists:foldl( + fun({DP, Alias}, Acc2) -> + F({Alias, Entry, DP}, Acc2) + end, Acc1, DPs); + (Other, Acc1) -> + %% Bad input, but let's pass it on to the check function + F(Other, Acc1) + end, Acc, Map). + +do_unload(F) -> + Map = F(), + lists:foreach( + fun({Entry, DPs}) when is_list(Entry), is_list(DPs) -> + lists:foreach( + fun({DP, Alias}) when is_atom(Alias); + is_binary(Alias) -> + Key = to_key(Alias), + ets:delete_object(?TAB, #alias{key = Key, + alias = Alias, + entry = Entry, + dp = DP}) + end, DPs) + end, Map). + +re_compile(R) -> + {ok, RC} = re:compile(R), + RC. + +regexp_prefix(Re) -> + regexp_prefix_(iolist_to_binary(Re)). + +regexp_prefix_(<<"^", R/binary>>) -> + regexp_prefix_(R, <<>>); +regexp_prefix_(_) -> + <<>>. + +regexp_prefix_(<>, Acc) when H==$[; H==$(; H==$.; H==$<; H==$\$ -> + Acc; +regexp_prefix_(<<"\\Q", T/binary>>, Acc) -> + [Head|Rest] = binary:split(T, <<"\\E">>), + regexp_prefix_(iolist_to_binary(Rest), <>); +regexp_prefix_(<>, Acc) -> + regexp_prefix_(T, <>); +regexp_prefix_(<<>>, Acc) -> + Acc. + +-ifdef(TEST). + +-define(Pfx, "exometer_alias"). + +alias_test_() -> + {setup, + fun() -> + exometer:start(), + create_entries(), + load_aliases(), + ets:tab2list(?TAB), + ok + end, + fun(_) -> application:stop(exometer) end, + [?_test(t_resolve()), + ?_test(t_reverse_map()), + ?_test(t_get_value()), + ?_test(t_prefix_match()), + ?_test(t_prefix_match2()), + ?_test(t_prefix_foldl()), + ?_test(t_regexp_foldl()), + ?_test(t_regexp_foldl2()), + ?_test(t_regexp_foldr()), + ?_test(t_unload()), + ?_test(t_new()), + ?_test(t_update()), + ?_test(t_delete())]}. + + +t_resolve() -> + ?assertMatch({[?Pfx,g,3], value}, resolve(<>)). + +t_reverse_map() -> + ?assertMatch([{<>, [?Pfx,g,5], value}], + reverse_map([?Pfx,g,5], value)), + ?assertMatch([{<>, [?Pfx,g,1], value}, + {<>, [?Pfx,g,10], value}, + {<>, [?Pfx,g,2], value}, + {<>, [?Pfx,g,3], value}, + {<>, [?Pfx,g,4], value}, + {<>, [?Pfx,g,5], value}, + {<>, [?Pfx,g,6], value}, + {<>, [?Pfx,g,7], value}, + {<>, [?Pfx,g,8], value}, + {<>, [?Pfx,g,9], value}], + reverse_map([?Pfx,g,'_'], value)), + ?assertMatch([{<>, [?Pfx,g,5], value}], + reverse_map([?Pfx,g,5], '_')). + + +t_get_value() -> + exometer:update([?Pfx,g,5], 3), + ?assertMatch({ok, 3}, get_value(<>)). + +t_prefix_match() -> + ?assertMatch( + [{<>, [?Pfx,g,1 ],value}, + {<>, [?Pfx,g,10],value}], + prefix_match(<>)). + +t_prefix_match2() -> + ?assertMatch([], prefix_match(<<"aaa">>)), + ?assertMatch([], prefix_match(<<"zzz">>)). + +t_prefix_foldl() -> + ?assertMatch( + [{<>, [?Pfx,g,10],value}, + {<>, [?Pfx,g,1 ],value}], + prefix_foldl(<>, + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + +t_regexp_foldl() -> + ?assertMatch( + [{<>,[?Pfx,g,5],value}, + {<>,[?Pfx,g,4],value}, + {<>,[?Pfx,g,3],value}], + regexp_foldl(<<"^",?Pfx,"_g_[345]$">>, + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + +t_regexp_foldl2() -> + ?assertMatch([], regexp_foldl(<<"^",?Pfx,"_g_[ab]$">>, + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + +t_regexp_foldr() -> + ?assertMatch( + [{<>,[?Pfx,g,3],value}, + {<>,[?Pfx,g,4],value}, + {<>,[?Pfx,g,5],value}], + regexp_foldr(<<"^",?Pfx,"_g_[345]$">>, + fun(A,E,D,Acc) -> [{A,E,D}|Acc] end, [])). + +t_unload() -> + ok = unload(fun test_aliases/0), + [] = ets:tab2list(?TAB). + +t_new() -> + ok = new(my_g_1, [?Pfx,g,1], value), + {ok, 0} = get_value(my_g_1). + +t_update() -> + ok = update(my_g_1, 3), + {ok, 3} = get_value(my_g_1). + +t_delete() -> + ok = delete(my_g_1), + {error, not_found} = get_value(my_g_1). + +create_entries() -> + [exometer:new([?Pfx,g,N], gauge) || N <- lists:seq(1,10)]. + +load_aliases() -> + load(fun test_aliases/0). + +test_aliases() -> + [{[?Pfx,g,N], [{value, iolist_to_binary([?Pfx, "_g_", + integer_to_list(N)])}]} + || N <- lists:seq(1,10)]. + +-endif. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_cache.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_cache.erl new file mode 100644 index 0000000..59cf86a --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_cache.erl @@ -0,0 +1,151 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-module(exometer_cache). +-behaviour(gen_server). + +-export([start_link/0]). + +-export([read/2, %% (Name, DataPoint) -> {ok, Value} | not_found + write/3, %% (Name, DataPoint, Value) -> ok + write/4, %% (Name, DataPoint, Value, TTL) -> ok + delete/2, + delete_name/1 + ]). + +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-define(TABLE, ?MODULE). + +-record(st, {ttl = 5000, waiters = [], workers = []}). + +-record(cache, {name, value, tref, time, ttl}). + +start_link() -> + ensure_table(), + gen_server:start_link({local,?MODULE}, ?MODULE, [], []). + +read(Name, DataPoint) -> + case ets:lookup(?TABLE, path(Name, DataPoint)) of + [#cache{value = Val}] -> + {ok, Val}; + _ -> + not_found + end. + +write(Name, DataPoint, Value) -> + write(Name, Value, DataPoint, undefined). + +write(Name, DataPoint, Value, TTL) -> + Path = path(Name, DataPoint), + try OldTRef = ets:lookup_element(?TABLE, Path, #cache.tref), + erlang:cancel_timer(OldTRef) + catch error:_ -> ok + end, + TS = os:timestamp(), + start_timer(Path, TTL, TS), + ets:insert(?TABLE, #cache{name = Path, value = Value, ttl = TTL, + time = TS}), + ok. + +delete(Name, DataPoint) -> + %% Cancel the timer? + ets:delete(?TABLE, path(Name, DataPoint)). + +delete_name(Name) -> + %% This function currently doesn't cancel any timers. The cost of doing + %% so would outweigh the cost of reacting to any timers that happen to + %% fire (timer-based cleanup matches on TRef, so will not accidentally + %% delete the wrong entries.) + ets:match_delete(?TABLE, #cache{name = {Name,'_'}, _ = '_'}), + ok. + +start_timer(Name, TTL, TS) -> + gen_server:cast(?MODULE, {start_timer, Name, TTL, TS}). + +init(_) -> + S = #st{}, + restart_timers(S#st.ttl), + {ok, #st{}}. + +handle_call(_, _, S) -> + {reply, error, S}. + +handle_cast({start_timer, Name, TTLu, T}, #st{ttl = TTL0} = S) -> + TTL = if TTLu == undefined -> TTL0; + is_integer(TTLu) -> TTLu + end, + Timeout = timeout(T, TTL), + TRef = erlang:start_timer(Timeout, self(), {name, Name}), + update_tref(Name, TRef), + {noreply, S}; + +handle_cast(_, S) -> + {noreply, S}. + +handle_info({timeout, Ref, {name, Name}}, S) -> + ets:select_delete( + ?TABLE, [{#cache{name = Name, tref = Ref, _='_'}, [], [true]}]), + {noreply, S}; + +handle_info(_, S) -> + {noreply, S}. + +terminate(_, _) -> + ok. + +code_change(_, S, _) -> + {ok, S}. + +timeout(T, TTL) -> + timeout(T, TTL, os:timestamp()). + +timeout(T, TTL, TS) -> + erlang:max(TTL - (timer:now_diff(TS, T) div 1000), 0). + +update_tref(Name, TRef) -> + try ets:update_element(?TABLE, Name, {#cache.tref, TRef}) + catch _ -> ok end. + + +ensure_table() -> + case ets:info(?TABLE, name) of + undefined -> + ets:new(?TABLE, [set, public, named_table, {keypos, 2}]); + _ -> + true + end. + +restart_timers(TTL) -> + exometer_util:seed(), + restart_timers( + ets:select( + ?TABLE, [{#cache{name = '$1', ttl = '$2', time = '$3', _='_'}, + [],[{{'$1','$2','$3'}}]}], 100), + TTL, os:timestamp()). + +restart_timers({Names, Cont}, TTL, TS) -> + lists:foreach( + fun({Name1, TTL1, T1}) -> + Timeout = timeout(T1, TTL1, TS), + TRef = erlang:start_timer(Timeout, self(), {name, Name1}), + ets:update_element(?TABLE, Name1, {#cache.tref, TRef}) + end, Names), + restart_timers(ets:select(Cont), TTL, TS); +restart_timers('$end_of_table', _, _) -> + ok. + +path( Name, DataPoint) -> + { Name, DataPoint }. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_core.app.src b/ptrans_deporder/_checkouts/exometer_core/src/exometer_core.app.src new file mode 100644 index 0000000..81499af --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_core.app.src @@ -0,0 +1,11 @@ +{application,exometer_core, + [{description,"Code instrumentation and metrics collection package."}, + {vsn,"1.5.2"}, + {applications,[kernel,stdlib,hut,setup,folsom]}, + {mod,{exometer_core_app,[]}}, + {start_phases,[{start_reporters,[]},{preset_defaults,[]}]}, + {env,[]}, + {maintainers,["Feuerlabs","Heinz N. Gies"]}, + {licenses,["MPL"]}, + {links,[{"Github", + "https://github.com/Feuerlabs/exometer_core"}]}]}. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_core_app.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_core_app.erl new file mode 100644 index 0000000..f63f04e --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_core_app.erl @@ -0,0 +1,35 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +%% @private +-module(exometer_core_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, + stop/1, + start_phase/3]). + +%% =================================================================== +%% Application callbacks +%% =================================================================== + +start(_StartType, _StartArgs) -> + exometer_core_sup:start_link(). + +start_phase(preset_defaults, _Type, []) -> + exometer_admin:preset_defaults(); +start_phase(start_reporters, _Type, []) -> + exometer_report:start_reporters(). + + +stop(_State) -> + ok. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_core_sup.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_core_sup.erl new file mode 100644 index 0000000..4405c2c --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_core_sup.erl @@ -0,0 +1,45 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014-15 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +%% @private +-module(exometer_core_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +%% Helper macro for declaring children of supervisor +-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). + +%% =================================================================== +%% API functions +%% =================================================================== + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% =================================================================== +%% Supervisor callbacks +%% =================================================================== + +init([]) -> + Children0 = [ + ?CHILD(exometer_admin, worker), + ?CHILD(exometer_cache, worker), + ?CHILD(exometer_report, worker), + ?CHILD(exometer_report_logger_sup, supervisor), + ?CHILD(exometer_folsom_monitor, worker), + ?CHILD(exometer_alias, worker) + ], + {ok, {{one_for_one, 5, 10}, Children0}}. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_cpu.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_cpu.erl new file mode 100644 index 0000000..2f9001b --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_cpu.erl @@ -0,0 +1,94 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +-module(exometer_cpu). + +-behaviour(exometer_probe). + +%% exometer_entry callbacks +%% exometer_probe callbacks +-export( + [ + behaviour/0, + probe_init/3, + probe_terminate/1, + probe_get_value/2, + probe_get_datapoints/1, + probe_update/2, + probe_reset/1, + probe_sample/1, + probe_setopts/3, + probe_handle_msg/2, + probe_code_change/3 + ]). + +-include("exometer.hrl"). + +-define(DATAPOINTS, [nprocs, avg1, avg5, avg15]). + +-record(st, { + datapoints, + data, + ref + }). + +-spec behaviour() -> exometer:behaviour(). +behaviour() -> + probe. + +probe_init(_, _, Opts) -> + DP = proplists:get_value(datapoints, Opts, ?DATAPOINTS), + {ok, #st{datapoints = DP}}. + +probe_terminate(_) -> ok. + +probe_get_value(DPs, #st{data = Data0, + datapoints = DPs0} = S) -> + Data1 = if Data0 == undefined -> sample(DPs0); + true -> Data0 + end, + DPs1 = if DPs == default -> DPs0; + true -> DPs + end, + {ok, probe_get_value_(Data1, DPs1), S#st{data = Data1}}. + +probe_get_value_(Data, DPs) -> + [D || {K,_} = D <- Data, + lists:member(K, DPs)]. + +probe_get_datapoints(#st{datapoints = DPs}) -> + {ok, DPs}. + +probe_update(_, _) -> + {error, not_supported}. + +probe_reset(S) -> + {ok, S#st{data = []}}. + +probe_sample(#st{datapoints = DPs} = S) -> + {_Pid, Ref} = spawn_monitor( + fun() -> + exit({sample, sample(DPs)}) + end), + {ok, S#st{ref = Ref}}. + +probe_setopts(_Entry, Opts, S) -> + DPs = proplists:get_value(datapoints, Opts, S#st.datapoints), + {ok, S#st{datapoints = DPs}}. + +probe_handle_msg({'DOWN', Ref, _, _, {sample,Data}}, #st{ref = Ref} = S) -> + {ok, S#st{ref = undefined, data = Data}}; + +probe_handle_msg(_, S) -> + {ok, S}. + +probe_code_change(_, S, _) -> {ok, S}. + +sample(DPs) -> + [{F, cpu_sup:F()} || F <- DPs, lists:member(F, ?DATAPOINTS)]. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_duration.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_duration.erl new file mode 100644 index 0000000..097995a --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_duration.erl @@ -0,0 +1,143 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-module(exometer_duration). +-behaviour(exometer_probe). + +%% exometer_entry callbacks +-export([new/3, + delete/3, + get_value/3, + get_value/4, + get_datapoints/3, + setopts/3, + update/4, + reset/3, + sample/3]). + +%% exometer_probe callbacks +-export([behaviour/0, + probe_init/3, + probe_terminate/1, + probe_get_value/2, + probe_update/2, + probe_reset/1, + probe_sample/1, + probe_setopts/3, + probe_get_datapoints/1, + probe_handle_msg/2, + probe_code_change/3]). + +-include("exometer.hrl"). +-import(netlink_stat, [get_value/1]). +-record(st, {name, + t_start, + count = 0, + last = 0, + histogram, + opts = []}). + + +-define(DATAPOINTS, [count, last]). + +behaviour() -> + entry. + +%% exometer_entry callbacks +new(Name, Type, Options) -> + exometer_probe:new(Name, Type, [{arg, ?MODULE}|Options]). + +delete(Name, Type, Ref) -> + exometer_probe:delete(Name, Type, Ref). + +get_value(Name, Type, Ref) -> + exometer_probe:get_value(Name, Type, Ref). + +get_value(Name, Type, Ref, DataPoints) -> + exometer_probe:get_value(Name, Type, Ref, DataPoints). + +get_datapoints(Name, Type, Ref) -> + exometer_probe:get_datapoints(Name, Type, Ref). + +setopts(Entry, Opts, Status) -> + exometer_probe:setopts(Entry, Opts, Status). + +update(Name, timer_start, Type, Ref) -> + exometer_probe:update(Name, {timer_start, os:timestamp()}, Type, Ref); +update(Name, timer_end, Type, Ref) -> + exometer_probe:update(Name, {timer_end, os:timestamp()}, Type, Ref); +update(_, _, _, _) -> + {error, badarg}. + +reset(Name, Type, Ref) -> + exometer_probe:reset(Name, Type, Ref). + +sample(_, _, _) -> + {error, unsupported}. + +%% exometer_probe callbacks + +probe_init(Name, Type, Options) -> + {ok, H} = exometer_histogram:probe_init(Name, Type, Options), + {ok, #st{histogram = H, opts = Options}}. + +probe_terminate(_ModSt) -> + ok. + +%% Not used +probe_get_datapoints(#st{histogram = H}) -> + {ok, HDPs} = exometer_histogram:probe_get_datapoints(H), + {ok, ?DATAPOINTS ++ HDPs}. + +probe_get_value(DataPoints, #st{histogram = H} = St) -> + case DataPoints -- ?DATAPOINTS of + [] -> + {ok, fill_datapoints(DataPoints, [], St)}; + HDPs -> + {ok, HVals} = exometer_histogram:probe_get_value(HDPs, H), + {ok, fill_datapoints(DataPoints, HVals, St)} + end. + +probe_setopts(_Entry, _Opts, _St) -> + ok. + +probe_update({timer_start, T}, St) -> + {ok, St#st{t_start = T}}; +probe_update({timer_end, T}, #st{histogram = H, count = C} = St) -> + try + Duration = timer:now_diff(T, St#st.t_start), + {ok, H1} = exometer_histogram:probe_update(Duration, H), + {ok, St#st{histogram = H1, count = C+1, last = Duration}} + catch + error:_ -> + {ok, St} + end. + +probe_reset(#st{histogram = H} = St) -> + {ok, H1} = exometer_histogram:probe_reset(H), + {ok, St#st{histogram = H1, count = 0, t_start = undefined, last = 0}}. + +probe_sample(St) -> + {ok, St}. + +probe_code_change(_From, ModSt, _Extra) -> + {ok, ModSt}. + +probe_handle_msg(_, S) -> + {ok, S}. + +fill_datapoints([D|DPs], [{D,_} = V|Vals], St) -> + [V|fill_datapoints(DPs, Vals, St)]; +fill_datapoints([count|DPs], Vals, #st{count = C} = St) -> + [{count, C}|fill_datapoints(DPs, Vals, St)]; +fill_datapoints([last|DPs], Vals, #st{last = Last} = St) -> + [{last, Last}|fill_datapoints(DPs, Vals, St)]; +fill_datapoints([], [], _) -> + []. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_entry.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_entry.erl new file mode 100644 index 0000000..615555e --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_entry.erl @@ -0,0 +1,307 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @doc +%% == Creating custom exometer entries == +%% +%% An exometer_entry behavior implementation can be created when custom +%% processing of various metrics is needed. +%% +%% A custom exometer entry is invoked by mapping a type to the module +%% name of the custom exometer entry module. All metrics created with the +%% given type will trigger the invocation of the new entry module. See +%% {@section Configuring type - entry maps} for details on how to setup +%% such maps. +%% +%% The life cycle of a an exometer entry consists of the following steps. +%% +%% + Metrics Creation +%%
`new/3' is invoked by exometer to signal that a new metrics +%% should be created. The name of the new metric will be provided as +%% a list of atoms. +%% +%% + Update Data +%%
Values will be sent to the entry through the `update/4' +%% function. The custom entry should store this value for the given +%% metric and break it down into data points that can be reported for +%% the metric. +%% +%% + Retrieve Value +%%
`get_value/4' will be invoked by exometer to retrieve specific +%% data points from a given metric. +%% +%% See individual functions for details on the +%% in the exometer_entry behavior. +%% +%% === behaviour/0 === +%% +%% The `behaviour()' function for an entry implementation should return +%% the atom `entry'. This function will be involved by the +%% exometer system in order to determine if a callback is +%% an entry or a probe. +%% +%% === new/3 === +%% +%% The `new()' function is invoked as follows: +%% +%%
+%%      new(Name, Type, Options)
+%% +%% The custom entry should create the necessary state for the new metric and store +%% it for furure access through `update()' and `get_value()' calls. +%% +%% + `Name' +%%
Specifies the name of the metric to be created as a list of atoms. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). It can be used if several +%% different types are mapped to the same entry module. +%% +%% + `Options' +%%
Specifies an option list that contains additional setup directives to +%% the entry. The actual options to support are implementation dependent. +%% +%% The `new()' function should return `{ok, Ref}' where Ref is a +%% tuple that will be provided as a reference argument to other calls +%% made into the module. Any other return formats will cancel the +%% creation of the new metric. +%% +%% === delete/3 === +%% +%% The `delete()' function is invoked as follows: +%% +%%
+%%      delete(Name, Type, Ref)
+%% +%% The custom entry should free all resources associated with the given name. +%% +%% + `Name' +%%
Specifies the name of the metric to be deleted as a list of atoms. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). +%% +%% + `Ref' +%%
Will contain the same tuple returned as `Ref' by the module's `new()' function. +%% +%% The `delete()' function shall return `ok'. +%% +%% +%% === get_value/4 === +%% +%% The `get_value()' function is invoked as follows: +%% +%%
+%%      get_value(Name, Type, Ref, DataPoints)
+%% +%% The custom entry should retrieve the metric with the given name and +%% return the values of the specified data points. Data points can be +%% expected to be one or more of those returned by the entry's +%% `get_datapoints()' function. +%% +%% + `Name' +%%
Specifies the name of the metric to update with a value. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). +%% +%% + `Ref' +%%
Will contain the same tuple returned as `Ref' by the module's `new()' function. +%% +%% + `DataPoints' +%%
Will contain a list of data points, each picked from the list returned by +%% the module's `get_datapoints()' function. +%% +%% The `get_value()' function should calculate the values of the given +%% data points based on previous calls to `update()' and return them to the caller. +%% +%% The return format shall be: +%% +%%
+%%      {ok, [ { DataPoint, Value }, ...]}
+%% +%% Each `{ DataPoint, Value }' tuple shall contain the name and value of +%% one of the data points provided as arguments to `get_value()'. +%% +%% If a data point is not valid (i.e. not in the list returned by +%% `get_datapoints()'), the returned tuple should be `{ DataPoint, +%% undefined }'. +%% +%% +%% === update/4 === +%% +%% The `update()' function is invoked as follows: +%% +%%
+%%      update(Name, Value, Type, Ref)
+%% +%% + `Name' +%%
Specifies the name of the metric to update. +%% +%% + `Value' +%%
Specifies the new value to integrate into the given metric. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). +%% +%% + `Ref' +%%
Will contain the same tuple returned as `Ref' by the module's `new()' function. +%% +%% The `update()' function should update the data points for the metric with the +%% given name in preparation for future calls to `get_value()'. +%% +%% The return format shall be `ok'. +%% +%% +%% === reset/3 === +%% +%% The `reset()' function is invoked as follows: +%% +%%
+%%      reset(Name, Type, Ref)
+%% +%% + `Name' +%%
Specifies the name of the metric to reset. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). +%% +%% + `Ref' +%%
Will contain the same tuple returned as `Ref' by the module's `new()' function. +%% +%% The `reset()' function should revert the metric with the given name to +%% its original state. A counter, for example, should be reset to 0 while +%% histograms should be emptied. +%% +%% The return format shall be `ok'. +%% +%% === sample/3 === +%% +%% The `sample()' function is invoked as follows: +%% +%%
+%%      sample(Name, Type, Ref)
+%% +%% + `Name' +%%
Specifies the name of the metric to run the sample. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). +%% +%% + `Ref' +%%
Will contain the same tuple returned as `Ref' by the module's +%% `new()' function. +%% +%% This function is only used by probes, where it is periodically called +%% to sample a local sub system such as /proc or netlink in order to +%% update its data points. +%% +%% Any exometer entry-based implementation should do nothing and return +%% `ok'. +%% +%% === get_datapoints/3 === +%% +%% The `get_datapoints()' function is invoked as follows: +%% +%%
+%%      get_datapoints(Name, Type, Ref)
+%% +%% + `Name' +%%
Specifies the name of the metric to return available datapoints for. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). +%% +%% + `Ref' +%%
Will contain the same tuple returned as `Ref' by the module's +%% `new()' function. +%% +%% This function should return a list of all data points supported by +%% the exometer entry implementation. The returned data points shall +%% be supported by the module's `get_value()' function. +%% +%% +%% === setopts/3 === +%% +%% The `setopts()' function is invoked as follows: +%% +%%
+%%      setopts(Entry, Options, Type, Ref)
+%% +%% + `Entry' +%% The (opaque) exometer entry record. See {@link exometer_info} for +%% information on how to inspect the data structure. +%% +%% + `Options' +%%
Specifies an option list that contains additional setup directives to +%% the entry. The actual options to support are implementation dependent. +%% +%% + `Type' +%%
Specifies the type provided to the `exometer:new()' call (before it +%% was translated by the type - exometer entry map). +%% +%% + `Ref' +%%
Will contain the same tuple returned as `Ref' by the module's +%% `new()' function. +%% +%% This function should modify the behavior of the given metric by the +%% options provided in the `Options' property list. +%% +%% The function should return either `ok' or `{error, Reason}', where +%% `Reason' contins a descriptive reason for a failure to set one or more +%% options. +%% +%% @end +-module(exometer_entry). + +-include("exometer.hrl"). + +-export_type([name/0, type/0, options/0, datapoint/0, datapoints/0, value/0, ref/0, error/0]). + +-type name() :: list(). +-type type() :: atom(). +-type options() :: [{atom(), any()}]. +-type datapoints() :: [datapoint()]. +-type datapoint() :: exometer:datapoint(). +-type value() :: any(). +-type ref() :: any(). +-type error() :: {error, any()}. + +-callback behaviour() -> exometer:behaviour(). + +-callback new(name(), type(), options()) -> + ok | {ok, ref()} | error(). + +-callback delete(name(), type(), ref()) -> + ok | error(). + +-callback get_value(name(), type(), ref(), datapoints()) -> + [{datapoint(), value()}]. + +-callback update(name(), value(), type(), ref()) -> + ok | {ok, value()} | error(). + +-callback reset(name(), type(), ref()) -> + ok | {ok, value()} | error(). + +-callback sample(name(), type(), ref()) -> + ok | error(). + +-callback get_datapoints(name(), type(), ref()) -> + datapoints(). + +-callback setopts(exometer:entry(), options(), exometer:status()) -> + ok | error(). diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_folsom.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_folsom.erl new file mode 100644 index 0000000..5bffc26 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_folsom.erl @@ -0,0 +1,232 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-module(exometer_folsom). +-behaviour(exometer_entry). + +-export([behaviour/0, + new/3, + delete/3, + get_datapoints/3, + get_value/4, + update/4, + reset/3, + sample/3, + setopts/3]). + +-spec behaviour() -> exometer:behaviour(). +behaviour() -> entry. + +new(Name, counter, _Opts) -> + folsom_metrics:new_counter(Name); +new(Name, spiral, _Opts) -> + folsom_metrics:new_spiral(Name); +new(Name, histogram, Opts) -> + case lists:keysearch(arg, 1, Opts) of + {_, {histogram, SampleType, SampleArgs}} -> + {folsom_metrics:new_histogram(Name, SampleType, SampleArgs), + opt_ref(Opts)}; + false -> + {folsom_metrics:new_histogram(Name, slide_uniform, {60, 1028}), + opt_ref(Opts)} + end; +new(Name, meter, Opts) -> + {folsom_metrics:new_meter(Name), opt_ref(Opts)}; +new(Name, meter_reader, Opts) -> + {folsom_metrics:new_meter_reader(Name), opt_ref(Opts)}; +new(Name, gauge, Opts) -> + {folsom_metrics:new_gauge(Name), opt_ref(Opts)}; +new(Name, duration, Opts) -> + {folsom_metrics:new_duration(Name), opt_ref(Opts)}; +new(Name, history, Opts) -> + case lists:keyfind(size, 1, Opts) of + {_, Sz} -> {folsom_metrics:new_history(Name, Sz), opt_ref(Opts)}; + false -> {folsom_metrics:new_history(Name), opt_ref(Opts)} + end. + + +opt_ref(Opts) -> + case lists:keyfind(truncate, 1, Opts) of + false -> + [{truncate,true}]; + {_, B} when is_boolean(B) -> + [{truncate,B}] + end. + +delete(Name, _Type, _Ref) -> + folsom_metrics:delete_metric(Name). + +update(Name, Value, counter, _Ref) -> + folsom_metrics:notify_existing_metric(Name, {inc, Value}, counter); +update(Name, tick, meter_reader, _Ref) -> + folsom_metrics_meter_reader:tick(Name); +update(Name, Value, meter_reader, _Ref) -> + folsom_metrics_meter_reader:mark(Name, Value); +update(Name, Value, Type, _Ref) -> + folsom_metrics:notify_existing_metric(Name, Value, Type). + +reset(Name, counter, _Ref) -> + folsom_metrics_counter:clear(Name); +reset(Name, gauge, _Ref) -> + folsom_metrics_gauge:clear(Name); +reset(_, _, _) -> + {error, unsupported}. + +get_value(Name, history, _Ref, DataPoints0) -> + try DataPoints = datapoints(history, DataPoints0), + lists:foldr( + fun(events, Acc) -> + [{events, just_events( + folsom_metrics_history:get_events(Name))} + | Acc]; + (values, Acc) -> + [{values, folsom_metrics_history:get_events(Name)} + | Acc]; + (timed_events, Acc) -> + [{timed_events, + timed_events( + folsom_metrics_history:get_events(Name))} + | Acc]; + (Sz, Acc) when is_integer(Sz), Sz > 0 -> + [{Sz, just_events( + folsom_metrics_history:get_events(Name, Sz))} + | Acc]; + (info, Acc) -> + [{info, folsom_metrics_history:get_value(Name)} + | Acc]; + (_, Acc) -> Acc + end, [], DataPoints) + catch + error:_ -> unavailable + end; +get_value(Name, Type, Ref, DataPoints) -> + Trunc = get_trunc_opt(Ref), + Vals = get_value_(Name, Type, Ref), + try [filter_dp(D, Vals, Trunc) || D <- datapoints(Type, DataPoints)] + catch + error:_Error -> + unavailable + end. + +get_trunc_opt(undefined) -> true; +get_trunc_opt(L) -> + proplists:get_value(truncate, L, true). + +get_datapoints(_Name, Type, _Ref) -> + datapoints(Type, default). + +datapoints(Type, default) -> datapoints(Type); +datapoints(_, L) when is_list(L) -> L. + +datapoints(counter) -> + [value]; +datapoints(gauge) -> + [value]; +datapoints(histogram) -> + stats_datapoints(); +datapoints(duration) -> + [count, last |stats_datapoints()]; +datapoints(spiral) -> + [one, count]; +datapoints(meter) -> + [count,one,five,fifteen,day,mean,acceleration]; +datapoints(history) -> + [events, info]. + + +filter_dp(Mean, DPs, Trunc) when Mean==mean; Mean==arithmetic_mean -> + case lists:keyfind(mean, 1, DPs) of + false -> + case lists:keyfind(arithmetic_mean, 1, DPs) of + false -> {mean, zero(Trunc)}; + {_,V} -> {mean, opt_trunc(Trunc, V)} + end; + {_,V} -> {mean, opt_trunc(Trunc, V)} + end; +filter_dp(H, DPs, Trunc) when is_integer(H) -> + case lists:keyfind(H, 1, DPs) of + false -> + case lists:keyfind(percentile, 1, DPs) of + false -> {H, zero(Trunc)}; + {_, Ps} -> + get_dp(H, Ps, Trunc) + end; + {_,V} -> {H, opt_trunc(Trunc, V)} + end; +filter_dp(H, DPs, Trunc) -> + get_dp(H, DPs, Trunc). + +opt_trunc(true, V) when is_float(V) -> + trunc(V); +opt_trunc(round, V) when is_float(V) -> + round(V); +opt_trunc(_, V) -> + V. + +get_dp(K, DPs, Trunc) -> + case lists:keyfind(K, 1, DPs) of + false -> {K, zero(Trunc)}; + {_, V} -> {K, opt_trunc(Trunc, V)} + end. + +zero(true) -> 0; +zero(round) -> 0; +zero(false) -> 0.0. + + +stats_datapoints() -> + [n,mean,min,max,median,50,75,90,95,99,999]. + +setopts(_Entry, _Options, _Status) -> + ok. + +sample(_Name, _Type, _Ref) -> + { error, unsupported }. + +get_value_(Name, counter, _Ref) -> + [{value, folsom_metrics_counter:get_value(Name)}]; +get_value_(Name, gauge, _Ref) -> + [{value, folsom_metrics_gauge:get_value(Name)}]; +get_value_(Name, histogram, _Ref) -> + calc_stats(folsom_metrics_histogram:get_values(Name)); +get_value_(Name, duration, _Ref) -> + {Name, Cnt, _Start, Last} = folsom_metrics_duration:get_value(Name), + Stats = calc_stats(folsom_metrics_histogram:get_values(Name)), + [{count, Cnt}, {last, Last} | Stats]; +get_value_(Name, meter, _Ref) -> + folsom_metrics:get_metric_value(Name); +get_value_(Name, spiral, _Ref) -> + folsom_metrics_spiral:get_values(Name). + +calc_stats(Values) -> + L = length(Values), + exometer_util:get_statistics(L, + lists:sum(Values), + lists:sort(Values)). + +just_events([{I, Events}|T]) when is_integer(I) -> + just_events1(Events, T); +just_events([]) -> + []. + +just_events1([{event, E}|Es], T) -> + [E|just_events1(Es, T)]; +just_events1([], T) -> + just_events(T). + +timed_events([{I, Events}|T]) when is_integer(I) -> + timed_events(Events, I, T); +timed_events([]) -> + []. + +timed_events([{event, E}|Es], I, T) -> + [{I, E}|timed_events(Es, I, T)]; +timed_events([], _, T) -> + timed_events(T). diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_folsom_monitor.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_folsom_monitor.erl new file mode 100644 index 0000000..092d467 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_folsom_monitor.erl @@ -0,0 +1,213 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% +%% @doc Hook API for following folsom-based legacy code with exometer +%% +%% This module installs hooks into folsom, allowing subscribers to trap +%% the creation of metrics using the folsom API, and instruct exometer +%% to create matching metrics entries. +%% +%% Subscriptions identify a module that should be on the call stack when +%% a module is created (when testing from the shell, use the module `shell'), +%% and a callback module which is used to retrieve the specs for exometer +%% metrics to create. +%% @end +-module(exometer_folsom_monitor). + +-behaviour(gen_server). + +-export([start_link/0, + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-export([monitor/2]). +-export([hook/1]). + +-record(st, {mon = orddict:new()}). + +-include_lib("parse_trans/include/codegen.hrl"). +-include_lib("hut/include/hut.hrl"). +-include("exometer.hrl"). + +-type type() :: exometer:type(). +-type name() :: exometer:name(). +-type options() :: exometer:options(). + +-callback copy_folsom(name(), type(), options()) -> + {name(), type(), options()} | + [{name(), type(), options()}] | + false. + +-spec monitor(atom(), atom()) -> ok. +%% @doc Monitor a legacy module. +%% +%% `FromMod' is the name of a module that should appear on the call stack +%% when a call to `folsom_metrics:new_' is made (or '_', +%% which will match any call stack). `Callback' is a callback module, +%% exporting the function `copy_folsom(Name,Type,Opts)', which returns a +%% `{Name, Type, Options}' tuple, a list of such tuples, or the atom `false'. +%% +%% The callback module is called from the `exometer_folsom_monitor' +%% process, so the call stack will not contain the legacy modules. +%% However, if the corresponding exometer metrics end up calling other +%% folsom-based metrics (e.g. using the `exometer_folsom' module), there +%% will be a risk of generating a loop. +%% @end +monitor(FromMod, Callback) when is_atom(FromMod), is_atom(Callback) -> + gen_server:call(?MODULE, {monitor, FromMod, Callback}). + +%% @private +hook(Args) -> + Stack = try error(x) catch error:_ -> erlang:get_stacktrace() end, + gen_server:cast(?MODULE, {hook, Args, Stack}). + +%% @doc Start the server (called automatically by exometer). +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +%% @private +init(_) -> + Mon = lists:foldl( + fun({Mf, Mc}, D) -> + orddict:append(Mf, Mc, D) + end, orddict:new(), find_env()), + init_monitor(Mon), + {ok, #st{mon = Mon}}. + +find_env() -> + E1 = [E || {_, E} <- setup:find_env_vars(exometer_folsom_monitor)], + E2 = exometer_util:get_env(folsom_monitor, []), + lists:flatmap( + fun({_,_} = M) -> [M]; + (L) when is_list(L) -> L + end, E1 ++ E2). + +%% @private +handle_call({monitor, Mod, CB}, _, #st{mon = Mon} = S) + when is_atom(Mod), is_atom(CB) -> + if Mon == [] -> do_init_monitor(); + true -> ok + end, + {reply, ok, S#st{mon = orddict:append(Mod, CB, Mon)}}; +handle_call(_, _, S) -> + {reply, {error, unsupported}, S}. + +%% @private +handle_cast({hook, Args, Stack}, S) -> + check_stack(S#st.mon, Stack, Args), + {noreply, S}. +%% @private +handle_info(_, S) -> {noreply, S}. +%% @private +terminate(_, _) -> ok. +%% @private +code_change(_, S, _) -> {ok, S}. + +init_monitor([]) -> + ok; +init_monitor([_|_]) -> + do_init_monitor(). + +do_init_monitor() -> + case is_transformed() of + true -> + ?log(debug, "already transformed...~n", []), + ok; + false -> + ?log(debug, "transforming folsom_metrics...~n", []), + parse_trans_mod:transform_module(folsom_metrics, fun pt/2, []) + end. + +pt(Forms, _) -> + Funcs = funcs(), + NewForms = parse_trans:plain_transform( + fun(F) -> + plain_pt(F, Funcs) + end, Forms), + mark_transformed(NewForms). + +is_transformed() -> + Attrs = folsom_metrics:module_info(attributes), + [true || {?MODULE,[]} <- Attrs] =/= []. + +mark_transformed([{attribute,L,module,_} = M|Fs]) -> + [M, {attribute,L,?MODULE,[]} | Fs]; +mark_transformed([H|T]) -> + [H | mark_transformed(T)]. + +plain_pt({function,L,F,A,Cs}, Funcs) -> + case lists:keyfind({F,A}, 1, Funcs) of + {_, Type} -> + {function,L,F,A,insert_hook(Type, Cs)}; + false -> + continue + end; +plain_pt(_, _) -> + continue. + +funcs() -> + [{{new_counter , 1}, counter}, + {{new_gauge , 1}, gauge}, + {{new_meter , 1}, meter}, + {{new_meter_reader, 1}, meter_reader}, + {{new_history , 2}, history}, + {{new_histogram , 4}, histogram}, + {{new_spiral , 1}, spiral}, + {{new_duration , 4}, duration}]. + + +insert_hook(Type, Cs) -> + lists:map( + fun({clause,L0,Args,Gs,Body}) -> + L = element(2,hd(Body)), + {clause,L0,Args,Gs, + [{call,L,{remote,L,{atom,L,?MODULE},{atom,L,hook}}, + [cons([{atom,L,Type}|Args], L)]}|Body]} + end, Cs). + +cons([H|T], L) -> {cons,L,H,cons(T,L)}; +cons([] , L) -> {nil,L}. + +check_stack(Mon, Stack, Args) -> + orddict:fold( + fun('_', CBs, Acc) -> + _ = [maybe_create(CB, Args) || CB <- CBs], + Acc; + (Mod, CBs, Acc) -> + case lists:keymember(Mod, 1, Stack) of + true -> + _ = [maybe_create(CB, Args) || CB <- CBs]; + false -> + ignore + end, + Acc + end, ok, Mon). + +maybe_create(CB, [FolsomType, Name | Args]) -> + try CB:copy_folsom(Name, FolsomType, Args) of + {ExoName, ExoType, ExoArgs} -> + exometer:new(ExoName, ExoType, ExoArgs); + L when is_list(L) -> + lists:foreach( + fun({ExoName, ExoType, ExoArgs}) -> + exometer:new(ExoName, ExoType, ExoArgs) + end, L); + false -> + ignore + catch + Cat:Msg -> + ?log(error, "~p:copy_folsom(~p,~p,~p): ~p:~p~n", + [CB, Name, FolsomType, Args, Cat, Msg]), + ignore + end. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_function.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_function.erl new file mode 100644 index 0000000..bf6d7a4 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_function.erl @@ -0,0 +1,689 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-module(exometer_function). + +-behaviour(exometer_entry). + +-export([behaviour/0, + new/3, + update/4, + reset/3, + get_value/4, + get_datapoints/3, + sample/3, + delete/3, + preprocess_setopts/5, + setopts/3]). + +-export([empty/0]). +-export([test_mem_info/1]). + +-export([eval_exprs/2]). + +-export_type([fun_spec/0, arg_spec/0, res_type/0]). + +-type arg() :: '$dp' + | {'$call', atom(), atom(), arg_spec()} + | any(). +-type arg_spec() :: [arg()]. +-type datapoints() :: [atom()]. +-type mod_name() :: atom(). +-type fun_name() :: atom(). +-type res_type() :: value %% The return value is the result + | proplist %% Pick the data point out of a proplist + | tagged. %% Either {DataPoint,Value} or {ok,Value} +-type simple_fun() :: {function, mod_name(), fun_name()}. +-type extended_fun() :: {function, mod_name(), fun_name(), + arg_spec(), res_type(), datapoints()}. +-type fun_spec() :: simple_fun() | extended_fun(). +-type fun_rep() :: {mod_name(), fun_name()} + | {mod_name(), fun_name(), each | once, + arg_spec(), res_type(), datapoints()} + | {mod_name(), fun_name(), each | once, + arg_spec(), match, any()} + | {eval, [expr()], datapoints()}. + +-spec behaviour() -> exometer:behaviour(). +behaviour() -> + entry. + +-spec new(exometer:name(), 'function', + exometer:options()) -> {ok, fun_rep()}. +%% @doc Callback for creating an exometer `function' entry. +%% +%% Function entries are created as +%%
+%% exometer:new(Name,{function,...},Opts)
+%% 
+%% which is syntactic sugar for +%%
+%% exometer:new(Name,function,[{arg,{function,...}}|Opts])
+%% 
+%% +%% `{function,...}' can be `{function, Mod, Fun}', in which case +%% where `get_value(Name, DataPoints)' will result in a call to +%% `Mod:Fun(DataPoints)'. +%% Invoking get_value(Name) (with no datapoints), will call +%% `Mod:Fun(default), which must return a default list of data point +%% values. +%% +%% `{function,...}' can also be setup as `{function, +%% Mod,Fun,ArgSpec,Type,DataPoints}' in order to invoke a limited +%% interpreter. The `ArgSpec' is evaluated as follows: +%% +%%
    +%%
  • `[]' means to call with no arguments, i.e. `M:F()'
  • +%%
  • A list of patterns will be used as arguments, substituting the +%% following patterns: +%%
      +%%
    • '$dp' is replaced by the current data point
    • +%%
    • '$datapoints' is replaced by the requested list of +%% data points. Note that '$dp' and +%% '$datapoints' are mutually exclusive
    • +%%
    • {'$call', M, F, Args0} will be replaced by the result +%% of calling `apply(M, F, Args)' where `Args' is the list of +%% arguments after performing substitution on `Args0'.
    • +%%
    • {'$value', Term} uses `Term' without +%% substitution.
    • +%%
  • +%%
+%% +%% The return value of the above call will be processed according to `Type': +%%
    +%%
  • If `Type==value', the return value is returned as-is
  • +%%
  • If `Type==histogram', the return value is a list of integers, which +%% will be compiled into a histogram (see {@link exometer_histogram}). +%%
  • +%%
  • If `Type==proplist', the current data point or list of data points +%% will be picked out of the returned proplist.
  • +%%
  • If `Type==tagged', the return value is assumed to be either +%% `{ok, Value}' or `{DataPointName, Value}'.
  • +%%
  • If `Type==match', `DataPoints' is used as a pattern to match against, +%% where the names of data points are used where the values are expected +%% to be, and '_' is used for values to ignore. The pattern +%% can be any combination of tuples and lists of datapoints or +%% '_'.
  • +%%
  • If `Type==eval', `DataPoints' is expected to be `{Exprs, DPs}', +%% and {@link eval_exprs/2} will be used to evaluate `Exprs'. The return +%% value from the function call will be bound to `Value', and the list +%% of data points will be bound to `DPs'. The evaluation must return +%% a list of `{DataPointName, Value}' tuples.
  • +%%
+%% +%% An alternative version of `arg' is `{arg, {eval, Exprs, Datapoints}}', which +%% doesn't in fact call a function, but simply evaluates `Exprs' using +%% {@link eval_exprs/2}, with the pre-bound variables `Value = undefined' +%% and `DPs = Datapoints'. +%% +%% Examples: +%% +%% An entry that returns a subset of `erlang:memory()': +%% +%%
+%% exometer:new([mem], {function,erlang,memory,[],proplist,[total,processes]}).
+%% 
+%% +%% An entry that reports the heap size and message queue length of the +%% code server: +%% +%%
+%% exometer:new(
+%%     [code_server, pinfo],
+%%     {function,erlang,process_info,[{'$call',erlang,whereis,[code_server]}],
+%%      proplist, [heap_size, message_queue_len]}).
+%% 
+%% +%% An entry that reports the heap size of the code server. +%% +%%
+%% exometer:new(
+%%   [code_server, heap_size],
+%%   {function,erlang,process_info,
+%%    [{'$call',erlang,whereis,[code_server]}, '$dp'], tagged, [heap_size]}).
+%% 
+%% +%% An entry that does pattern-matching on the return value +%% (`erlang:statistics(garbage_collection)' returns `{GCs, Reclaimed, 0}'). +%% +%%
+%% exometer:new(
+%%    [gc],
+%%    { function,erlang,statistics,[garbage_collection],
+%%      match, {gcs,reclaimed,'_'} }, []).
+%% 
+%% +%% An entry that calls `erlang:processes()' and evaluates a list of expressions +%% that calculate the length of the returned list. +%% +%%
+%% exometer:new(
+%%     [ps],
+%%     {function,erlang,processes,[],
+%%      eval, {[{l,[{t,[value,{call,length,[{v,'Value'}]}]}]}],[value]}}, []).
+%% 
+%% +%% An entry that simply builds a list of datapoints, using the abstract syntax. +%% +%%
+%% exometer:new([stub],
+%%     {function,{eval,[{l,[{t,[{a,1}]},{t,[{b,2}]}]}], [a,b]}}, []).
+%% 
+%% @end +new(_Name, function, Opts) -> + case lists:keyfind(arg, 1, Opts) of + {_, Arg} -> + {ok, ref_from_arg(Arg)}; + false -> + {ok, {?MODULE, empty}} + end. + +ref_from_arg({_M,_F} = Arg) -> Arg; +ref_from_arg({M, F, ArgsP, Type, DPs}) -> + {M, F, mode(ArgsP), ArgsP, Type, DPs}; +ref_from_arg({eval,_,_} = Arg) -> + Arg; +ref_from_arg(_Arg) -> + error(invalid_arg). + +arg_from_ref({_M,_F} = Ref) -> Ref; +arg_from_ref({M, F, _, ArgsP, Type, DPs}) -> + {M, F, ArgsP, Type, DPs}; +arg_from_ref(_Ref) -> + error(invalid_ref). + + +get_value(_, function, {M, F, once, ArgsP, match, Pat}, DataPoints0) -> + DataPoints = if DataPoints0 == default -> + pattern_datapoints(Pat); + is_list(DataPoints0) -> + DataPoints0 + end, + try call_once(M, F, ArgsP, match, {Pat, DataPoints}) + catch + _:_ -> + {error, unavailable} + end; +get_value(_, _, {M, F, each, ArgsP, Type, DPs}, DataPoints) -> + [{D,call(M,F,ArgsP,Type,D)} || D <- datapoints(DataPoints, DPs), + lists:member(D, DPs)]; +get_value(_, function, {M, F, once, ArgsP, match, Pat}, DataPoints0) -> + DataPoints = if DataPoints0 == default -> + pattern_datapoints(Pat); + is_list(DataPoints0) -> + DataPoints0 + end, + try call_once(M, F, ArgsP, match, {Pat, DataPoints}) + catch + _:_ -> + {error, unavailable} + end; +get_value(_, _, {M, F, once, ArgsP, Type, DPs}, DataPoints0) -> + DataPoints = actual_datapoints(DataPoints0, DPs, Type), + try call_once(M, F, ArgsP, Type, DataPoints) + catch + _:_ -> + {error, unavailable} + end; +get_value(_, _, {eval, Exprs, DataPoints}, DataPoints0) -> + DataPoints = actual_datapoints(DataPoints0, DataPoints, eval), + return_eval(eval_expr(Exprs, undefined, DataPoints), DataPoints); +get_value(_, _, {M, F}, DataPoints) -> + if DataPoints == default -> + M:F(DataPoints); + is_list(DataPoints) -> + [D || {K,_} = D <- M:F(DataPoints), + lists:member(K, DataPoints)] + end. + +actual_datapoints(default, default, histogram) -> + exometer_histogram:datapoints(); +actual_datapoints(DPs, default, histogram) -> + actual_datapoints(DPs, exometer_histogram:datapoints(), histogram); +actual_datapoints(default, DPs, _) -> + DPs; +actual_datapoints(DPs0, DPs, _) -> + [D || D <- datapoints(DPs0, DPs), + lists:member(D, DPs)]. + + +get_datapoints(_Name, _Type, {_, _, once, _, match, Pat}) -> + pattern_datapoints(Pat); +get_datapoints(_Name, _Type, T) when is_tuple(T), is_list( + element(size(T),T)) -> + element(size(T), T); +get_datapoints(_Name, _Type, _Ref) -> + [value]. + +update(_, _, _, _) -> + {error, unsupported}. + +sample(_, _, _) -> + {error, unsupported}. + +reset(_, _, _) -> + {error, unsupported}. + +preprocess_setopts(_Name, Opts, _Type, _Ref, _OldOpts) -> + case {lists:keyfind(arg,1,Opts), lists:keyfind(ref,1,Opts)} of + {{_,A}, {_,R}} -> + case ref_from_arg(A) of + R -> Opts; + _ -> + error({conflict, [{arg,A},{ref,R}]}) + end; + {{_,New}, false} -> + [{ref, ref_from_arg(New)}|Opts]; + {false, {ref, New}} -> + [{arg, arg_from_ref(New)}|Opts]; + _ -> + Opts + end. + +setopts(_, _, _) -> + ok. + +delete(_, _, _) -> + ok. + +empty() -> + []. + +datapoints(default, DPs) -> datapoints_(DPs); +datapoints(DataPoints,_) when is_list(DataPoints) -> DataPoints. + +datapoints_(DPs) when is_list(DPs) -> DPs; +datapoints_(DP ) when is_atom(DP ) -> [DP]. + +pattern_datapoints(A) when is_atom(A), A =/= '_' -> + [A]; +pattern_datapoints([H|T]) -> + pattern_datapoints(H) ++ pattern_datapoints(T); +pattern_datapoints(T) when is_tuple(T) -> + pattern_datapoints(tuple_to_list(T)); +pattern_datapoints(_) -> + []. + +call(M,F,ArgsP,T,D) -> + try begin + Args = substitute(ArgsP, D), + return_dp(apply(M, F, Args), T, D) + end + catch + _:_ -> + undefined + end. + +call_once(M,F,ArgsP,T,DPs) -> + Args = substitute_dps(ArgsP, DPs), + return_dps(apply(M, F, Args), T, DPs). + +substitute([], _) -> + []; +substitute(['$dp'|T], D) -> + [D | substitute(T, D)]; +substitute([{'$call', M, F, ArgsP}|T], D) -> + Args = substitute(ArgsP, D), + [apply(M, F, Args) | substitute(T, D)]; +substitute([{'$value',V}|T], D) -> + [V | substitute(T, D)]; +substitute([H|T], D) -> + [H|substitute(T, D)]. + +substitute_dps([], _) -> + []; +substitute_dps(['$datapoints'|T], DPs) -> + [DPs | substitute_dps(T, DPs)]; +substitute_dps([{'$call', M, F, ArgsP}|T], DPs) -> + Args = substitute_dps(ArgsP, DPs), + [apply(M, F, Args) | substitute_dps(T, DPs)]; +substitute_dps([{'$value',V}|T], DPs) -> + [V | substitute_dps(T, DPs)]; +substitute_dps([H|T], DPs) -> + [H|substitute_dps(T, DPs)]. + +return_dp({T,V}, tagged, D) when T==D; T==ok -> + V; +return_dp(V, value, _) -> + V; +return_dp(L, length, _) -> + if is_list(L) -> length(L); + true -> undefined + end; +return_dp(L, proplist, D) -> + case lists:keyfind(D, 1, L) of + false -> undefined; + {_, V} -> V + end. + +return_dps(L, value, _) -> + L; +return_dps({DP, V}, tagged, DPs) -> + %% This is a slightly special case, which can happen e.g. for + %% {function,erlang,process_info,[P, heap_size],tagged,heap_size}. + %% The implicit mode will be 'once' since no '$dp' entry present, but + %% only one data point will be returned. Since it's tagged, we know + %% which data point it is. + [{D, if D==DP -> V; true -> undefined end} || D <- DPs]; +return_dps(L, proplist, DPs) -> + [get_dp(D, L) || D <- DPs]; +return_dps(L, histogram, DPs) -> + exometer_util:histogram(L, DPs); +return_dps(Val, eval, {Expr, DPs}) -> + try return_eval(eval_expr(Expr, Val, DPs), DPs) + catch + _:_ -> undefined + end; +return_dps(Val, match, {Pat, DPs}) -> + match_pat(Pat, Val, DPs). + +return_eval({value, L, _}, default) when is_list(L) -> + L; +return_eval({value, L, _}, DPs) when is_list(DPs), is_list(L) -> + [get_dp(D, L) || D <- DPs]; +return_eval(_, _) -> + undefined. + + +get_dp(D, L) -> + case lists:keyfind(D, 1, L) of + false -> + {D, undefined}; + V -> + V + end. + +mode(Args) -> + case mode(Args, undefined) of + undefined -> once; + Other -> Other + end. + +mode(['$dp'|T], M) -> + if M==each; M==undefined -> + mode(T, each); + true -> + error(mode_conflict) + end; +mode(['$datapoints'|T], M) -> + if M==once; M==undefined -> + mode(T, once); + true -> + error(mode_conflict) + end; +mode([H|T], M) when is_list(H) -> + mode(T, mode(H, M)); +mode([H|T], M) when is_tuple(H) -> + mode(T, mode(tuple_to_list(H), M)); +mode([_|T], M) -> + mode(T, M); +mode([], M) -> + M. + + +test_mem_info(DataPoints) -> + Res = erlang:memory(), + [get_dp(D, Res) || D <- DataPoints]. + +match_pat(Pat, Val, DPs) when tuple_size(Pat) == tuple_size(Val) -> + match_pat(tuple_to_list(Pat), tuple_to_list(Val), DPs); +match_pat(['_'|T], [_|T1], DPs) -> + match_pat(T, T1, DPs); +match_pat([H|T], [H1|T1], DPs) when is_atom(H) -> + case lists:member(H, DPs) of + true -> + [{H, H1}|match_pat(T, T1, DPs)]; + false -> + match_pat(T, T1, DPs) + end; +match_pat([H|T], [H1|T1], DPs) -> + match_pat(H, H1, DPs) ++ match_pat(T, T1, DPs); +match_pat(A, B, DPs) when is_atom(A), A =/= '_' -> + case lists:member(A, DPs) of + true -> + [{A, B}]; + false -> + [] + end; +match_pat(_, _, _) -> + []. + +%% Expressions: +eval_expr([_|_] = Exprs, Value, DPs) -> + eval_exprs(Exprs, [{'DPs',DPs},{'Value',Value}]); +eval_expr(Expr, Value, DPs) -> + eval_exprs([Expr], [{'DPs',DPs},{'Value',Value}]). + +-type expr() :: expr_descr() | expr_action() | expr_match() | expr_erl(). +-type expr_descr() :: expr_int() | expr_atom() | expr_list() | expr_tuple() + | expr_string(). +-type expr_action() :: expr_op() | expr_call() | expr_fold() | expr_case(). +-type expr_int() :: integer() | {i, integer()} | {integer, integer()}. +-type expr_atom() :: atom() | {a, atom()} | {atom, atom()}. +-type expr_string() :: {string, string()} | {s, string()}. +-type expr_tuple() :: {tuple, [expr()]} | {t, [expr()]}. +-type expr_list() :: {cons, expr(), expr()} | nil + | {l, [expr()]}. +-type expr_match() :: {match, expr_pattern(), expr()} + | {m, expr_pattern(), expr()}. +-type expr_pattern() :: '_' | expr_descr(). +-type expr_op() :: expr_unary_op() | expr_binary_op(). +-type expr_unary_op() :: {op, '-' | 'not', expr()}. +-type expr_binary_op() :: {op, expr_operator(), expr(), expr()}. +-type expr_operator() :: '+' | '-' | '*' | '/' | 'div' | 'rem' | 'band' + | 'and' | 'bor' | 'bxor' | 'bsl' | 'bsr' | 'or' + | 'xor' | '++' | '--' | '==' | '/=' | '>=' | '=<' + | '<' | '>' | '=:=' | '=/='. +-type expr_call() :: {call, atom(), [expr()]} + | {call, {atom(), atom()}, [expr()]}. +-type expr_fold() :: {fold, _IterVal::atom(), _AccVar::atom(), + _IterExpr::[expr()], _Acc0Expr::expr(), + _ListExpr::expr()}. +-type expr_case() :: {'case', [expr()], [expr_clause()]}. +-type expr_clause() :: {expr_pattern(), [expr_guard()], [expr()]}. +-type expr_guard() :: [expr()]. % Must all return 'true'. +-type expr_erl() :: {erl, [erl_parse:abstract_expr()]}. +-type binding() :: {atom(), any()}. + + + +-spec eval_exprs([expr()], [binding()]) -> {value, any(), [binding()]}. +%% @doc Evaluate a list of abstract expressions. +%% +%% This function is reminiscent of `erl_eval:exprs/2', but with a slightly +%% different expression grammar. Most prominently, forms have no line numbers, +%% and a few aliases for more compact representation. Otherwise, the forms can +%% be seen as mostly a subset of the Erlang abstract forms. +%% +%% The list of bindings correspods exactly to the bindings in `erl_eval'. +%% +%% * Integers: `{integer, I}', `{i, I}', or simply just the integer +%% * Atoms: `{atom, A}', `{a, A}', or simply just the atom (note that some atoms +%% are special). +%% * Lists: `{cons, H, T}', `nil', or `{l, [...]}' +%% * Tuples: `{tuple, [Elem]}', or `{t, [Elem]}' +%% * Variables: `{var, V}', or `{v, V}' +%% * Matches: `{match, Pattern, Expr}', or `{m, Pattern, Expr}' +%% * Function calls: `{call, {M, F}, Args}', or `{call, F, Args}' +%% * Folds: `{fold, IterVar, AccVar, [IterExpr], Acc0Expr, ListExpr}' +%% * Operators: `{op, Op, ExprA, ExprB}' +%% * Unary operators: {op, '-' | 'not', Expr} +%% * Case exprs: {'case', [Expr], [{Pat, Gs, Body}]} +%% * Generic Erlang: `{erl, [ErlAbstractExpr]}' +%% +%% The currently supported "built-in functions" are `length/1', `size/1', +%% `byte_size/1' and `bit_size/1'. +%% +%% The operators supported are all the Erlang binary operators (as in: '+', +%% '-', '==', '=/=', etc.) +%% +%% When evaluating guards in a case clause, any expression is legal. The +%% guard must return true to succeed. Note that the abstract form of a guard +%% sequence is [ [G11,...], [G21,...], ...], where each sublist represents +%% an 'and' sequence, i.e. all guards in the sublist must succeed. The +%% relationship between sublists is 'or'. This is the same as in Erlang. +%% @end +eval_exprs([E|Es], Bs) -> + {value, Val, Bs1} = eval_(E, Bs), + eval_exprs(Es, Val, Bs1). + +eval_exprs([], Val, Bs) -> + {value, Val, Bs}; +eval_exprs([E|Es], _, Bs) -> + {value, Val, Bs1} = eval_(E, Bs), + eval_exprs(Es, Val, Bs1). + +eval_({erl, Exprs}, Bs) -> + erl_eval:exprs(Exprs, Bs); +eval_({T, P, E}, Bs) when T==m; T==match -> + Val = e(E, Bs), + {value, Val, match(P, Val, Bs)}; +eval_({'case', Es, Cls}, Bs) -> + {value, V, _} = eval_exprs(Es, Bs), + case_clauses(Cls, V, Bs); +eval_(Expr, Bs) -> {value, e(Expr, Bs), Bs}. + +e({T,V}, Bs) when T==v; T==var -> + case erl_eval:binding(V, Bs) of + unbound -> error({unbound, V}); + {value, Val} -> Val + end; +e(nil, _) -> []; +e(I, _) when is_integer(I) -> I; +e(A, _) when is_atom(A) -> A; +e({T,I}, _) when T==i; T==integer -> I; +e({T,A}, _) when T==a; T==atom -> A; +e({cons,Eh,Et}, Bs) -> [e(Eh, Bs)|e(Et, Bs)]; +e({hd,E}, Bs) -> hd(e(E, Bs)); +e({tl,E}, Bs) -> tl(e(E, Bs)); +e({l, Es}, Bs) -> [e(E, Bs) || E <- Es]; +e({T,S}, _) when T==s; T==string -> S; +e({T,Es}, Bs) when T==t; T==tuple -> list_to_tuple([e(E,Bs) || E <- Es]); +e({call,F,As}, Bs) -> + call1(F, [e(A,Bs) || A <- As]); +e({lc,_E0,_Es}, _Bs) -> error(nyi); +e({op,Op,E1,E2}, Bs) -> op(Op, e(E1,Bs), e(E2,Bs)); +e({op,Op,E}, Bs) when Op=='-'; Op=='not' -> + erlang:Op(e(E, Bs)); +e({element,E,T}, Bs) -> + case e(T, Bs) of + Tup when is_tuple(Tup) -> + element(e(E,Bs), Tup); + _ -> error(badarg) + end; +e({histogram, Vs}, Bs) -> + case e(Vs, Bs) of + L when is_list(L) -> + exometer_util:histogram(L); + _ -> error(badarg) + end; +e({histogram, Vs, DPs}, Bs) -> + case e(Vs, Bs) of + L when is_list(L) -> + DataPoints = case e(DPs, Bs) of + default -> default; + D when is_list(D) -> D; + _ -> error(badarg) + end, + exometer_util:histogram(L, DataPoints); + _ -> error(badarg) + end; +e({fold,Vx,Va,Es,Ea,El}, Bs) when is_atom(Vx), is_atom(Va) -> + case e(El, Bs) of + L when is_list(L) -> + Bs1 = erl_eval:add_binding(Va, e(Ea, Bs), Bs), + fold_(L, Vx, Va, Es, Bs1); + _ -> + error(badarg) + end. + +match({T,Es}, V, Bs) when T==t; T==tuple -> + Vals = tuple_to_list(V), + Z = lists:zip(Es, Vals), + lists:foldl(fun({E1,V1}, Bs1) -> + match(E1, V1, Bs1) + end, Bs, Z); +match('_', _, Bs) -> Bs; +match(nil, [], Bs) -> Bs; +match({T,X}, V, Bs) when T==a; T==atom; T==i; T==integer -> + X = V, + Bs; +match(I, I, Bs) when is_integer(I) -> Bs; +match(A, V, Bs) when is_atom(A) -> + case atom_to_list(A) of + "_" ++ _ -> Bs; + _ -> + A = V, + Bs + end; +match({cons,Eh,Et}, [H|T], Bs) -> + Bs1 = match(Eh, H, Bs), + match(Et, T, Bs1); +match({T,Var}, V, Bs) when T==v; T==var -> + erl_eval:add_binding(Var, V, Bs). + +case_clauses([{Pat, Gs, Body}|Cs], Val, Bs) -> + try match(Pat, Val, Bs) of + Bs1 -> + case match_gs(Gs, Bs1) of + true -> + eval_exprs(Body, Bs1); + _ -> + case_clauses(Cs, Val, Bs) + end + catch + _:_ -> + case_clauses(Cs, Val, Bs) + end; +case_clauses([], _, _) -> + error(case_clause). + +match_gs([], _) -> + true; +match_gs([_|_] = Gs, Bs) -> + and_gs(Gs, Bs). + +and_gs([], _) -> false; +and_gs([G|Gs], Bs) -> + try [{value, true, _} = eval_(G1, Bs) || G1 <- G], true + catch + _:_ -> + and_gs(Gs, Bs) + end. + +call1(length , [L]) -> length(L); +call1(size , [T]) -> size(T); +call1(byte_size, [B]) -> byte_size(B); +call1(bit_size , [B]) -> bit_size(B); +call1(tuple_to_list , [T]) -> tuple_to_list(T); +call1(list_to_tuple , [L]) -> list_to_tuple(L); +call1(atom_to_list , [A]) -> atom_to_list(A); +call1(list_to_atom , [L]) -> list_to_atom(L); +call1(list_to_binary, [L]) -> list_to_binary(L); +call1(binary_to_list, [B]) -> binary_to_list(B); +call1(t2l, [T]) -> tuple_to_list(T); +call1(l2t, [L]) -> list_to_tuple(L); +call1(a2l, [A]) -> atom_to_list(A); +call1(l2a, [L]) -> list_to_atom(L); +call1(l2b, [L]) -> list_to_binary(L); +call1(b2l, [B]) -> binary_to_list(B); +call1({M,F}, As) when is_atom(M), is_atom(F) -> + apply(M, F, As). + +op(Op, A, B) when is_atom(Op) -> + erlang:Op(A, B). + +fold_([H|T], Vx, Va, Es, Bs) -> + Bs1 = erl_eval:add_binding(Vx, H, Bs), + {value, NewA, _} = eval_exprs(Es, Bs1), + fold_(T, Vx, Va, Es, erl_eval:add_binding(Va, NewA, Bs)); +fold_([], _, Va, _, Bs) -> + {value, Acc} = erl_eval:binding(Va, Bs), + Acc. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_global.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_global.erl new file mode 100644 index 0000000..e30c1a4 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_global.erl @@ -0,0 +1,17 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @private +-module(exometer_global). + +-export([status/0]). + +-spec status() -> enabled | disabled. +status() -> + enabled. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_histogram.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_histogram.erl new file mode 100644 index 0000000..3989cc9 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_histogram.erl @@ -0,0 +1,516 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +%% @doc Exometer histogram probe behavior +%% This module implements histogram metrics. Each histogram is a sliding +%% window, for which the following datapoints are calculated: +%% +%% * `max': the maximum value +%% * `min': the minimum value +%% * `mean': the arithmetic mean +%% * `median': the median +%% * `50|75|90|95|97|99': percentiles +%% * `999': the 99.9th percentile +%% * `n': the number of values used in the calculation (Note) +%% +%% Two histogram implementations are supported and can be selected using +%% the option `histogram_module': +%% +%% * `exometer_slide' implements a sliding window, which saves all elements +%% within the window. Updating the histogram is cheap, but calculating the +%% datapoints may be expensive depending on the size of the window. +%% +%% * `exometer_slot_slide' (default), aggregates mean, min and max values +%% within given time slots, thereby reducing the amount of data kept for +%% datapoint calculation. The update overhead should be insignificant. +%% However, some loss of precision must be expected. To achieve slightly +%% better accuracy of percentiles, 'extra values' are kept (every 4th +%% value). For the calculation, extra vaules are included in the set +%% until a suitable number has been reached (up to 600). Note that +%% `n' reflects the number of values used in the calculation - not the +%% number of updates made within the time window. +%% +%% Supported options: +%% +%% * `time_span' (default: `60000') size of the window in milliseconds. +%% * `slot_period' (default: `10') size of the time slots in milliseconds. +%% * `histogram_module' (default: `exometer_slot_slide'). +%% * `truncate' (default: `true') whether to truncate the datapoint values. +%% Supported values: `true | false | round', where `round' means to round +%% the value rather than truncating it. +%% * `keep_high' (default: `0') number of top values to actually keep. +%% +%% The `keep_high' option can be used to get better precision for the higher +%% percentiles. A bounded buffer (see {@link exometer_shallowtree}) is used +%% to store the highest values, and these values are used to calculate the +%% exact higher percentiles, as far as they go. For example, if the window +%% saw 10,000 values, and the 1000 highest values are kept, these can be used +%% to determine the percentiles `90' and up. +%% +%% @end +-module(exometer_histogram). +-behaviour(exometer_probe). + +%% exometer_probe callbacks +-export([behaviour/0, + probe_init/3, + probe_terminate/1, + probe_setopts/3, + probe_update/2, + probe_get_value/2, + probe_get_datapoints/1, + probe_reset/1, + probe_code_change/3, + probe_sample/1, + probe_handle_msg/2]). + +-compile(inline). +-compile(inline_list_funcs). +-export([datapoints/0]). +-export([average_sample/3, + average_transform/2]). + +-export([test_run/1, test_run/2, + test_series/0]). + +%% -compile({parse_transform, exometer_igor}). +%% -compile({igor, [{files, ["src/exometer_util.erl" +%% , "src/exometer_proc.erl" +%% , "src/exometer_slot_slide.erl" +%% , "src/exometer_slide.erl" +%% ]}]}). + +-include("exometer.hrl"). + +-record(st, {name, + slide = undefined, %% + slot_period = 10, %% msec + time_span = 60000, %% msec + truncate = true, + histogram_module = exometer_slot_slide, + heap, + opts = []}). + +%% for auto-conversion +-define(OLDSTATE, {st,_,_,_,_,_,_,_}). + +-define(DATAPOINTS, + [n, mean, min, max, median, 50, 75, 90, 95, 99, 999 ]). + +-spec behaviour() -> exometer:behaviour(). +behaviour() -> + probe. + +probe_init(Name, _Type, Options) -> + {ok, init_state(Name, Options)}. + +init_state(Name, Options) -> + St = process_opts(#st{name = Name}, Options), + Slide = (St#st.histogram_module):new(St#st.time_span, + St#st.slot_period, + fun average_sample/3, + fun average_transform/2, + Options), + Heap = if St#st.histogram_module == exometer_slot_slide -> + case lists:keyfind(keep_high, 1, Options) of + false -> + undefined; + {_, N} when is_integer(N), N > 0 -> + T = exometer_shallowtree:new(N), + {T, T}; + {_, 0} -> + undefined + end; + true -> undefined + end, + St#st{slide = Slide, heap = Heap}. + + +probe_terminate(_St) -> + ok. + +probe_get_value(DPs, ?OLDSTATE = St) -> + probe_get_value(DPs, convert(St)); +probe_get_value(DataPoints, St) -> + {ok, get_value_int(St, DataPoints)}. + +probe_get_datapoints(_St) -> + {ok, datapoints()}. + +datapoints() -> + ?DATAPOINTS. + + +get_value_int(St, default) -> + get_value_int_(St, ?DATAPOINTS); + +get_value_int(_, []) -> + []; + +get_value_int(?OLDSTATE = St, DPs) -> + get_value_int(convert(St), DPs); +get_value_int(St, DataPoints) -> + get_value_int_(St, DataPoints). + +get_value_int_(#st{truncate = Trunc, + histogram_module = Module, + time_span = TimeSpan, + heap = Heap} = St, DataPoints) -> + %% We need element count and sum of all elements to get mean value. + Tot0 = case Trunc of true -> 0; round -> 0; false -> 0.0 end, + TS = exometer_util:timestamp(), + {Length, FullLength, Total, Min0, Max, Lst0, Xtra} = + Module:foldl( + TS, + fun + ({_TS1, {Val, Cnt, NMin, NMax, X}}, + {Length, FullLen, Total, OMin, OMax, List, Xs}) -> + {Length + 1, FullLen + Cnt, Total + Val, + min(OMin, NMin), max(OMax, NMax), + [Val|List], [X|Xs]}; + + ({_TS1, Val}, {Length, _, Total, Min, Max, List, Xs}) -> + L1 = Length+1, + {L1, L1, Total + Val, min(Val, Min), max(Val, Max), + [Val|List], Xs} + end, + {0, 0, Tot0, infinity, 0, [], []}, St#st.slide), + Min = if Min0 == infinity -> 0; true -> Min0 end, + Mean = case Length of + 0 -> 0.0; + N -> Total / N + end, + + {Len, List} = + if Module == exometer_slot_slide -> + {Length1, Lst} = add_extra(Length, Lst0, Xtra), + {Length1 + 2, [Min|lists:sort(Lst)] ++ [Max]}; + true -> + {Length, lists:sort(Lst0)} + end, + TopPercentiles = get_from_heap(Heap, TS, TimeSpan, FullLength, DataPoints), + Results = case List of + [0, 0] -> + %% a case where Min and Max are the only data, nil + []; + _ -> + exometer_util:get_statistics2(Len, List, Total, Mean) + end, + CombinedResults = TopPercentiles ++ Results, + [get_dp(K, CombinedResults, Trunc) || K <- DataPoints]. + +get_from_heap({New,Old}, TS, TSpan, N, DPs) when N > 0 -> + Sz = exometer_shallowtree:size(New) + + exometer_shallowtree:size(Old), + if Sz > 0 -> + MinPerc = 100 - ((Sz*100) div N), + MinPerc10 = MinPerc * 10, + GetDPs = lists:foldl( + fun(D, Acc) when is_integer(D), + D < 100, D >= MinPerc -> + [{D, p(D, N)}|Acc]; + (D, Acc) when is_integer(D), + D > 100, D >= MinPerc10 -> + [{D, p(D, N)}|Acc]; + (_, Acc) -> + Acc + end, [], DPs), + pick_heap_vals(GetDPs, New, Old, TS, TSpan); + true -> + [] + end; +get_from_heap(_, _, _, _, _) -> + []. + +pick_heap_vals([], _, _, _, _) -> + []; +pick_heap_vals(DPs, New, Old, TS, TSpan) -> + TS0 = TS - TSpan, + NewVals = exometer_shallowtree:filter(fun(V,_) -> {true,V} end, New), + OldVals = exometer_shallowtree:filter( + fun(V,T) -> + if T >= TS0 -> + {true, V}; + true -> + false + end + end, Old), + Vals = revsort(OldVals ++ NewVals), + exometer_util:pick_items(Vals, DPs). + +revsort(L) -> + lists:sort(fun erlang:'>'/2, L). + +p(50, N) -> perc(0.5, N); +p(75, N) -> perc(0.75, N); +p(90, N) -> perc(0.9, N); +p(95, N) -> perc(0.95, N); +p(99, N) -> perc(0.99, N); +p(999,N) -> perc(0.999, N). + +perc(P, N) -> N - exometer_util:perc(P, N) + 1. + +add_extra(Length, L, []) -> + {Length, L}; +add_extra(Length, L, X) when Length < 300 -> + %% aim for 600 elements, since experiments indicate that this + %% gives decent accuracy at decent speed (ca 300-400 us on a Core i7) + Pick = max(2, ((600 - Length) div Length) + 1), + pick_extra(X, Pick, Pick, L, Length); +add_extra(Length, L, X) -> + %% Always take something from the Xtra, since this improves percentile + %% accuracy + pick_extra(X, 1, 1, L, Length). + + +pick_extra([[H|T]|T1], P, Pick, L, Length) when P > 0 -> + pick_extra([T|T1], P-1, Pick, [H|L], Length+1); +pick_extra([_|T], 0, Pick, L, Length) -> + pick_extra(T, Pick, Pick, L, Length); +pick_extra([[]|T], _, Pick, L, Length) -> + pick_extra(T, Pick, Pick, L, Length); +pick_extra([], _, _, L, Length) -> + {Length, L}. + +get_dp(K, L, Trunc) -> + case lists:keyfind(K, 1, L) of + false -> + {K, if Trunc -> 0; Trunc==round -> 0; true -> 0.0 end}; + {median, F} when is_float(F) -> + %% always truncate median + {median, trunc(F)}; + {_, V} = DP when is_integer(V) -> + DP; + {_,_} = DP -> + opt_trunc(Trunc, DP) + end. + +probe_setopts(_Entry, _Opts, _St) -> + ok. + +probe_update(Value, ?OLDSTATE = St) -> + probe_update(Value, convert(St)); +probe_update(Value, St) -> + if is_number(Value) -> + {ok, update_int(exometer_util:timestamp(), Value, St)}; + true -> + %% ignore + {ok, St} + end. + +update_int(Timestamp, Value, #st{slide = Slide, + histogram_module = Module, + heap = Heap} = St) -> + {Wrapped, Slide1} = Module:add_element(Timestamp, Value, Slide, true), + St#st{slide = Slide1, heap = into_heap(Wrapped, Value, Timestamp, Heap)}. + +into_heap(_, _Val, _TS, undefined) -> + undefined; +into_heap(false, Val, TS, {New,Old}) -> + {exometer_shallowtree:insert(Val, TS, New), Old}; +into_heap(true, Val, TS, {New,_}) -> + Limit = exometer_shallowtree:limit(New), + {exometer_shallowtree:insert( + Val, TS, exometer_shallowtree:new(Limit)), New}. + +probe_reset(?OLDSTATE = St) -> + probe_reset(convert(St)); +probe_reset(#st{slide = Slide, + histogram_module = Module} = St) -> + {ok, St#st{slide = Module:reset(Slide)}}. + +probe_sample(_St) -> + {error, unsupported}. + +probe_handle_msg(_, S) -> + {ok, S}. + +probe_code_change(_, ?OLDSTATE = S, _) -> + {ok, convert(S)}; +probe_code_change(_, S, _) -> + {ok, S}. + +convert({st, Name, Slide, Slot_period, Time_span, + Truncate, Histogram_module, Opts}) -> + #st{name = Name, slide = Slide, slot_period = Slot_period, + time_span = Time_span, truncate = Truncate, + histogram_module = Histogram_module, opts = Opts}. + +process_opts(St, Options) -> + exometer_proc:process_options(Options), + lists:foldl( + fun + %% Sample interval. + ( {time_span, Val}, St1) -> St1#st {time_span = Val}; + ( {slot_period, Val}, St1) -> St1#st {slot_period = Val}; + ( {histogram_module, Val}, St1) -> St1#st {histogram_module = Val}; + ( {truncate, Val}, St1) when is_boolean(Val); Val == round -> + St1#st{truncate = Val}; + %% Unknown option, pass on to State options list, replacing + %% any earlier versions of the same option. + ({Opt, Val}, St1) -> + St1#st{ opts = [ {Opt, Val} + | lists:keydelete(Opt, 1, St1#st.opts) ] } + end, St, Options). + +-record(sample, {count, total, min, max, extra = []}). +%% Simple sample processor that maintains an average +%% of all sampled values +average_sample(_TS, Val, undefined) -> + #sample{count = 1, + total = Val, + min = Val, + max = Val}; + +average_sample(_TS, Val, #sample{count = Count, + total = Total, + min = Min, + max = Max, extra = X} = S) -> + Count1 = Count + 1, + X1 = if Count1 rem 4 == 0 -> [Val|X]; + true -> X + end, + S#sample{count = Count1, + total = Total + Val, + min = min(Min, Val), + max = max(Max, Val), + extra = X1}. + +%% If average_sample() has not been called for the current time slot, +%% then the provided state will still be 'undefined' +average_transform(_TS, undefined) -> + undefined; + +%% Return the calculated total for the slot and return it as the +%% element to be stored in the histogram. +average_transform(_TS, #sample{count = Count, + total = Total, + min = Min, + max = Max, extra = X}) -> + %% Return the sum of all counter increments received during this slot + {Total / Count, Count, Min, Max, X}. + + +opt_trunc(true, {K,V}) when is_float(V) -> + {K, trunc(V)}; +opt_trunc(round, {K,V}) when is_float(V) -> + {K, round(V)}; +opt_trunc(_, V) -> + V. + +test_new(Opts) -> + init_state(test, Opts). + +%% @equiv test_run(Module, 1) +test_run(Module) -> + test_run(Module, 1). + +%% @doc Test the performance and accuracy of a histogram callback module. +%% +%% This function uses a test set ({@link test_series/0}) and initializes +%% and updates a histogram using the callback module `Module'. +%% +%% The `Module' argument can either be the module name, or `{ModName, Opts}' +%% where `Opts' are options passed on to the histogram module. +%% +%% `Interval' is the gap in milliseconds between the inserts. The test run +%% will not actually wait, but instead manipulate the timestamp. +%% +%% Return value: `[Result1, Result2]', where the results are +%% `{Time1, Time2, Datapoints}'. `Time1' is the time (in microsecs) it took to +%% insert the values. `Time2' is the time it took to calculate all default +%% datapoints. The data set is shuffled between the two runs. +%% +%% To assess the accuracy of the reported percentiles, use e.g. +%% `bear:get_statistics(exometer_histogram:test_series())' as a reference. +%% @end +test_run(Module, Interval) -> + Series = test_series(), + [test_run(Module, Interval, Series), + test_run(Module, Interval, shuffle(Series))]. + +test_run(Module, Int, Series) -> + St = test_new(test_opts(Module)), + {T1, St1} = tc(fun() -> + test_update( + Series, Int, + exometer_util:timestamp(), St) + end), + {T2, Result} = tc(fun() -> + get_value_int(St1, default) + end), + erlang:garbage_collect(), erlang:yield(), + {T1, T2, Result}. + +test_opts(M) when is_atom(M) -> + [{histogram_module, M}]; +test_opts({M, Opts}) -> + [{histogram_module, M}|Opts]. + + +test_update([H|T], Int, TS, St) -> + test_update(T, Int, TS+Int, update_int(TS, H, St)); +test_update([], _, _, St) -> + St. + +tc(F) -> + T1 = os:timestamp(), + Res = F(), + T2 = os:timestamp(), + {timer:now_diff(T2, T1), Res}. + +-spec test_series() -> [integer()]. +%% @doc Create a series of values for histogram testing. +%% +%% These are the properties of the current test set: +%%
+%% 1> rp(bear:get_statistics(exometer_histogram:test_series())).
+%% [{min,3},
+%%  {max,100},
+%%  {arithmetic_mean,6.696},
+%%  {geometric_mean,5.546722009408586},
+%%  {harmonic_mean,5.033909932832006},
+%%  {median,5},
+%%  {variance,63.92468674297564},
+%%  {standard_deviation,7.995291535833802},
+%%  {skewness,7.22743137858698},
+%%  {kurtosis,59.15674033499604},
+%%  {percentile,[{50,5},{75,7},{90,8},{95,9},{99,50},{999,83}]},
+%%  {histogram,[{4,2700},
+%%              {5,1800},
+%%              {6,900},
+%%              {7,1800},
+%%              {8,900},
+%%              {9,720},
+%%              {53,135},
+%%              {83,36},
+%%              {103,9}]},
+%%  {n,9000}]
+%% 
+%% @end +test_series() -> + S = lists:flatten( + [dupl(200,3), + dupl(100,4), + dupl(200,5), + dupl(100,6), + dupl(200,7), + dupl(100,8), + dupl(80,9), + dupl(15,50), 80,81,82,83,100]), + shuffle(S ++ S ++ S ++ S ++ S ++ S ++ S ++ S ++ S). + +dupl(N,V) -> + lists:duplicate(N, V). + +shuffle(List) -> + exometer_util:seed(exometer_util:seed0()), + Randomized = lists:keysort(1, [{exometer_util:uniform(), Item} || Item <- List]), + [Value || {_, Value} <- Randomized]. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_igor.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_igor.erl new file mode 100644 index 0000000..3fd4100 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_igor.erl @@ -0,0 +1,27 @@ +-module(exometer_igor). +-export([parse_transform/2]). + + +parse_transform(Forms, Opts) -> + IgorOpts = lists:append([Os || {attribute,_,compile,{igor,Os}} <- Forms]), + io:fwrite("IgorOpts = ~p~nOpts = ~p", [IgorOpts, Opts]), + Includes = [I || {i,I} <- Opts], + NewForms = + igor:parse_transform( + Forms, [{igor, IgorOpts ++ [{includes, Includes}, + {preprocess, true}]}|Opts]), + fix_for_r16b03(NewForms). + +%% erl_syntax:revert/1 is horribly broken in R16B03. This transform +%% corrects +fix_for_r16b03({'fun',L1,{function,{atom,_,F},{integer,_,A}}}) -> + {'fun',L1,{function,F,A}}; +fix_for_r16b03({'fun',L1,{function,{atom,_,M},{atom,_,F},{integer,_,A}}}) -> + {'fun',L1,{function,M,F,A}}; +fix_for_r16b03(F) when is_tuple(F) -> + list_to_tuple([fix_for_r16b03(X) || X <- tuple_to_list(F)]); +fix_for_r16b03([H|T]) -> + [fix_for_r16b03(H) | fix_for_r16b03(T)]; +fix_for_r16b03(F) -> + F. + diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_info.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_info.erl new file mode 100644 index 0000000..f6d2235 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_info.erl @@ -0,0 +1,102 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @doc Accessor function for exometer data structures +%% +%% This module uses the exprecs transform (see exprecs) +%% to generate accessor functions for exometer data structures. +%% +%% Note that the `value' attribute in `exometer_entry{}' records may not +%% represent the true value of the metric, since exometer entries often +%% have structured values, or are represented as CRDTs for update efficiency. +%% +%% @end +-module(exometer_info). + +-export([status/1, + pp/1, + pp_lookup/1, + pp_find/1, + pp_select/1]). + +-include("exometer.hrl"). +-include_lib("parse_trans/include/exprecs.hrl"). + +-export_type([pp/0]). + +-export_records([exometer_entry]). + +-type pp() :: {atom(), [{atom(), any()}]}. + +-spec status(exometer:entry()) -> enabled | disabled. +%% @doc Return the operational status of the given exometer entry. +%% +%% The `status' attribute is overloaded in the `#exometer_entry{}' record. +%% This function extracts the correct status (`enabled | disabled'). +%% @end +status(#exometer_entry{status = St}) -> + exometer_util:get_status(St). + +-spec pp(tuple() | list()) -> pp() | [pp() | any()]. +%% @doc Pretty-print a record, or list containing records. +%% +%% This function pretty-prints a record as `{RecordName, [{Attr,Value}]}', +%% or, if the input is a list, recognizes records and pretty-prints them, +%% leaving other data structures unchanged. +%% @end +pp(L) when is_list(L) -> + [pp(X) || X <- L]; +pp(X) -> + case '#is_record-'(X) of + true -> + RecName = element(1,X), + {RecName, lists:zip( + '#info-'(RecName,fields), + pp(tl(tuple_to_list(X))))}; + false -> + if is_tuple(X) -> + list_to_tuple(pp(tuple_to_list(X))); + true -> + X + end + end. + +-spec pp_lookup(exometer:name()) -> pp() | undefined. +%% @doc Performs a lookup by name of entry and pretty-prints the result. +%% +%% This function returns `undefined' if the entry cannot be found. +%% @end +pp_lookup(Name) -> + case exometer:info(Name, entry) of + undefined -> + undefined; + Entry -> + pp(Entry) + end. + +-spec pp_find(list()) -> [pp()]. +%% @doc Performs `exometer:find_entries(Path) & returns pretty-printed result. +%% +%% This function calls `exometer:find_entries(Path)', retrieves the entry +%% for each matching metric, and calls `pp(Entry)' for each entry. +%% @end +pp_find(Path) -> + pp([exometer:info(M, entry) || {M,_,_} <- exometer:find_entries(Path)]). + +-spec pp_select(ets:match_spec()) -> [pp()]. +%% @doc Performs `exometer:select(Pattern) & returns pretty-printed result. +%% +%% This function calls `exometer:select(Pattern)', retrieves the entry +%% for each matching metric, and calls `pp(Entry)' for each entry. +%% +%% Note that the match body of the select pattern must produce the full +%% `{Name, Type, Status}' object, e.g. by specifying ['$_']. +%% @end +pp_select(Pat) -> + pp([exometer:info(M, entry) || {M,_,_} <- exometer:select(Pat)]). diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_probe.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_probe.erl new file mode 100644 index 0000000..c846aff --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_probe.erl @@ -0,0 +1,825 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @doc Interface library for managing probes.
+%% +%% This library contains the main API for accessing all probes +%% executing in exometer. +%% +%% All exported functions in the `exomter_probe' module are invoked +%% by the `exometer' module; a developer will not have to call +%% `exometer_probe' functions directly. +%% +%% A probe is an implementation of the `exometer_probe' behavior +%% which runs in its own process in order to collect data to be +%% handled and reported by exometer. The implementation will be +%% invoked through the `exomoeter_probe' module, which, as stated +%% above, in its turn is invoked by the `exometer' module. +%% +%% A custom exometer probe is invoked by mapping a type to the module +%% name of the custom exometer probe module. All metrics created with the +%% given type will trigger the invocation of the new probe module. See +%% {@section Configuring type - entry maps} for details on how to setup +%% such maps. +%% +%% If the data can be collected at a high speed, and without +%% blocking, an `exometer_entry' implementation can be used instead +%% to do the gathering in-process. +%% +%% A probe is created throgh the `exomter_probe:new/3' call, which in +%% its turn is called by `exometer:new/3'. During probe creation, a +%% new process is spawned to handle the probe and call its +%% implementation. While the created process is not a gen_server, it +%% behaves similarly and provides a state to all implementation +%% calls. +%% +%% Once running, the probe collects data from a subsystem, such as +%% `/proc', `sysfs', and `netlink', through timer-based calls to +%% `probe_sample/1' or explicit calls to `probe_update/2'. +%% +%% A probe implementation can support any number of data points, +%% where each data point is a specifric sample from the probe. For +%% example, a probe that measures network traffic would have +%% `rx_packets', `tx_packets', `errors', `dropped', and other data +%% points reported by `ifconfig(8)' and `ip(8)'. +%% +%% Values are retrieved from the probe through the `probe_get_value/2' +%% call, which specifies the data points to be returned. The probe is +%% expected to gather the given data points and return them to the +%% caller. +%% +%% == The probe callback interface == +%% +%% The following functions are to be implemented and exported by a probe +%% implementation. +%% +%% === behaviour/0 === +%% +%% The `behaviour/0' function for an entry implementation should return +%% the atom `probe'. This function will be involved by the +%% exometer system in order to determine if a callback is +%% an entry or a probe. +%% +%% === probe_init/3 === +%% The `probe_init/3' function is invoked as follows: +%% +%%
+%%      probe_init(Name, Type, Options)
+%% +%% The implementation shall initiate the probe, create the +%% necessary state, and return it for furure access +%% through `probe_update/2', `probe_sample/1' and `get_value/2' calls. +%% +%% The arguments are as follows: +%% +%% + `Name' +%% Specifies the name of the metric to be created as a list of atoms. +%% +%% + `Type' +%% Specifies the type provided to the `exometer:new/3' call (before it +%% was translated by the type - exometer probe map). It can be used if several +%% different types are mapped to the same probe module. +%% +%% + `Options' +%% Specifies an option list that contains additional setup directives to +%% the probe. The actual options to support are a combination of the +%% standard options, described below, and probe specific options +%% processed by `probe_init/3'. +%% +%% Standard options are processed directly by `new/3', before +%% `probe_init/3' is calledm and are as follows: +%% +%% + `{priority, P}' +%% Will be forwarded by the probe's process to `erlang:process_flag/2'. +%% +%% + `{min_heap_size, S}' +%% Will be forwarded by the probe's process to `erlang:process_flag/2'. +%% +%% + `{min_bin_vheap_size, S}' +%% Will be forwarded by the probe's process to `erlang:process_flag/2'. +%% +%% + `{sensitive, true | false}' +%% Will be forwarded by the probe's process to `erlang:process_flag/2'. +%% +%% + `{sample_interval, t}' +%% Specifies the interval, in milliseconds, that `exometer_probe:sample/1'. +%% should be invoked at. +%% +%% The `probe_init/3' implementation is invoked by `exometer:new/3', +%% which calls `exometer_probe:new/3', which invokes the probe +%% implementation.. +%% +%% The `probe_init/3' function shall return `{ok, State}' where State +%% is a tuple that will be provided as a the `State' argument to all +%% future probe implementation calls for the metric. +%% +%% If the `sample_interval' option has been specified in `Opts', +%% probe_sample/2' will be invoked immediately after `probe_init/2' +%% returns to retrieve a first sample. After that, `probe_sample/2' +%% will repeatedly will be called by the probe process at the +%% millisecond-specified interval. +%% +%% Should `probe_init/3' return antyhing else but `{ok, State}', +%% invoking `new/3' call will fail. +%% +%% +%% === probe_terminate/1 === +%% The `probe_terminate/1' function is invoked as follows: +%% +%%
+%%      probe_terminate(State)
+%% +%% The custom probe shall release any resources associated with the +%% given state and return `ok'. +%% +%% The arguments are as follows: +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% +%% The `probe_terminate/1' implementation is invoked by `exometer:delete/1', which +%% calls `exometer_probe:delete/3', which invokes the probe +%% implementation. +%% +%% +%% === probe_setopts/3 === +%% The `probe_setopts/2' function is invoked as follows: +%% +%%
+%%      probe_setopts(Entry, Opts, State)
+%% +%% The `probe_setopts/4' implementation is invoked by +%% `exometer:setopts/3', which calls `exometer_probe:setopts/3', +%% which invokes the probe implementation. +%% +%% The implementation of this function shall modify the options of a +%% probe. The `setopts/3' function, which will process standard +%% options before invoking `probe_setopts/4' with the remaining +%% options. See the documentation for `probe_init/3' for details. +%% +%% The arguments are as follows: +%% +%% + `Entry' +%% The (opaque) exometer entry record. See {@link exometer_info} for +%% information on how to inspect the data structure. +%% +%% + `Opts' +%% The probe-specific options to be processed. +%% +%% + `Status' +%% The new status of the entry. +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequently +%% modified by other probe implementation calls. +%% +%% This function shall return `{ok, NewState}' where `NewState' is +%% the modified probe state that incorporates the new options. +%% +%% +%% === probe_update/2 === +%% The `probe_update/2' function is invoked as follows: +%% +%%
+%%      probe_update(Value, State)
+%% +%% Incorporate a new value into the metric maintained by the metric. +%% +%% The arguments are as follows: +%% +%% + `Value' +%% The value to integrate. +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% This function can be called outside the periodic `probe_sample/1/' +%% call to have the probe process a value given in `Value'. +%% +%% The `probe_update/2' implementation is invoked by `exometer:update/2', which +%% calls `exometer_probe:update/4', which invokes the probe +%% implementation. +%% +%% Once processed, `probe_update/2' shall return `{ok, NewState}', +%% where `NewState' contains the new probe state with the processed +%% value. +%% +%% +%% === probe_get_value/2 === +%% The `probe_get_value/2' function is invoked as follows: +%% +%%
+%%      probe_get_value(DataPoints, State)
+%% +%% The `probe_get_value/2' implementation shall retrieve the value of +%% one or more data points from the probe. +%% +%% The arguments are as follows: +%% +%% + `DataPoints' +%% List of data point atoms to retrieve values for. +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% The `probe_get_value/2' implementation is invoked by +%% `exometer:get_value/2', which calls `exometer_probe:get_value/4', +%% which invokes the probe implementation. +%% +%% If `exometer:get_value/2' is invoked with `default' as a single +%% data point, the probe's `probe_get_datapoints/1' function will be +%% called to retrieve all data points supported by the probe +%% implementation. `probe_get_value/2' will then be called with the +%% returned set of data points provided as an argument. +%% +%% This function shall return the value of all data points provided in +%% `DataPoints', given that they are supported. +%% +%% The list in the returned tuple shall have the format: +%% +%%
+%%      [{ DP, Val}, ...]
+%% +%% Where `DP' one of the data points in the `DataPoints' argument, and +%% `Val' is the value of that data point. +%% +%% If one of the argument-provided data points are not supported by the probe, +%% the tuple returned for that data point shall be `{ DP, {error, undefined}'. +%% +%% For example, if the provided `DataPoint' argument is set to `[ min, +%% max, xyzzy ]', and only `min' and `max' are supported +%% by the probe, the returned list shall look like below: +%% +%%
+%%      [{ min, 0.1265 }, { max, 3338.21 }, { xyzzy, { error, unsupported } ]
+%% +%% The `probe_get_value/2' implementation shall return `{ok, List}', +%% where `List' is the list of data points and their values described +%% above. No new state is returned by this function. +%% +%% +%% === probe_get_datapoints/1 === +%% The `probe_get_datapoints/1' function is invoked as follows: +%% +%%
+%%      probe_get_datapoints(State)
+%% +%% The `probe_get_datapoints/1' shall return a list with all data points +%% supported by the probe +%% +%% The arguments are as follows: +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% The `probe_get_datapoints/1' implementation is invoked by +%% `exometer:info/2', which calls `exometer_probe:get_datapoints/3', +%% which invokes the probe implementation. +%% +%% In cases where `exometer:get_value/2' is called with `default' as a +%% single data point, `probe_get_datapoints/1' is also called to +%% retrieve a list of all supported data points, which is then +%% forwarded to `probe_get_value/2'. +%% +%% The implementation of `probe_get_datapoints/1' shall return `{ok, DpList}', +%% where `DpList' is a list of data point atoms supported by the probe. +%% +%% +%% === probe_reset/1 === +%% The `probe_reset/1' function is invoked as follows: +%% +%%
+%%      probe_reset(State)
+%% +%% The `probe_reset/1' shall reset the state of the probe to its initial state. +%% +%% The arguments are as follows: +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% +%% The `probe_reset/1' implementation is invoked by +%% `exometer:reset/1', which calls `exometer_probe:reset/3', which +%% invokes the probe implementation. +%% +%% The implementation of `probe_reset/1' shall return `{ok, +%% NewState}', where `NewState' contains the reset state of the probe. +%% +%% +%% === probe_sample/1 === +%% The `probe_sample/1' function is invoked as follows: +%% +%%
+%%      probe_sample(State)
+%% +%% The `probe_sample/1' implementation shall sample data from the +%% subsystem the probe is integrated with. +%% +%% The arguments are as follows: +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% When invoked, `probe_sample/1' is expected to interface the +%% sub-system (/proc, /sysfs, etc) monitored by the probe, extract the +%% relevant data from it, and return an updated probe state that +%% incorporates the extracted data. +%% +%% The `probe_sample/1' function is invoked by the probe thread at +%% intervals specified by the `{sample_interval, Intv}' option +%% provided to `exometer_probe:new/3'. If this option is missing, or +%% set to infinity, `probe_sample/1' will never be called. +%% +%% The implementation of `probe_sample/1' shall return `{ok, +%% NewState}', where `NewState' contains the new state of the probe +%% with the sampled data integrated into it. +%% +%% +%% === probe_handle_msg/2 === +%% The `probe_handle_msg/2' function is invoked as follows: +%% +%%
+%%      probe_handle_msg(Msg, State)
+%% +%% The `probe_handle_msg/1' is invoked to process messages received +%% by the probe process. +%% +%% The arguments are as follows: +%% +%% + `State' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% + `Msg' +%% The probe state, originally returned by `probe_init/3' and subsequentially +%% modified by other probe implementation calls. +%% +%% The implementation of this function will be called by the probe's +%% process when it receives a message that is not recognized by the +%% internal receive loop. +%% +%% The implementation of `probe_handle_msg/2' shall return `{ok, +%% NewState}', where `NewState' contains the new state of the probe +%% that reflects the processed message. +%% +%% == Fault tolerance == +%% Probes are supervised by the `exometer_admin' process, and can be restarted +%% after a crash. Restart parameters are provided via the option +%% `{restart, Params}', where `Params' is a list of `{Frequency, Action}' +%% tuples. `Frequency' is either `{Count, MilliSecs}' or ``'_''', and +%% the corresponding `Action :: restart | disable | delete' will be performed +%% if the frequency of restarts falls within the given limit. +%% +%% For example, `[{{3, 1000}, restart}]' will allow 3 restarts within a 1-second +%% window. The matching is performed from top to bottom, and the first matching +%% pattern is acted upon. If no matching pattern is found, the default action +%% is `delete'. A pattern ``{'_', Action}'' acts as a catch-all, and should +%% be put last in the list. +%% +%% It is also possible to specify ``{{Count, '_'}, Action}'', which means +%% that a total of `Count' restarts is permitted, regardless of how far apart +%% they are. The count is reset after each restart. +%% +%% Example: +%%
+%%   {restart, [{{3,1000}, restart},   % up to 3 restarts within 1 sec
+%%              {{4,'_'} , disable},   % a total of up to 4 restarts
+%%              {'_'     , delete}]}   % anything else
+%% 
+%% @end +-module(exometer_probe). + +-behaviour(exometer_entry). + +%% exometer_entry callbacks +-export( + [ + behaviour/0, + new/3, + delete/3, + get_datapoints/3, + get_value/3, get_value/4, + update/4, + reset/3, + sample/3, + setopts/3 + ]). + +-export([start_probe/1, + stop_probe/1]). + +-export([restart/4]). + +-include("exometer.hrl"). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-record(st, { + name, + type, + module = undefined, + mod_state, + sample_timer, + sample_interval = infinity, %% msec. infinity = disable probe_sample() peridoc calls. + opts = [] + }). + +-type name() :: exometer:name(). +-type options() :: exometer:options(). +-type type() :: exometer:type(). +-type mod_state() :: any(). +-type data_points() :: [atom()]. +-type probe_reply() :: ok + | {ok, mod_state()} + | {ok, any(), mod_state()} + | {noreply, mod_state()} + | {error, any()}. +-type probe_noreply() :: ok + | {ok, mod_state()} + | {error, any()}. + +-callback behaviour() -> exometer:behaviour(). +-callback probe_init(name(), type(), options()) -> probe_noreply(). +-callback probe_terminate(mod_state()) -> probe_noreply(). +-callback probe_setopts(exometer:entry(), options(), mod_state()) -> + probe_reply(). +-callback probe_update(any(), mod_state()) -> probe_noreply(). +-callback probe_get_value(data_points(), mod_state()) -> probe_reply(). +-callback probe_get_datapoints(mod_state()) -> {ok, data_points()}. +-callback probe_reset(mod_state()) -> probe_noreply(). +-callback probe_sample(mod_state()) -> probe_noreply(). +-callback probe_handle_msg(any(), mod_state()) -> probe_noreply(). + +%% FIXME: Invoke this. +-callback probe_code_change(any(), mod_state(), any()) -> {ok, mod_state()}. + +new(Name, Type, [{arg, Module}|Opts]) -> + Restart = proplists:get_value(restart, Opts, default_restart()), + SpawnOpts = proplists:get_value(spawn_opts, Opts, spawn_opts()), + { ok, exometer_proc:spawn_process( + Name, fun() -> + init(Name, Type, Module, Opts) + end, proc_opts(Name, Module, Restart, SpawnOpts)) + }; + +new(Name, Type, Options) -> + %% Extract the module to use. + {value, { module, Module }, Opts1 } = lists:keytake(module, 1, Options), + new(Name, Type, [{arg, Module} | Opts1]). + +proc_opts(Name, Module, Restart, SpawnOpts) when is_list(Restart) -> + proc_opts_(Name, Module, on_error_init(Restart), SpawnOpts); +proc_opts(Name, Module, Restart, SpawnOpts) -> + proc_opts_(Name, Module, Restart, SpawnOpts). + +proc_opts_(Name, Module, Restart, SpawnOpts) -> + OnError = on_error(Name, Module, Restart, SpawnOpts), + [{on_error, OnError}, {spawn_opts, SpawnOpts}]. + + +spawn_opts() -> + [{fullsweep_after, 10}]. + +on_error_init(R) when is_list(R) -> + {0, [], max_time(R), R}. + +on_error(Name, Module, R, SpawnOpts) -> + {restart, {?MODULE, restart, [Name, Module, R, SpawnOpts]}}. + +check_restart(Restart) -> + TS = exometer_util:timestamp(), + check_restart(TS, Restart). + +check_restart(TS, {Total, Hist, MaxT, R}) when is_integer(MaxT) -> + NewTotal = Total + 1, + Oldest = TS - MaxT, + NewHist = [TS | [T || T <- Hist, + T > Oldest]], + {action(NewTotal, NewHist, MaxT, R), {NewTotal, NewHist, MaxT, R}}; +check_restart(_TS, {Total, Hist, '_', R}) -> + NewTotal = Total + 1, + {action(NewTotal, [], '_', R), {NewTotal, Hist, '_', R}}. + + +max_time([{{_,T},_}|Rest]) -> + max_time(Rest, T); +max_time([{_,_}|Rest]) -> + max_time(Rest); +max_time([]) -> + '_'. + +max_time([{{_,T},_}|Rest], Max) when is_integer(T) -> + max_time(Rest, erlang:max(T, Max)); +max_time([_|Rest], Max) -> + max_time(Rest, Max); +max_time([], Max) -> + Max. + +action(_Total, _Hist, _MaxT, [{'_', Action}|_]) -> + Action; +action(Total, Hist, MaxT, [{{N,'_'}, Action}|R]) -> + if N >= Total -> Action; + true -> action(Total, Hist, MaxT, R) + end; +action(Total, Hist, MaxT, [{{N,T}, Action}|R]) -> + case within(Hist, N, T) of + true -> Action; + false -> + action(Total, Hist, MaxT, R) + end; +action(_, _, _, []) -> + delete. + +within([T|Ts], N, Tm) -> + within(Ts, 1, N, T - Tm); +within([], _, _) -> + false. + +within([H|T], N, M, Lim) when H > Lim -> + N1 = N + 1, + if N1 =< M -> + within(T, N1, M, Lim); + true -> + false + end; +within(_, _, _, _) -> + true. + +restart(Name, Module, Error, SpawnOpts) -> + Type = exometer:info(Name, type), + Opts = exometer:info(Name, options), + case check_restart(Error) of + {restart, Error1} -> + {ok, exometer_proc:spawn_process( + Name, fun() -> + init(Name, Type, Module, Opts) + end, + proc_opts(Name, Module, Error1, SpawnOpts))}; + {Other, _} when Other==delete; Other==disable -> + Other + end. + +default_restart() -> + [{{3, 60000}, restart}, + {'_', disable}]. + +%% Should never be called directly for exometer_probe. +behaviour() -> + entry. + +delete(_Name, _Type, undefined) -> + ok; +delete(_Name, _Type, Pid) when is_pid(Pid) -> + exometer_proc:cast(Pid, delete). + +get_value(_Name, _Type, Pid) when is_pid(Pid) -> + exometer_proc:call(Pid, {get_value, default}). + +get_value(_Name, _Type, Pid, DataPoints) when is_pid(Pid) -> + exometer_proc:call(Pid, {get_value, DataPoints}). + +get_datapoints(_Name, _Type, Pid) when is_pid(Pid) -> + exometer_proc:call(Pid, get_datapoints). + +setopts(#exometer_entry{ref = Pid} = E, Opts, Status) when is_pid(Pid) -> + exometer_proc:cast(Pid, {setopts, E, Opts, Status}). + +update(_Name, Value, _Type, Pid) when is_pid(Pid) -> + exometer_proc:cast(Pid, {update, Value}). + +reset(_Name, _Type, Pid) when is_pid(Pid) -> + exometer_proc:cast(Pid, reset). + +sample(_Name, _Type, Pid) when is_pid(Pid) -> + exometer_proc:cast(Pid, sample). + + +%% === housekeeping functions used e.g. at enable/disable + +%% @private +stop_probe(#exometer_entry{name = Name, + type = Type, + ref = Ref}) -> + exometer_cache:delete_name(Name), + if is_pid(Ref) -> + try exometer_probe:delete(Name, Type, Ref) + catch + error:_ -> + kill_probe(Ref) + end; + true -> + ok + end. + +kill_probe(Ref) when is_pid(Ref) -> + exometer_admin:demonitor(Ref), + exit(Ref, kill). + +%% @private +start_probe(#exometer_entry{module = Module, + name = Name, + type = Type, + options = Opts}) -> + new(Name, Type, [{ arg, Module} | Opts ]). + +%% == Probe implementation + +init(Name, Type, Mod, Opts) -> + process_flag(min_heap_size, 40000), + {St0, Opts1} = process_opts(Opts, #st{name = Name, + type = Type, + module = Mod}), + St = St0#st{opts = Opts1}, + + %% Create a new state for the module + case {Mod:probe_init(Name, Type, St#st.opts), + St#st.sample_interval} of + { ok, infinity} -> + %% No sample timer to start. Return with undefined mod state + loop(St#st{ mod_state = undefined }); + + {{ok, ModSt}, infinity} -> + %% No sample timer to start. Return with the mod state returned by probe_init. + loop(St#st{ mod_state = ModSt }); + + {ok, _} -> + %% Fire up the timer, with undefined mod + loop(sample(St#st{ mod_state = undefined })); + + {{ok, ModSt}, _ } -> + %% Fire up the timer. + loop(sample(St#st{ mod_state = ModSt })); + + {{error, Reason}, _} -> + %% FIXME: Proper shutdown. + {error, Reason} + end. + +loop(St) -> + receive Msg -> + loop(handle_msg(Msg, St)) + end. + + +handle_msg(Msg, St) -> + Module = St#st.module, + case Msg of + {system, From, Req} -> + exometer_proc:handle_system_msg( + Req, From, St, fun(St1) -> loop(St1) end); + {exometer_proc, {From, Ref}, {get_value, default} } -> + {ok, DataPoints} = Module:probe_get_datapoints(St#st.mod_state), + {Reply, NSt} = + process_probe_reply(St, Module:probe_get_value(DataPoints, + St#st.mod_state)), + From ! {Ref, Reply }, + NSt; + + {exometer_proc, {From, Ref}, {get_value, DataPoints} } -> + {Reply, NSt} = + process_probe_reply(St, Module:probe_get_value(DataPoints, + St#st.mod_state)), + From ! {Ref, Reply }, + NSt; + + {exometer_proc, {From, Ref}, get_datapoints } -> + {ok, DataPoints} =Module:probe_get_datapoints(St#st.mod_state), + From ! {Ref, DataPoints }, + St; + + {exometer_proc, { update, Value } } -> + Res = process_probe_noreply(St, Module:probe_update(Value, St#st.mod_state)), + Res; + + {exometer_proc, reset } -> + process_probe_noreply(St, Module:probe_reset(St#st.mod_state)); + + {exometer_proc, sample } -> + process_probe_noreply(St, Module:probe_sample(St#st.mod_state)); + + {exometer_proc, {From, Ref}, {setopts, Entry, Options}} -> + %% Extract probe-level options (sample_interval) + {NSt, Opts1} = process_opts(Options, St), + + {Reply, NSt1} = %% Call module setopts for remainder of opts + process_probe_reply( + NSt, Module:probe_setopts(Entry, Opts1, NSt#st.mod_state)), + + From ! {Ref, Reply }, + %% Return state with options and any non-duplicate original opts. + NSt1#st { + opts = Opts1 ++ + [{K,V} || {K,V} <- St#st.opts, not lists:keymember(K,1,Opts1) ] + }; + + {timeout, _TRef, {exometer_proc, sample_timer}} -> + sample(St); + + {exometer_proc, delete} -> + Module:probe_terminate(St#st.mod_state), + exometer_proc:stop(); + + {exometer_proc, code_change} -> + Module:probe_terminate(St#st.mod_state), + exometer_proc:stop(); + + Other -> + process_probe_noreply(St, Module:probe_handle_msg(Other, St#st.mod_state)) + end. + + +process_probe_reply(St, ok) -> + {ok, St}; + +process_probe_reply(St, {ok, Reply}) -> + {Reply, St}; + +process_probe_reply(St, {ok, Reply, ModSt}) -> + {Reply, St#st { mod_state = ModSt }} ; + +process_probe_reply(St, {noreply, ModSt}) -> + {noreply, St#st { mod_state = ModSt }}; + +process_probe_reply(St, {error, Reason}) -> + {{error, Reason}, St}; + +process_probe_reply(St, Err) -> + {{error, { unsupported, Err}}, St}. + + +process_probe_noreply(St, {ok, ModSt}) -> + St#st{mod_state=ModSt}; + +process_probe_noreply(St, _) -> + St. + +%% =================================================================== + +sample(#st{module = Mod, mod_state = ModSt} = St) -> + St1 = restart_timer(sample, St), + process_probe_noreply(St1, Mod:probe_sample(ModSt)). + +restart_timer(sample, #st{sample_interval = Int} = St) -> + St#st{sample_timer = start_timer(Int, {exometer_proc, sample_timer})}. + +start_timer(infinity, _) -> + undefined; + +start_timer(T, Msg) when is_integer(T), T >= 0, T =< 16#FFffFFff -> + erlang:start_timer(T, self(), Msg). + +process_opts(Options, #st{} = St) -> + exometer_proc:process_options(Options), + process_opts(Options, St, []). + +process_opts([{sample_interval, Val}|T], #st{} = St, Acc) -> + process_opts(T, St#st{ sample_interval = Val }, Acc); +process_opts([Opt|T], St, Acc) -> + process_opts(T, St, [Opt | Acc]); +process_opts([], St, Acc) -> + {St, lists:reverse(Acc)}. + +%% EUnit + +-ifdef(TEST). + +restart_test_() -> + [ + ?_test(t_restart_1()), + ?_test(t_restart_2()) + ]. + +t_restart_1() -> + R = [{{3,1000}, restart}, % up to 3 restarts within 1 sec + {{4,'_'} , disable}, % a total of up to 4 restarts + {'_', delete}], % anything else + OE = on_error_init(R), + {restart, OE1} = check_restart(100, OE), + {restart, OE2} = check_restart(200, OE1), + {restart, OE3} = check_restart(300, OE2), + {disable, OE4} = check_restart(400, OE3), + {delete , _} = check_restart(500, OE4), + ok. + +t_restart_2() -> + R = [], + {delete, _} = check_restart({1, [500], 1000, R}), + ok. + +-endif. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_proc.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_proc.erl new file mode 100644 index 0000000..7aa3248 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_proc.erl @@ -0,0 +1,235 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +%% @doc Utility functions for the `exometer_proc' probe type. +%% +%% The `exometer_proc' probe type is a vanilla Erlang process. All messages +%% must be handled explicitly. +%% +%% The functions in this module can be used by custom types +%% (see e.g. {@link exometer_spiral}). When the `exometer_proc' type is +%% specified explicitly, the process is started automatically, and the +%% following messages: +%% ``` lang="erlang" +%% {exometer_proc, {update, Value}} +%% {exometer_proc, sample} +%% {exometer_proc, reset} +%% {exometer_proc, {Pid,Ref}, {get_value, Datapoints}} -> {Ref, Reply} +%% {exometer_proc, {Pid,Ref}, {setopts, Opts}} -> {Ref, Reply} +%% {exometer_proc, stop} +%% ''' +%% @end + +%% PLAIN_FSM not parse transform +%% Receive system message and call library functions +%% Use plain_fsm:spawn() +%% handle system message +%% Implement system message callbacks + + + +-module(exometer_proc). + +-export([spawn_process/2, + spawn_process/3, + cast/2, + call/2, + process_options/1, + stop/0]). + +-export([handle_system_msg/4]). + +%% Callbacks used by the sys.erl module +-export([system_continue/3, + system_terminate/4, + system_code_change/4, + format_status/2]). + +%% Internal housekeeping records. The split into two records is used +%% to separate the variables that are passed as explicit arguments in +%% the sys API from the ones that are embedded in the 'state'. +%% (the #sys{} record is the one being embedded...) +%% +-record(sys, {cont,mod,name}). +-record(info, {parent, + debug = [], + sys = #sys{}}). + + +-spec spawn_process(exometer:name(), fun(() -> no_return())) -> pid(). +%% @doc Spawn an `exometer_proc' process. +%% +%% This function sets up appropriate monitoring, and calls the function `F' +%% which needs to initialize the probe and enter an event loop. +%% (Note: `exometer_proc' processes are responsible for their own event loop). +%% @end +spawn_process(Name, F) when is_function(F,0) -> + {_, Mod} = erlang:fun_info(F, module), + Parent = self(), + proc_lib:spawn(fun() -> + exometer_admin:monitor(Name, self()), + init(Name, Mod, F, Parent) + end). + +spawn_process(Name, F, Opts) -> + {_, Mod} = erlang:fun_info(F, module), + Parent = self(), + SpawnOpts = proplists:get_value(spawn_opts, Opts, []), + OnError = proplists:get_value(on_error, Opts, delete), + proc_lib:spawn_opt(fun() -> + exometer_admin:monitor(Name, self(), OnError), + init(Name, Mod, F, Parent) + end, SpawnOpts). + +init(Name, Mod, StartF, ParentPid) -> + I = #info{parent = ParentPid}, + Sys = I#info.sys, + put({?MODULE, info}, I#info{sys = Sys#sys{mod = Mod, name = Name}}), + StartF(). + +-spec cast(pid() | atom(), Msg::any()) -> ok. +%% @doc Send an asynchronous message to an `exometer_proc' process. +%% +%% This function sends a message on the form `{exometer_proc, Msg}' to the +%% given process. +%% @end +cast(Pid, Msg) -> + Pid ! {exometer_proc, Msg}, + ok. + +-spec call(pid() | atom(), any()) -> any(). +%% @doc Make a synchronous call to an `exometer_proc' process. +%% +%% Note that the receiving process must explicitly handle the message in a +%% `receive' clause and respond properly. The protocol is: +%% ``` lang="erlang" +%% call(Pid, Req) -> +%% MRef = erlang:monitor(process, Pid), +%% Pid ! {exometer_proc, {self(), MRef}, Req}, +%% receive +%% {MRef, Reply} -> Reply +%% after 5000 -> error(timeout) +%% end. +%% ''' +call(Pid, Req) -> + MRef = erlang:monitor(process, Pid), + Pid ! {exometer_proc, {self(), MRef}, Req}, + receive + {MRef, Reply} -> + erlang:demonitor(MRef, [flush]), + Reply; + {'DOWN', MRef, _, _, Reason} -> + error(Reason) + after 5000 -> + error(timeout) + end. + +-spec stop() -> no_return(). +%% @doc Terminate probe process in an orderly way. +%% +%% This function doesn't return. +%% @end +stop() -> + exometer_admin:demonitor(self()), + exit(normal). + +-spec process_options([{atom(), any()}]) -> ok. +%% @doc Apply process_flag-specific options. +%% @end +process_options(Opts) -> + Defaults = exometer_util:get_env(probe_defaults, []), + lists:foreach( + fun({priority, P}) when P==low; P==normal; P==high; P==max -> + process_flag(priority, P); + ({min_heap_size, S}) when is_integer(S), S > 0 -> + process_flag(min_heap_size, S); + ({min_bin_vheap_size, S}) when is_integer(S), S > 0 -> + process_flag(min_bin_vheap_size, S); + ({sensitive, B}) when is_boolean(B) -> + process_flag(sensitive, B); + %% ({scheduler, I}) when is_integer(I), I >= 0 -> + %% process_flag(scheduler, I); + (_) -> + ok + end, Opts ++ Defaults). + + +handle_system_msg(Req, From, State, Cont) -> + #info{parent = Parent, debug = Debug, sys = Sys} = I = + get({?MODULE, info}), + Sys1 = Sys#sys{cont = Cont}, + put({?MODULE,info}, I#info{sys = Sys1}), + sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, + {Sys1, State}). + +system_continue(Parent, Debug, IntState) -> + #info{} = I = get({?MODULE, info}), + {#sys{cont = Cont} = Sys, State} = IntState, + put({?MODULE, info}, I#info{parent = Parent, debug = Debug, + sys = Sys}), + continue(State, Cont). + +continue(State, Cont) when is_function(Cont) -> + Cont(State); +continue(State, Cont) when is_atom(Cont) -> + #info{sys = #sys{mod = Mod}} = get({?MODULE, info}), + Mod:Cont(State). + +system_terminate(Reason, _Parent, _Debug, _State) -> + exit(Reason). + +system_code_change(IntState, Module, OldVsn, Extra) -> + {Sys,State} = IntState, + case apply(Module, code_change, [OldVsn, State, Extra]) of + {ok, NewState} -> + {ok, {Sys, NewState}}; + {ok, NewState, NewOptions} when is_list(NewOptions) -> + NewSys = process_sys_opts(NewOptions, Sys), + {ok, {NewSys, NewState}} + end. + +process_sys_opts(Opts, Sys) -> + lists:foldl( + fun({cont, Cont}, S) -> + S#sys{cont = Cont}; + ({mod, Mod}, S) -> + S#sys{mod = Mod}; + ({name, Name}, S) -> + S#sys{name = Name} + end, Sys, Opts). + +format_status(Opt, StatusData) -> + [PDict, SysState, Parent, Debug, IntState] = StatusData, + {#sys{mod = Mod, name = Name}, State} = IntState, + NameTag = if is_pid(Name) -> + pid_to_list(Name); + is_atom(Name) -> + Name; + true -> + lists:flatten(io_lib:fwrite("~w", [Name])) + end, + Header = lists:concat(["Status for exometer_proc ", NameTag]), + Log = sys:get_debug(log, Debug, []), + Specific = + case erlang:function_exported(Mod, format_status, 2) of + true -> + case catch Mod:format_status(Opt, [PDict, State]) of + {'EXIT', _} -> [{data, [{"State", State}]}]; + Else -> Else + end; + _ -> + [{data, [{"State", State}]}] + end, + [{header, Header}, + {data, [{"Status", SysState}, + {"Module", Mod}, + {"Parent", Parent}, + {"Logged events", Log} | + Specific]}]. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_report.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report.erl new file mode 100644 index 0000000..6316d90 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report.erl @@ -0,0 +1,1890 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +%% @doc +%% +%% A custom reporter plugin, executing in its own process, can receive +%% updated metric values by having its module referenced in an +%% `exometer_report:subscribe()' call. +%% +%% The reporter, once it is setup as a subscription destination, will +%% receive periodic calls with updated metrics and data points to be +%% reported. +%% +%% Each custom plugin implements the exometer_report behavior. +%% +%% The life cycle of a a custom reporter consists of the following steps. +%% +%% + Reporter creation
`exometer_init/1' is invoked by exometer when +%% the reporter is configured in the reporter application +%% environment. See {@section Configuring reporter plugins} for +%% details. +%% +%% + Setup subscription
When `exometer_report:subscribe()' is called, targeting the +%% custom report plugin, the gen_server's `exometer_subscribe()' function +%% will be invoked to notify the plugin of the new metrics subscription. +%% +%% + Report Metrics
Updated metrics are sent by exometer to the +%% `exometer_report/4'. All reported metrics will have been notified +%% to the recipient through a previous `exometer_report()' function. +%% +%% + Tear down subscription
When `exometer_report:unsubscribe()' is called, addressing the +%% custom report plugin, the recipient's `exometer_unsubscribe()' function +%% will be invoked to notify the plugin of the deleted subscription. +%% +%% +%% The following chapters details each of the callbacks to be implemented +%% in the exometer_report behavior. +%% +%% === exometer_init/1 === +%% +%% The `exometer_init()' function is invoked as follows: +%% +%%
+%%      exometer_init(Options)
+%% +%% The custom reporter plugin should create the necessary state for the +%% new plugin and return a state to be used in future plugin calls. +%% +%% + `Options'
Provides the prop list with attributes from the application environment +%% for the cusom recipient. See {@section Configuring reporter plugins} for +%% +%% The `exomoeter_init()' function should return `{ok, State}' where +%% State is a tuple that will be provided as a reference argument to +%% future calls made into the plugin. Any other return formats will +%% cancel the creation of the custom reporting plugin. +%% +%% +%% === exometer_subscribe/5 === +%% +%% The `exometer_subscribe()' function is invoked as follows: +%% +%%
+%%      exometer_subscribe(Metric, DataPoint, Interval, Extra, State)
+%% +%% The custom plugin can use this notification to modify and return its +%% state in order to prepare for future calls to `exometer_report()' with +%% the given meteric and data point. +%% +%% + `Metric'
Specifies the metric that is now subscribed to by the plugin +%% as a list of atoms. +%% +%% + `DataPoint'
Specifies the data point within the subscribed-to metric +%% as an atom, or a list of atoms. +%% +%% + `Interval'
Specifies the interval, in milliseconds, that the +%% subscribed-to value will be reported at, or an atom, referring to a named +%% interval configured in the reporter. +%% +%% + `Extra'
Specifies the extra data, which can be anything the reporter +%% can understand. +%% +%% + `State'
Contains the state returned by the last called plugin function. +%% +%% The `exomoeter_subscribe()' function should return `{ok, State}' where +%% State is a tuple that will be provided as a reference argument to +%% future calls made into the plugin. Any other return formats will +%% generate an error log message by exometer. +%% +%% +%% === exometer_report/4 === +%% +%% The `exometer_report()' function is invoked as follows: +%% +%%
+%%      exometer_report(Metric, DataPoint, State)
+%% +%% The custom plugin will receive this call when a periodic subscription +%% triggers and wants to report its current value through the plugin. +%% The plugin should export the value to the external system it interfaces and +%% return its possibly modified state. +%% +%% + `Metric'
Specifies the metric that is to be reported. +%% +%% + `DataPoint'
Specifies the data point or data points within the metric +%% to be reported. +%% +%% + `State'
Contains the state returned by the last called plugin function. +%% +%% The `exometer_report()' function should return `{ok, State}' where +%% State is a tuple that will be provided as a reference argument to +%% future calls made into the plugin. Any other return formats will +%% generate an error log message by exometer. +%% +%% +%% === exometer_unsubscribe/4 === +%% +%% The `exometer_unsubscribe()' function is invoked as follows: +%% +%%
+%%      exometer_unsubscribe(Metric, DataPoint, Extra, State)
+%% +%% The custom plugin can use this notification to modify and return its +%% state in order to free resources used to maintain the now de-activated +%% subscription. When this call returns, the given metric / data point +%% will not be present in future calls to `exometer_report()'. +%% +%% + `Metric'
Specifies the metric that is now subscribed to by the plugin +%% as a list of atoms. +%% +%% + `DataPoint'
Specifies the data point or data points within the +%% subscribed-to metric as an atom or a list of atoms. +%% +%% + `Extra'
Specifies the extra data, which can be anything the reporter +%% can understand. +%% +%% + `State'
Contains the state returned by the last called plugin function. +%% +%% The `exometer_unsubscribe()' function should return `{ok, State}' where +%% State is a tuple that will be provided as a reference argument to +%% future calls made into the plugin. Any other return formats will +%% generate an error log message by exometer. +%% +%% === exometer_report_bulk/3 (Optional) === +%% +%% If the option `{report_bulk, true}' has been given when starting the +%% reporter, and this function is exported, it will be called as: +%% +%%
+%%      exometer_report_bulk(Found, Extra, State)
+%% 
+%% +%% where `Found' has the format `[{Metric, [{DataPoint, Value}|_]}|_]' +%% +%% That is, e.g. when a `select' pattern is used, all found values are passed +%% to the reporter in one message. If bulk reporting is not enabled, each +%% datapoint/value pair will be passed separately to the +%% exometer_report/4 function. If `report_bulk' was enabled, the +%% reporter callback will get all values at once. Note that this happens +%% also for single values, which are then passed as a list of one metric, +%% with a list of one datapoint/value pair. +%% +%% @end +-module(exometer_report). + +-behaviour(gen_server). + +%% API +-export( + [ + start_link/0, + subscribe/4, subscribe/5, subscribe/6, + unsubscribe/3, unsubscribe/4, + unsubscribe_all/2, + list_metrics/0, list_metrics/1, + list_reporters/0, + list_subscriptions/1, + add_reporter/2, + set_interval/3, + delete_interval/2, + restart_intervals/1, + trigger_interval/2, + get_intervals/1, + remove_reporter/1, remove_reporter/2, + terminate_reporter/1, + enable_reporter/1, + disable_reporter/1, + call_reporter/2, + cast_reporter/2, + setopts/3, + new_entry/1 + ]). + +%% Start phase function +-export([start_reporters/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([disable_me/2]). + +-export_type([metric/0, datapoint/0, interval/0, extra/0]). + +-include_lib("hut/include/hut.hrl"). +-include("exometer.hrl"). + +-define(SERVER, ?MODULE). + +-type error() :: {error, any()}. +-type metric() :: exometer:name() + | {find, exometer:name()} + | {select, ets:match_spec()}. +-type datapoint() :: exometer:datapoint(). +-type datapoints() :: datapoint() | [datapoint()]. +-type options() :: [{atom(), any()}]. +-type mod_state() :: any(). +-type value() :: any(). +-type interval() :: pos_integer() | atom(). +-type time_ms() :: pos_integer(). +-type delay() :: time_ms(). +-type named_interval() :: {atom(), time_ms()} + | {atom(), time_ms(), delay()}. +-type callback_result() :: {ok, mod_state()} | any(). +-type extra() :: any(). +-type retry() :: boolean(). +-type reporter_name() :: atom(). +%% Restart specification +-type maxR() :: pos_integer(). +-type maxT() :: pos_integer(). +-type action() :: {atom(), atom()}. +-type restart() :: [{maxR(), maxT()} | action()]. + +%% Callback for function, not cast-based, reports that +%% are invoked in-process. +-callback exometer_init(options()) -> callback_result(). + +-callback exometer_report(metric(), datapoint(), + extra(), value(), mod_state()) -> + callback_result(). + +-callback exometer_subscribe(metric(), datapoint(), + interval(), extra(), mod_state()) -> + callback_result(). + +-callback exometer_unsubscribe(metric(), datapoint(), + extra(), mod_state()) -> + callback_result(). + +-callback exometer_info(any(),mod_state()) -> + callback_result(). + +-callback exometer_call(any(), pid(), mod_state()) -> + {reply, any(), mod_state()} | {noreply, mod_state()} | any(). + +-callback exometer_cast(any(), mod_state()) -> + {noreply, mod_state()} | any(). + +-callback exometer_terminate(any(), mod_state()) -> + any(). + +-callback exometer_setopts(exometer:entry(), options(), + exometer:status(), mod_state()) -> + callback_result(). + +-callback exometer_newentry(exometer:entry(), mod_state()) -> + callback_result(). + +-record(key, { + reporter :: module() | '_', + metric :: metric() | '_', + datapoint :: datapoints() | '_', + retry_failed_metrics :: boolean() | '_', + extra :: extra() | '_' + }). + +-record(subscriber, { + key :: #key{} | '_', + interval :: interval() | '_', + t_ref :: reference() | undefined | '_' + }). + +-record(restart, { + spec = default_restart() :: restart(), + history = [] :: [pos_integer()], + save_n = 10 :: pos_integer()} + ). + +-record(interval, { + name :: atom(), + time = 0 :: non_neg_integer() | 'manual', + delay = 0 :: non_neg_integer(), + t_ref :: reference() | undefined + }). + +-record(reporter, { + name :: atom() | '_', + pid :: pid() | atom(), % in select() + mref :: reference() | '_', + module :: module() | '_', + opts = [] :: [{atom(), any()}] | '_', + intervals = [] :: [#interval{}] | '_', + restart = #restart{} :: #restart{} | '_', + status = enabled :: enabled | disabled | '_' + }). + +-record(st, { + subscribers = [] :: [#subscriber{}], + reporters = [] :: [#reporter{}] + }). + +-record(rst, {st, bulk = false}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @doc Starts the server +%%-------------------------------------------------------------------- +-spec start_link() -> {ok, pid()} | ignore | {error, any()}. +start_link() -> + %% Launch the main server. + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +-spec subscribe(reporter_name(), metric(), datapoints(), interval()) -> + ok | not_found | unknown_reporter | error. +%% @equiv subscribe(Reporter, Metric, DataPoint, Interval, [], true) +subscribe(Reporter, Metric, DataPoint, Interval) -> + subscribe(Reporter, Metric, DataPoint, Interval, []). + +-spec subscribe(reporter_name(), metric(), datapoints(), interval(), extra()) -> + ok | not_found | unknown_reporter | error. +%% @equiv subscribe(Reporter, Metric, DataPoint, Interval, Extra, false) +subscribe(Reporter, Metric, DataPoint, Interval, Extra) -> + call({subscribe, #key{reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + retry_failed_metrics = true, + extra = Extra}, Interval}). + +-spec subscribe(reporter_name(), metric(), datapoints(), interval(), + extra(), retry()) -> + ok | not_found | unknown_reporter | error. +%% @doc Add a subscription to an existing reporter. +%% +%% The reporter must first be started using {@link add_reporter/2}, or through +%% a static configuration. `Metric' is the name of an exometer entry. `DataPoint' +%% is either a single data point (an atom) or a list of data points (a list). +%% +%% `Interval' is the sampling/reporting interval in milliseconds, or an atom, +%% referring to a named interval configured in the reporter. The named +%% interval need not be defined yet in the reporter (the subscription will +%% not trigger until it is defined.) +%% +%% `Extra' can be anything that the chosen reporter understands (default: `[]'). +%% If the reporter uses {@link exometer_util:report_type/3}, `Extra' should be +%% a proplist, and the option `{report_type, T}' can control which type (e.g. +%% for collectd or statsd) that the value corresponds to. +%% +%% `Retry': boolean(). If true, retry the subscription at the next interval, +%% even if the metric cannot be read. +%% @end +subscribe(Reporter, Metric, DataPoint, Interval, Extra, Retry) + when is_boolean(Retry) -> + call({subscribe, #key{reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + retry_failed_metrics = Retry, + extra = Extra}, Interval}). + +-spec unsubscribe(module(), metric(), datapoint()) -> + ok | not_found. +%% @equiv unsubscribe(Reporter, Metric, DataPoint, []) +unsubscribe(Reporter, Metric, DataPoint) -> + unsubscribe(Reporter, Metric, DataPoint, []). + +-spec unsubscribe(module(), metric(), datapoint() | [datapoint()], extra()) -> + ok | not_found. +%% @doc Removes a subscription. +%% +%% Note that the subscription is identified by the combination +%% `{Reporter, Metric, DataPoint, Extra}'. The exact information can be +%% extracted using {@link list_subscriptions/1}. +%% @end +unsubscribe(Reporter, Metric, DataPoint, Extra) -> + call({unsubscribe, #key{reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + extra = Extra}}). + +-spec unsubscribe_all(module(), metric()) -> ok. +%% @doc Removes all subscriptions related to Metric in Reporter. +%% @end +unsubscribe_all(Reporter, Metric) -> + call({unsubscribe_all, Reporter, Metric}). + +-spec list_metrics() -> {ok, [{ exometer:name(), + [datapoint()], + [{reporter_name(), datapoint()}], + exometer:status() }]} | {error, any()}. +%% @equiv list_metrics([]) +list_metrics() -> + list_metrics([]). + +-spec list_metrics(Path :: metric()) -> + {ok, [{ exometer:name(), + [datapoint()], + [{reporter_name(), datapoint()}], + exometer:status() }]} | {error, any()}. +%% @doc List all metrics matching `Path', together with subscription status. +%% +%% This function performs a metrics search using `exometer:find_entries/1', +%% then matches the result against known subscriptions. It reports, for each +%% metric, the available data points, as well as which reporters subscribe to +%% which data points. +%% @end +list_metrics(Path) -> + call({list_metrics, Path}). + +-spec list_reporters() -> [{reporter_name(), pid()}]. +%% @doc List the name and pid of each known reporter. +list_reporters() -> + call(list_reporters). + +-spec list_subscriptions(reporter_name()) -> + [{metric(), datapoint(), interval(), extra()}]. +%% @doc List all subscriptions for `Reporter'. +list_subscriptions(Reporter) -> + call({list_subscriptions, Reporter}). + +-spec add_reporter(reporter_name(), options()) -> ok | {error, any()}. +%% @doc Add a reporter. +%% +%% The reporter can be configured using the following options. Note that all +%% options are also passed to the reporter callback module, which may support +%% additional options. +%% +%% `{module, atom()}' - The name of the reporter callback module. If no module +%% is given, the module name defaults to the given reporter name. +%% +%% `{status, enabled | disabled}' - The operational status of the reporter +%% if enabled, the reporter will report values to its target. If disabled, the +%% reporter process will be terminated and subscription timers canceled, but +%% the subscriptions will remain, and it will also be possible to add new +%% subscriptions to the reporter. +%% +%% `{intervals, [named_interval()]}' +%% named_interval() :: {Name::atom(), Interval::pos_integer()} +%% | {Name::atom(), Interval::time_ms(), delay()::time_ms()} +%% | {Name::atom(), 'manual'} +%% Define named intervals. The name can be used by subscribers, so that all +%% subsriptions for a given named interval will be reported when the interval +%% triggers. An optional delay (in ms) can be given: this will cause the first +%% interval to start in `Delay' milliseconds. When all intervals are named +%% at the same time, the delay parameter can be used to achieve staggered +%% reporting. If the interval is specified as ```'manual'''', it will have +%% to be triggered manually using {@link trigger_interval/2}. +%% +%% `{report_bulk, true | false}' +%% Pass all found datapoint/value pairs for a given subscription at once to +%% the `exometer_report_bulk/3' function, if it is exported, otherwise use +%% `exometer_report/4' as usual. +%% +%% @end +add_reporter(Reporter, Options) -> + call({add_reporter, Reporter, Options}). + +-spec remove_reporter(reporter_name()) -> ok | {error, any()}. +%% @doc Remove reporter and all its subscriptions. +remove_reporter(Reporter) -> + call({remove_reporter, Reporter}). + +-spec set_interval(reporter_name(), atom(), + time_ms() | {time_ms(), delay()} | manual) -> ok |error(). +%% @doc Specify a named interval. +%% +%% See {@link add_reporter/2} for a description of named intervals. +%% The named interval is here specified as either `Time' (milliseconds) or +%% `{Time, Delay}', where a delay in milliseconds is provided. It is also +%% specify an interval as ```'manual'''', indicating that the interval can +%% only be triggered manually via {@link trigger_interval/2}. +%% +%% If the named interval exists, it will be replaced with the new definition. +%% Otherwise, it will be added. Use {@link restart_intervals/1} if you want +%% all intervals to be restarted/resynched with corresponding relative delays. +%% @end +set_interval(Reporter, Name, Time) when is_atom(Name), + is_integer(Time), Time >= 0 -> + call({set_interval, Reporter, Name, Time}); +set_interval(Reporter, Name, manual) when is_atom(Name) -> + call({set_interval, Reporter, Name, manual}); +set_interval(Reporter, Name, {Time, Delay}) when is_atom(Name), + is_integer(Time), Time >= 0, + is_integer(Delay), + Delay >= 0 -> + call({set_interval, Reporter, Name, {Time, Delay}}). + +-spec delete_interval(reporter_name(), atom()) -> ok | error(). +%% @doc Delete a named interval. +%% +delete_interval(Reporter, Name) -> + call({delete_interval, Reporter, Name}). + +-spec restart_intervals(reporter_name()) -> ok. +%% @doc Restart all named intervals, respecting specified delays. +%% +%% This function can be used if named intervals are added incrementally, and +%% it is important that all intervals trigger separated by the given delays. +%% @end +restart_intervals(Reporter) -> + call({restart_intervals, Reporter}). + +-spec trigger_interval(reporter_name(), atom()) -> ok. +%% @doc Trigger a named interval. +%% +%% This function is mainly used to trigger intervals defined as ```'manual'''', +%% but can be used to trigger any named interval. If a named interval with +%% a specified time in milliseconds is triggered this way, it will effectively +%% be restarted, and will repeat as usual from that point on. +%% @end +trigger_interval(Reporter, Name) -> + cast({trigger_interval, Reporter, Name}). + +-spec get_intervals(reporter_name()) -> + [{atom(), [{time, pos_integer()} + | {delay, pos_integer()} + | {timer_ref, reference()}]}]. +%% @doc List the named intervals for `Reporter'. +get_intervals(Reporter) -> + call({get_intervals, Reporter}). + + +-spec enable_reporter(reporter_name()) -> ok | {error, any()}. +%% @doc Enable `Reporter'. +%% +%% The reporter will be 'restarted' in the same way as if it had crashed +%% and was restarted by the supervision logic, but without counting it as +%% a restart. +%% +%% If the reporter was already enabled, nothing is changed. +%% @end +enable_reporter(Reporter) -> + call({change_reporter_status, Reporter, enabled}). + +-spec disable_reporter(reporter_name()) -> ok | {error, any()}. +%% @doc Disable `Reporter'. +%% +%% The reporter will be terminated, and all subscription timers will be +%% canceled, but the subscriptions themselves and reporter metadata are kept. +%% @end +disable_reporter(Reporter) -> + call({change_reporter_status, Reporter, disabled}). + +-spec disable_me(module(), any()) -> no_return(). +%% @doc Used by a reporter to disable itself. +%% +%% This function can be called from a reporter instance if it wants to be +%% disabled, e.g. after exhausting a configured number of connection attempts. +%% The arguments passed are the name of the reporter callback module and the +%% module state, and are used to call the `Mod:terminate/2' function. +%% @end +disable_me(Mod, St) -> + cast({disable, self()}), + receive + {exometer_terminate, shutdown} -> + Mod:exometer_terminate(shutdown, St), + exit(shutdown) + end. + +-spec call_reporter(reporter_name(), any()) -> any() | {error, any()}. +%% @doc Send a custom (synchronous) call to `Reporter'. +%% +%% This function is used to make a client-server call to a given reporter +%% instance. Note that the reporter type must recognize the request. +%% @end +call_reporter(Reporter, Msg) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{pid = Pid}] when is_pid(Pid) -> + exometer_proc:call(Pid, Msg); + [#reporter{status = disabled}] -> + {error, disabled}; + [] -> + {error, {no_such_reporter, Reporter}} + end. + +-spec cast_reporter(reporter_name(), any()) -> ok | {error, any()}. +%% @doc Send a custom (asynchronous) cast to `Reporter'. +%% +%% This function is used to make an asynchronous cast to a given reporter +%% instance. Note that the reporter type must recognize the message. +%% @end +cast_reporter(Reporter, Msg) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{pid = Pid}] when is_pid(Pid) -> + exometer_proc:cast(Pid, Msg); + [#reporter{status = disabled}] -> + {error, disabled}; + [] -> + {error, {no_such_reporter, Reporter}} + end. + +-spec remove_reporter(reporter_name(), _Reason::any()) -> ok | {error, any()}. +%% @doc Remove `Reporter' (non-blocking call). +%% +%% This function can be used to order removal of a reporter with a custom +%% reason. Note that the function is asynchronous, making it suitable e.g. +%% for calling from within the reporter itself. +%% @end +remove_reporter(Reporter, Reason) -> + cast({remove_reporter, Reporter, Reason}). + +-spec setopts(exometer:entry(), options(), exometer:status()) -> ok. +%% @doc Called by exometer when options of a metric entry are changed. +%% +%% Reporters subscribing to the metric get a chance to process the options +%% change in the function `Mod:exometer_setopts(Metric,Options,Status,St)'. +%% @end +setopts(Metric, Options, Status) -> + call({setopts, Metric, Options, Status}). + +-spec new_entry(exometer:entry()) -> ok. +%% @doc Called by exometer whenever a new entry is created. +%% +%% This function is called whenever a new metric is created, giving each +%% reporter the chance to enable a subscription for it. Note that each +%% reporter is free to call the subscription management functions, as there +%% is no risk of deadlock. The callback function triggered by this call is +%% `Mod:exometer_newentry(Entry, St)'. +%% @end +new_entry(Entry) -> + cast({new_entry, Entry}). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Initializes the server +%% +%% @spec init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% @end +%%-------------------------------------------------------------------- +init([]) -> + process_flag(trap_exit, true), + D = ets:foldl( + fun (#reporter{name = Name, module = Module, restart = Restart} = R, Acc) -> + terminate_reporter(R), + case add_restart(Restart) of + {remove, How} -> + case How of + {M, F} when is_atom(M), is_atom(F) -> + try M:F(Module, {?MODULE, parent_restart}) catch _:_ -> ok end; + _ -> + ok + end, + [Name | Acc]; + {restart, Restart1} -> + restart_reporter(R#reporter{restart = Restart1}), + Acc + end + end, + [], + ?EXOMETER_REPORTERS), + [ets:delete(?EXOMETER_REPORTERS, R) || R <- D], + {ok, #st{}}. + +start_reporters() -> + call(start_reporters). + +do_start_reporters(S) -> + Opts = get_report_env(), + ?log(info, "Starting reporters with ~p~n", [ Opts ]), + %% Dig out the mod opts. + %% { reporters, [ {reporter1, [{opt1, val}, ...]}, {reporter2, [...]}]} + %% Traverse list of reporter and launch reporter gen servers as dynamic + %% supervisor children. + case lists:keyfind(reporters, 1, Opts) of + {reporters, ReporterList} -> + ReporterRecs = make_reporter_recs(ReporterList), + assert_no_duplicates(ReporterRecs), + lists:foreach( + fun(#reporter{name = Reporter, + status = Status, + opts = ROpts, + intervals = Ints0} = R) -> + Restart = get_restart(ROpts), + {Pid, MRef, Ints} = + if Status =:= enabled -> + {P1,R1} = spawn_reporter(Reporter, ROpts), + I1 = start_interval_timers(R), + {P1,R1,I1}; + true -> {undefined, undefined, Ints0} + end, + ets:insert(?EXOMETER_REPORTERS, + R#reporter{pid = Pid, + mref = MRef, + intervals = Ints, + restart = Restart}) + end, ReporterRecs); + false -> + [] + end, + %% Dig out configured 'static' subscribers + case lists:keyfind(subscribers, 1, Opts) of + {subscribers, Subscribers} -> + lists:foreach(fun init_subscriber/1, Subscribers); + false -> [] + end, + S#st{}. + +make_reporter_recs([{R, Opts}|T]) when is_atom(R), is_list(Opts) -> + [#reporter{name = R, + module = get_module(R, Opts), + status = proplists:get_value(status, Opts, enabled), + opts = Opts, + intervals = get_interval_opts(Opts)}|make_reporter_recs(T)]; +make_reporter_recs([]) -> + []. + +get_module(R, Opts) -> + proplists:get_value(module, Opts, R). + +-spec get_interval_opts([named_interval() | any()]) -> [#interval{}]. +get_interval_opts(Opts) -> + Is1 = [singelton_interval(I) || {interval, I} <- Opts], + Is = proplists:get_value(intervals, Opts, []), + lists:map( + fun({Name, Time}) when is_atom(Name), + is_integer(Time), Time >= 0 -> + #interval{name = Name, time = Time}; + ({Name, Time, Delay}) when is_atom(Name), + is_integer(Time), Time >= 0, + is_integer(Delay), Delay >= 0 -> + #interval{name = Name, time = Time, delay = Delay}; + ({Name, manual}) when is_atom(Name) -> + #interval{name = Name, time = manual}; + (Other) -> + error({invalid_interval, Other}) + end, Is ++ Is1). + +singelton_interval({N,T}=I) when is_atom(N), is_integer(T) -> I; +singelton_interval({N,T,D}=I) when is_atom(N), + is_integer(T), + is_integer(D) -> I. + +start_interval_timers(#reporter{name = R, intervals = Ints}) -> + lists:map(fun(I) -> start_interval_timer(I, R) end, Ints). + +start_interval_timer(#interval{time = manual} = I, _) -> + I; +start_interval_timer(#interval{name = Name, delay = Delay, + t_ref = Ref} = I, R) -> + cancel_timer(Ref), + case Delay of + 0 -> + do_start_interval_timer(I, R); + D -> + TRef = erlang:send_after(D, self(), {start_interval, R, Name}), + I#interval{t_ref = TRef} + end. + +do_start_interval_timer(#interval{name = Name, time = Time} = I, R) -> + TRef = erlang:send_after(Time, self(), batch_timer_msg(R, Name, Time)), + I#interval{t_ref = TRef}. + +batch_timer_msg(R, Name, Time) -> + batch_timer_msg(R, Name, Time, os:timestamp()). + +batch_timer_msg(R, Name, Time, TS) -> + {report_batch, R, Name, Time, TS}. + +subscr_timer_msg(Key, Interval) -> + subscr_timer_msg(Key, Interval, os:timestamp()). + +subscr_timer_msg(Key, Interval, TS) -> + {report, Key, Interval, TS}. + +get_report_env() -> + Opts0 = exometer_util:get_env(report, []), + {Rs1, Opts1} = split_env(reporters, Opts0), + {Ss2, Opts2} = split_env(subscribers, Opts1), + get_reporters(Rs1) ++ get_subscribers(Ss2) ++ Opts2. + +split_env(Tag, Opts) -> + case lists:keytake(Tag, 1, Opts) of + {value, {_, L}, Rest} -> {L, Rest}; + false -> {[], Opts} + end. + +get_reporters(L0) -> + Rs = exometer_util:get_env(reporters, []), + Ext = setup:find_env_vars(exometer_reporters), + merge_env(reporters, Rs ++ L0, Ext). + +get_subscribers(L0) -> + Ss = exometer_util:get_env(subscribers, []), + Ext = setup:find_env_vars(exometer_subscribers), + merge_env(subscribers, Ss ++ L0, Ext). + +merge_env(_, [], []) -> []; +merge_env(Tag, L, E) -> + [{Tag, L} || L =/= []] ++ [{Tag, X} || {_, X} <- E]. + + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling call messages +%% +%% @spec handle_call(Request, From, State) -> +%% {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_call(start_reporters, _From, S) -> + {reply, ok, do_start_reporters(S)}; +handle_call({subscribe, + #key{ reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + retry_failed_metrics = RetryFailedMetrics, + extra = Extra} , Interval }, + _From, #st{} = St) -> + %% Verify that the given metric/data point actually exist. + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{status = Status, pid=ReporterPid}] -> + case is_valid_metric(Metric, DataPoint) of + true -> + if Status =:= enabled -> + ReporterPid ! {exometer_subscribe, Metric, + DataPoint, Interval, Extra}; + true -> ignore + end, + subscribe_(Reporter, Metric, DataPoint, + Interval, RetryFailedMetrics, + Extra, Status), + {reply, ok, St}; + %% Nope - Not found. + false -> + case RetryFailedMetrics of + true -> + subscribe_(Reporter, Metric, DataPoint, + Interval, RetryFailedMetrics, + Extra, Status), + {reply, ok, St}; + false -> + {reply, not_found, St} + end; + error -> {reply, error, St} + end; + [] -> + {reply, unknown_reporter, St} + end; + +handle_call({unsubscribe, + #key{reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + extra = Extra}}, _, #st{} = St) -> + Res = unsubscribe_(Reporter, Metric, DataPoint, Extra), + {reply, Res, St}; + +handle_call({unsubscribe_all, Reporter, Metric}, _, + #st{}=St) -> + Subs = ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{reporter = Reporter, + metric = Metric, + _ = '_'}, + _ = '_'}, [], ['$_']}]), + lists:foreach(fun unsubscribe_/1, Subs), + {reply, ok, St}; + +handle_call({list_metrics, Path}, _, St) -> + if is_list(Path) -> + DP = lists:foldr(fun(Metric, Acc) -> + retrieve_metric(Metric, Acc) + end, [], exometer:find_entries(Path)), + {reply, {ok, DP}, St}; + true -> + {reply, {error, badarg}, St} + end; + +handle_call({list_subscriptions, Reporter}, _, #st{} = St) -> + Subs1 = lists:foldl( + fun + (#subscriber{key=#key{reporter=Rep}}=Sub, Acc) when Reporter == Rep -> + #subscriber{ + key=#key{ + metric=Metric, + datapoint=Dp, + extra=Extra}, + interval=Interval} = Sub, + [{Metric, Dp, Interval, Extra} | Acc]; + (_, Acc) -> + Acc + end, [], ets:select(?EXOMETER_SUBS, [{'_',[],['$_']}])), + {reply, Subs1, St}; + +handle_call(list_reporters, _, #st{} = St) -> + Info = ets:select(?EXOMETER_REPORTERS, + [{#reporter{name = '$1', pid = '$2', _ = '_'}, + [], [{{'$1', '$2'}}]}]), + {reply, Info, St}; + +handle_call({add_reporter, Reporter, Opts}, _, #st{} = St) -> + case ets:member(?EXOMETER_REPORTERS, Reporter) of + true -> + {reply, {error, already_running}, St}; + false -> + try + [R] = make_reporter_recs([{Reporter, Opts}]), + {Pid, MRef} = spawn_reporter(Reporter, Opts), + Ints = start_interval_timers(R), + R1 = R#reporter{intervals = Ints, + pid = Pid, + mref = MRef}, + ets:insert(?EXOMETER_REPORTERS, R1), + {reply, ok, St} + catch + error:Reason -> + {reply, {error, Reason}, St} + end + end; + +handle_call({remove_reporter, Reporter}, _, St) -> + case do_remove_reporter(Reporter) of + ok -> + {reply, ok, St}; + E -> + {reply, E, St} + end; + +handle_call({change_reporter_status, Reporter, Status}, _, St) -> + case change_reporter_status(Reporter, Status) of + ok -> + {reply, ok, St}; + E -> + {reply, E, St} + end; +handle_call({set_interval, Reporter, Name, Int}, _, #st{}=St) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{intervals = Ints}] -> + try + I0 = case lists:keyfind(Name, #interval.name, Ints) of + false -> #interval{name = Name}; + Interval -> Interval + end, + I1 = case Int of + {Time, Delay} when is_integer(Time), Time >= 0, + is_integer(Delay), Delay >= 0 -> + I0#interval{time = Time, delay = Delay}; + Time when is_integer(Time), Time >= 0 -> + I0#interval{time = Time}; + manual -> + cancel_timer(I0#interval.t_ref), + I0#interval{time = manual} + end, + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keystore( + Name, #interval.name, Ints, + start_interval_timer(I1, Reporter))}]), + {reply, ok, St} + catch + error:Reason -> + {reply, {error, Reason}, St} + end; + [] -> + {reply, {error, not_found}, St} + end; +handle_call({delete_interval, Reporter, Name}, _, #st{} = St) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{intervals = Ints}] -> + case lists:keyfind(Name, #interval.name, Ints) of + #interval{t_ref = TRef} -> + cancel_timer(TRef), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keydelete( + Name, #interval.name, Ints)}]), + {reply, ok, St}; + false -> + {reply, {error, not_found}, St} + end; + [] -> + {reply, {error, not_found}, St} + end; +handle_call({restart_intervals, Reporter}, _, #st{} = St) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{} = R] -> + Ints = start_interval_timers(R), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, Ints}]), + {reply, ok, St}; + [] -> + {reply, {error, not_found}, St} + end; +handle_call({get_intervals, Reporter}, _, #st{} = St) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{intervals = Ints}] -> + Info = + [{Name, [{time, T}, + {delay, D}, + {timer_ref, TR}]} || #interval{name = Name, + time = T, + delay = D, + t_ref = TR} <- Ints], + {reply, Info, St}; + [] -> + {reply, {error, not_found}, St} + end; +handle_call({setopts, Metric, Options, Status}, _, #st{}=St) -> + [erlang:send(Pid, {exometer_setopts, Metric, Options, Status}) + || Pid <- reporter_pids()], + {reply, ok, St}; + +handle_call(_Request, _From, State) -> + {reply, {error, unknown_call}, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc Handling cast messages. +%% +%% @spec handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_cast({new_entry, Entry}, #st{} = St) -> + [try erlang:send(Pid, {exometer_newentry, Entry}) + catch error:_ -> ok end + || Pid <- reporter_pids()], + maybe_enable_subscriptions(Entry), + {noreply, St}; + +handle_cast({remove_reporter, Reporter, Reason}, St) -> + Terminate = case Reason of + user -> + true; + _ -> + false + end, + do_remove_reporter(Reporter, Terminate), + {noreply, St}; +handle_cast({disable, Pid}, #st{} = St) -> + case reporter_by_pid(Pid) of + [#reporter{} = Reporter] -> + do_change_reporter_status(Reporter, disabled); + [] -> ok + end, + {noreply, St}; +handle_cast({trigger_interval, Reporter, Name}, #st{} = St) -> + report_batch(Reporter, Name, os:timestamp()), + {noreply, St}; +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc Handling all non call/cast messages. +%% +%% @spec handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_info({start_interval, Reporter, Name}, #st{} = St) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{intervals = Ints, status = enabled}] -> + case lists:keyfind(Name, #interval.name, Ints) of + #interval{time = Time} = I when is_integer(Time) -> + I1 = do_start_interval_timer(I, Reporter), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keyreplace( + Name, #interval.name, Ints, I1)}]); + #interval{time = manual} -> + ok; + false -> + ok + end; + _ -> + ok + end, + {noreply, St}; +handle_info({report_batch, Reporter, Name}, #st{} = St) -> + %% Find all entries where reporter is Reporter and interval is Name, + %% and report them. + report_batch(Reporter, Name, os:timestamp()), + {noreply, St}; +handle_info({report_batch, Reporter, Name, Int, TS}, #st{} = St) -> + %% Find all entries where reporter is Reporter and interval is Name, + %% and report them. + TS1 = calc_fire_time(TS, Int), + report_batch(Reporter, Name, TS1), + {noreply, St}; +handle_info({report, #key{} = Key, Interval}, #st{} = St) -> + %% BW Compat. Old-style timeout msg, which doesn't include timestamp + {noreply, handle_report(Key, Interval, os:timestamp(), St)}; +handle_info({report, #key{} = Key, Interval, TS}, #st{} = St) -> + TS1 = calc_fire_time(TS, Interval), + {noreply, handle_report(Key, Interval, TS1, St)}; + +handle_info({'DOWN', Ref, process, _Pid, Reason}, #st{} = S) -> + case reporter_by_mref(Ref) of + [#reporter{module = Module, restart = Restart} = R] -> + case add_restart(Restart) of + {remove, How} -> + case How of + {M, F} when is_atom(M), is_atom(F) -> + try M:F(Module, Reason) catch _:_ -> ok end; + _ -> + ok + end, + S; + {restart, Restart1} -> + restart_reporter(R#reporter{restart = Restart1}) + end; + _ -> S + end, + {noreply, S}; + +handle_info(_Info, State) -> + ?log(warning, "exometer_report:info(??): ~p~n", [ _Info ]), + {noreply, State}. + +restart_reporter(#reporter{name = Name, opts = Opts, restart = Restart}) -> + {Pid, MRef} = spawn_reporter(Name, Opts), + [resubscribe(S) || + S <- ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{reporter = Name, + _ = '_'}, + _ = '_'}, [], ['$_']}])], + ets:update_element(?EXOMETER_REPORTERS, Name, + [{#reporter.pid, Pid}, + {#reporter.mref, MRef}, + {#reporter.restart, Restart}, + {#reporter.status, enabled}]), + ok. + +%% If there are already subscriptions, enable them. +maybe_enable_subscriptions(#exometer_entry{name = Metric}) -> + lists:foreach( + fun(#subscriber{key = #key{reporter = RName}} = S) -> + case get_reporter_status(RName) of + enabled -> + resubscribe(S); + _ -> + ok + end + end, ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{metric = Metric, + _ = '_'}, + _ = '_'}, [], ['$_']}])), + %% Also re-check the static subscribers for select and apply + case lists:keyfind(subscribers, 1, get_report_env()) of + {subscribers, Subscribers} -> + lists:foreach( + fun(Sub) -> + case Sub of + {select, _} -> init_subscriber(Sub); + {apply, _} -> init_subscriber(Sub); + _ -> ok + end + end, Subscribers); + false -> [] + end. + +resubscribe(#subscriber{key = #key{reporter = RName, + metric = Metric, + datapoint = DataPoint, + extra = Extra} = Key, + t_ref = OldTRef, + interval = Interval}) when is_integer(Interval) -> + try_send(RName, {exometer_subscribe, Metric, DataPoint, Interval, Extra}), + cancel_timer(OldTRef), + TRef = erlang:send_after(Interval, self(), + subscr_timer_msg(Key, Interval)), + ets:update_element(?EXOMETER_SUBS, Key, [{#subscriber.t_ref, TRef}]); + +resubscribe(_) -> undefined. + +handle_report(#key{reporter = Reporter} = Key, Interval, TS, #st{} = St) -> + _ = case ets:member(?EXOMETER_SUBS, Key) andalso + get_reporter_status(Reporter) == enabled of + true -> + case do_report(Key, Interval) of + true -> restart_subscr_timer(Key, Interval, TS); + false -> ok + end; + false -> + %% Possibly an unsubscribe removed the subscriber + ?log(error, "No such subscriber (Key=~p)~n", [Key]) + end, + St. + +do_report(#key{metric = Metric, + datapoint = DataPoint, + retry_failed_metrics = RetryFailedMetrics} = Key, Interval) -> + case {RetryFailedMetrics, get_values(Metric, DataPoint)} of + %% We found a value, or values. + {_, [_|_] = Found} -> + %% Distribute metric value to the correct process + report_values(Found, Key), + true; + %% We did not find a value, but we should try again. + {true, _ } -> + ?log(debug, "Metric(~p) Datapoint(~p) not found." + " Will try again in ~p msec~n", + [Metric, DataPoint, Interval]), + true; + %% We did not find a value, and we should not retry. + _ -> + %% Entry removed while timer in progress. + ?log(warning, "Metric(~p) Datapoint(~p) not found. Will not try again~n", + [Metric, DataPoint]), + false + end. + +report_batch(Reporter, Name, T0) when is_atom(Name) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{status = disabled}] -> + false; + [R] -> + Entries = ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{reporter = Reporter, + _ = '_'}, + interval = Name, + _ = '_'}, [], ['$_']}]), + lists:foreach( + fun(#subscriber{key = Key}) -> + do_report(Key, Name) + end, Entries), + restart_batch_timer(Name, R, T0); + [] -> + false + end. + + +cancel_subscr_timers(Reporter) -> + lists:foreach( + fun(#subscriber{key = Key, t_ref = TRef}) -> + cancel_timer(TRef), + ets:update_element(?EXOMETER_SUBS, Key, + [{#subscriber.t_ref, undefined}]) + end, ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{reporter = Reporter, + _ = '_'}, + _ = '_'}, [], ['$_']}])). + +restart_subscr_timer(Key, Interval, T0) when is_integer(Interval) -> + {AdjInt, RptTime} = adjust_interval(Interval, T0), + TRef = erlang:send_after(AdjInt, self(), + subscr_timer_msg(Key, Interval, RptTime)), + ets:update_element(?EXOMETER_SUBS, Key, + [{#subscriber.t_ref, TRef}]); +restart_subscr_timer(_, _, _) -> + true. + +restart_batch_timer(Name, #reporter{name = Reporter, + intervals = Ints}, T0) when is_list(Ints) -> + case lists:keyfind(Name, #interval.name, Ints) of + #interval{time = Time, t_ref = OldTRef} = I when is_integer(Time) -> + cancel_timer(OldTRef), + {Int, RptTime} = adjust_interval(Time, T0), + TRef = erlang:send_after(Int, self(), + batch_timer_msg( + Reporter, Name, Time, RptTime)), + ets:update_element(?EXOMETER_REPORTERS, Reporter, + [{#reporter.intervals, + lists:keyreplace(Name, #interval.name, Ints, + I#interval{t_ref = TRef})}]); + #interval{time = manual} -> + false; + false -> + false + end. + +adjust_interval(Time, T0) -> + T1 = os:timestamp(), + case tdiff(T1, T0) of + D when D > Time; D < 0 -> + %% Most likely due to clock adjustment + {Time, T1}; + D -> + {D, T0} + end. + +tdiff(T1, T0) -> + timer:now_diff(T1, T0) div 1000. + +%% Calculate time when timer should have fired, based on timestamp logged +%% at send_after/3 and the intended interval (in ms). +calc_fire_time({manual, TS}, _) -> + TS; +calc_fire_time({M,S,U}, Int) -> + {M, S, U + (Int*1000)}. + + +cancel_timer(undefined) -> + false; +cancel_timer(TRef) -> + erlang:cancel_timer(TRef). + + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Reporter:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% +%% @spec terminate(Reason, State) -> void() +%% @end +%%-------------------------------------------------------------------- +terminate(_Reason, _) -> + [terminate_reporter(R) || R <- ets:tab2list(?EXOMETER_REPORTERS)], + ok. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @end +%%-------------------------------------------------------------------- + +%% -record(reporter, { +%% name :: atom(), +%% pid :: pid(), +%% mref :: reference(), +%% module :: module(), +%% opts = [] :: [{atom(), any()}], +%% restart = #restart{} +%% }). +code_change(_OldVan, #st{reporters = Rs, subscribers = Ss} = S, _Extra) -> + Rs1 = lists:map( + fun({reporter,Pid,MRef,Module,Opts,Restart}) -> + #reporter{name = Module, pid = Pid, mref = MRef, + module = Module, opts = Opts, + restart = Restart}; + ({reporter,Name,Pid,MRef,Module,Opts,Restart}) -> + #reporter{name = Name, pid = Pid, mref = MRef, + module = Module, opts = Opts, + restart = Restart}; + ({reporter,Name,Pid,Mref,Module,Opts,Restart,Status}) -> + #reporter{name = Name, pid = Pid, mref = Mref, + module = Module, opts = Opts, + restart = Restart, status = Status}; + (#reporter{} = R) -> R + end, Rs), + [ets:insert(?EXOMETER_REPORTERS, R) || R <- Rs1], + [ets:insert(?EXOMETER_SUBS, Sub) || Sub <- Ss], + {ok, S#st{reporters = [], subscribers = []}}; +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +reporter_pids() -> + ets:select(?EXOMETER_REPORTERS, + [{#reporter{pid = '$1', _ = '_'}, + [{is_pid,'$1'}], ['$1']}]). + +reporter_by_pid(Pid) -> + ets:select(?EXOMETER_REPORTERS, + [{#reporter{pid = Pid, _='_'}, [], ['$_']}]). + +reporter_by_mref(Ref) -> + ets:select(?EXOMETER_REPORTERS, + [{#reporter{mref = Ref, _='_'}, [], ['$_']}]). + +try_send(To, Msg) -> + try To ! Msg + catch + error:_ -> + Msg + end. + +is_valid_metric({find, Name}, _DataPoint) when is_list(Name) -> + true; +is_valid_metric({select, Name}, _DataPoint) when is_list(Name) -> + try ets:match_spec_compile(Name), true + catch + error:_ -> error + end; +is_valid_metric(Name, default) when is_list(Name) -> + case exometer:info(Name, type) of + undefined -> false; + _ -> true + end; +is_valid_metric(Name, DataPoint) when is_list(Name) -> + case dp_list(DataPoint) of + [] -> error; + [_|_] = DataPoints -> + case exometer:info(Name, datapoints) of + undefined -> false; + DPs -> + case DataPoints -- DPs of + [] -> true; + _ -> false + end + end + end; +is_valid_metric(_, _) -> + false. + +dp_list(DP) when is_list(DP) -> DP; +dp_list(DP) when is_atom(DP) -> [DP]; +dp_list(50) -> [50]; +dp_list(75) -> [75]; +dp_list(90) -> [90]; +dp_list(95) -> [95]; +dp_list(99) -> [99]; +dp_list(999) -> [999]. + +get_values(Name, DataPoint) when is_list(Name) -> + case exometer:get_value(Name, DataPoint) of + {ok, Values} when is_list(Values) -> + [{Name, Values}]; + _ -> + [] + end; +get_values({How, Path}, DataPoint) -> + Entries = case How of + find -> exometer:find_entries(Path); + select -> exometer:select(Path) + end, + lists:foldr( + fun({Name, _, enabled}, Acc) -> + case exometer:get_value(Name, DataPoint) of + {ok, Values} when is_list(Values) -> + [{Name, Values}|Acc]; + _ -> + Acc + end; + (_, Acc) -> Acc + end, [], Entries). + + +assert_no_duplicates([#reporter{name = R}|T]) -> + case lists:keymember(R, #reporter.name, T) of + true -> error({duplicate_reporter, R}); + false -> assert_no_duplicates(T) + end; +assert_no_duplicates([]) -> + ok. + +-spec spawn_reporter(reporter_name(), options()) -> {pid(), reference()}. +spawn_reporter(Reporter, Opt) when is_atom(Reporter), is_list(Opt) -> + Fun = fun() -> + {ok, Mod, St} = reporter_init(Reporter, Opt), + reporter_loop(Mod, St) + end, + Pid = proc_lib:spawn(Fun), + maybe_register(Reporter, Pid, Opt), + MRef = erlang:monitor(process, Pid), + {Pid, MRef}. + +maybe_register(R, Pid, Opts) -> + case lists:keyfind(registered_name, 1, Opts) of + {_, none} -> ok; + {_, Name} -> register(Name, Pid); + false -> register(R, Pid) + end. + +terminate_reporter(#reporter{pid = Pid, mref = MRef}) when is_pid(Pid) -> + Pid ! {exometer_terminate, shutdown}, + receive + {'DOWN', MRef, _, _, _} -> + ok + after 1000 -> + exit(Pid, kill), + erlang:demonitor(MRef, [flush]) + end; +terminate_reporter(#reporter{pid = undefined}) -> + ok. + + + +subscribe_(Reporter, Metric, DataPoint, Interval, RetryFailedMetrics, + Extra, Status) -> + ?log(debug, "subscribe_(~p, ~p, ~p, ~p, ~p, ~p, ~p)~n", [Reporter, Metric, DataPoint, Interval, RetryFailedMetrics, Extra, Status]), + Key = #key{reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + extra = Extra, + retry_failed_metrics = RetryFailedMetrics + }, + case ets:lookup(?EXOMETER_SUBS, Key) of + [] -> ets:insert(?EXOMETER_SUBS, + #subscriber{key = Key, + interval = Interval, + t_ref = maybe_send_after(Status, Key, Interval)}); + _ -> + ?log(debug, "subscribe_(): not adding duplicate subscription") + end. + +maybe_send_after(enabled, Key, Interval) when is_integer(Interval) -> + erlang:send_after( + Interval, self(), subscr_timer_msg(Key, Interval)); +maybe_send_after(_, _, _) -> + undefined. + +unsubscribe_(Reporter, Metric, DataPoint, Extra) -> + ?log(info, "unsubscribe_(~p, ~p, ~p, ~p)~n", + [ Reporter, Metric, DataPoint, Extra]), + case ets:lookup(?EXOMETER_SUBS, #key{reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + extra = Extra}) of + [#subscriber{} = Sub] -> + unsubscribe_(Sub); + [] -> + not_found + end. + +unsubscribe_(#subscriber{key = #key{reporter = Reporter, + metric = Metric, + datapoint = DataPoint, + extra = Extra} = Key, t_ref = TRef}) -> + try_send( + Reporter, {exometer_unsubscribe, Metric, DataPoint, Extra}), + cancel_timer(TRef), + ets:delete(?EXOMETER_SUBS, Key), + ok. + + +report_values(Found, #key{reporter = Reporter, extra = Extra} = Key) -> + try Reporter ! {exometer_report, Found, Extra} + catch + error:Reason -> + ?log(error, "~p~nKey = ~p~nTrace: ~p", + [Reason, Key, erlang:get_stacktrace()]) + end. + +retrieve_metric({Metric, Type, Enabled}, Acc) -> + Cands = ets:select( + ?EXOMETER_SUBS, + [{#subscriber{key = #key{metric = Metric, _='_'}, + _ = '_'}, [], ['$_']}]), + [ { Metric, exometer:info(Metric, datapoints), + get_subscribers(Metric, Type, Enabled, Cands), Enabled } | Acc ]. + +%% find_entries_in_list(find, Path, List) -> +%% Pat = Path ++ '_', +%% Spec = ets:match_spec_compile([{ {Pat, '_', '_'}, [], ['$_'] }]), +%% ets:match_spec_run(List, Spec); +%% find_entries_in_list(select, Pat, List) -> +%% Spec = ets:match_spec_compile(Pat), +%% ets:match_spec_run(List, Spec). + +get_subscribers(_Metric, _Type, _Status, []) -> + []; + +%% This subscription matches Metric +get_subscribers(Metric, Type, Status, + [ #subscriber { + key = #key { + reporter = SReporter, + metric = Metric, + datapoint = SDataPoint + }} | T ]) -> + ?log(debug,"get_subscribers(~p, ~p, ~p): match~n", [ Metric, SDataPoint, SReporter]), + [ { SReporter, SDataPoint } | get_subscribers(Metric, Type, Status, T) ]; + +%% get_subscribers(Metric, Type, Status, +%% [ #subscriber { +%% key = #key { +%% metric = {How, Path}, +%% reporter = SReporter, +%% datapoint = SDataPoint +%% }} | T ]) -> +%% case find_entries_in_list(How, Path, [{Metric, Type, Status}]) of +%% [] -> +%% get_subscribers(Metric, Type, Status, T); +%% [_] -> +%% [ { SReporter, SDataPoint } +%% | get_subscribers(Metric, Type, Status, T) ] +%% end; + +%% This subscription does not match Metric. +get_subscribers(Metric, Type, Status, + [ #subscriber { + key = #key { + reporter = SReporter, + metric = SMetric, + datapoint = SDataPoint + }} | T]) -> + ?log(debug, "get_subscribers(~p, ~p, ~p) nomatch(~p) ~n", + [ SMetric, SDataPoint, SReporter, Metric]), + get_subscribers(Metric, Type, Status, T). + +%% Purge all subscriptions associated with a specific reporter +%% (that just went down). +purge_subscriptions(R) -> + %% Go through all #subscriber elements in Subs and + %% cancel the timer of those who match the provided reporter + %% + %% Return new #subscriber list with all original subscribers + %% that do not reference reporter R. + Subs = ets:select(?EXOMETER_SUBS, + [{#subscriber{key = #key{reporter = R, _='_'}, + _ = '_'}, [], ['$_']}]), + lists:foreach(fun(#subscriber {key = Key, t_ref = TRef}) -> + cancel_timer(TRef), + ets:delete(?EXOMETER_SUBS, Key) + end, Subs). + +%% Called by the spawn_monitor() call in init +%% Loop and run reporters. +%% Module is expected to implement exometer_report behavior +reporter_init(Reporter, Opts) -> + Module = proplists:get_value(module, Opts, Reporter), + Bulk = proplists:get_value(report_bulk, Opts, false), + case Module:exometer_init(Opts) of + {ok, St} -> + {ok, Module, #rst{st = St, bulk = Bulk}}; + {error, Reason} -> + ?log(error, "Failed to start reporter ~p: ~p~n", [Module, Reason]), + exit(Reason) + end. + +reporter_loop(Module, #rst{st = St, bulk = Bulk} = RSt) -> + NSt = receive + {exometer_report, Found, Extra} -> + {ok, r_exometer_report( + Bulk, Module, Found, Extra, St)}; + {exometer_unsubscribe, Metric, DataPoint, Extra } -> + case Module:exometer_unsubscribe(Metric, DataPoint, Extra, St) of + {ok, St1} -> {ok, St1}; + _ -> {ok, St} + end; + {exometer_subscribe, Metric, DataPoint, Interval, Extra } -> + case Module:exometer_subscribe(Metric, DataPoint, Interval, Extra, St) of + {ok, St1} -> {ok, St1}; + _ -> {ok, St} + end; + {exometer_newentry, Entry} -> + case Module:exometer_newentry(Entry, St) of + {ok, St1} -> {ok, St1}; + _ -> {ok, St} + end; + {exometer_setopts, Metric, Options, Status} -> + case Module:exometer_setopts(Metric, Options, Status, St) of + {ok, St1} -> {ok, St1}; + _ -> {ok, St} + end; + {exometer_terminate, Reason} -> + Module:exometer_terminate(Reason, St), + terminate; + {exometer_proc, {From, Ref}, Req} -> + case Module:exometer_call(Req, From, St) of + {reply, Reply, St1} -> + From ! {Ref, Reply}, + {ok, St1}; + {noreply, St1} -> + {ok, St1}; + _ -> + {ok, St} + end; + {exometer_proc, Req} -> + case Module:exometer_cast(Req, St) of + {noreply, St1} -> + {ok, St1}; + _ -> + {ok, St} + end; + %% Allow reporters to generate their own callbacks. + Other -> + ?log(debug, "Custom invocation: ~p(~p)~n", [ Module, Other]), + case Module:exometer_info(Other, St) of + {ok, St1} -> {ok, St1}; + _ -> {ok, St} + end + end, + case NSt of + {ok, St2} -> + reporter_loop(Module, RSt#rst{st = St2}); + _ -> + ok + end. + +r_exometer_report(false, Module, Found, Extra, St) -> + lists:foldl( + fun({Name, Values}, Acc) -> + lists:foldl( + fun({DP, Val}, Acc1) -> + case Module:exometer_report( + Name, DP, Extra, Val, Acc1) of + {ok, St1} -> St1; + _ -> St + end + end, Acc, Values) + end, St, Found); +r_exometer_report(true, Module, Found, Extra, St) -> + case erlang:function_exported(Module, exometer_report_bulk, 3) of + true -> + case Module:exometer_report_bulk(Found, Extra, St) of + {ok, St1} -> + St1; + _ -> + St + end; + false -> + r_exometer_report(false, Module, Found, Extra, St) + end. + +call(Req) -> + gen_server:call(?MODULE, Req). + +cast(Req) -> + gen_server:cast(?MODULE, Req). + +init_subscriber({Reporter, Metric, DataPoint, Interval, RetryFailedMetrics}) -> + Status = get_reporter_status(Reporter), + subscribe_(Reporter, Metric, DataPoint, Interval, + RetryFailedMetrics, undefined, Status); +init_subscriber({Reporter, Metric, DataPoint, Interval, + RetryFailedMetrics, Extra}) -> + Status = get_reporter_status(Reporter), + subscribe_(Reporter, Metric, DataPoint, Interval, + RetryFailedMetrics, Extra, Status); +init_subscriber({Reporter, Metric, DataPoint, Interval}) -> + Status = get_reporter_status(Reporter), + subscribe_(Reporter, Metric, DataPoint, Interval, + true, undefined, Status); +init_subscriber({apply, {M, F, A}}) -> + lists:foreach(fun(Sub) -> + init_subscriber(Sub) + end, apply(M, F, A)); +init_subscriber({select, Expr}) when tuple_size(Expr)==3; + tuple_size(Expr)==4; + tuple_size(Expr)==5 -> + {Pattern, Reporter, DataPoint, Interval, Retry, Extra} = + case Expr of + {P, R, D, I} -> {P, R, D, I, true, undefined}; + {P, R, D, I, Rf} -> {P, R, D, I, Rf, undefined}; + {P, R, D, I, Rf, X} -> {P, R, D, I, Rf, X} + end, + Status = get_reporter_status(Reporter), + Entries = exometer:select(Pattern), + lists:foreach( + fun({Entry, _, _}) -> + subscribe_(Reporter, Entry, DataPoint, Interval, + Retry, Extra, Status) + end, Entries); + +init_subscriber(Other) -> + ?log(warning, "Incorrect static subscriber spec ~p. " + "Use { Reporter, Metric, DataPoint, Interval [, Extra ]}~n", + [ Other ]). + +get_reporter_status(R) -> + try ets:lookup_element(?EXOMETER_REPORTERS, R, #reporter.status) + catch + error:_ -> disabled + end. + +add_restart(#restart{spec = Spec, + history = H, + save_n = N} = R) -> + T = exometer_util:timestamp(), + H1 = lists:sublist([T|H], 1, N), + case match_frequency(H1, Spec) of + {remove, Action} -> + {remove, Action}; + restart -> + {restart, R#restart{history = H1}} + end. + +match_frequency([H|T], Spec) -> + match_frequency(T, 1, H, Spec). + +match_frequency([H|T], R, Since, Spec) -> + R1 = R+1, + %% Note that we traverse millisec timestamps backwards in time + Span = (Since - H) div 1000, + case find_match(Spec, R1, Span) of + {true, Action} -> + {remove, Action}; + false -> + match_frequency(T, R1, Since, Spec) + end; +match_frequency([], _, _, _) -> + restart. + +find_match([{R1,T1}|Tail], R, T) when R1 =< R, T1 >= T -> + {true, find_action(Tail)}; +find_match([_|Tail], R, T) -> + find_match(Tail, R, T); +find_match([], _, _) -> + false. + +find_action([{M,F} = H|_]) when is_atom(M), is_atom(F) -> H; +find_action([_|T]) -> + find_action(T); +find_action([]) -> + no_action. + +default_restart() -> + [{3, 1}, {10, 30}, {?MODULE, remove_reporter}]. + +get_restart(Opts) -> + case lists:keyfind(restart, 1, Opts) of + {_, R} -> + restart_rec(valid_restart(R)); + false -> + restart_rec(default_restart()) + end. + +restart_rec(L) -> + Save = lists:foldl( + fun + ({R,_}, Acc) when is_integer(R) -> + erlang:max(R, Acc); + (_, Acc) -> + Acc + end, 0, L), + #restart{spec = L, save_n = Save}. + +valid_restart(L) when is_list(L) -> + lists:foreach( + fun({R,T}) when is_integer(R), is_integer(T), R > 0, T > 0 -> + ok; + ({M,F}) when is_atom(M), is_atom(F) -> ok; + (_) -> + erlang:error({invalid_restart_spec, L}) + end, L), + L. + +do_remove_reporter(Reporter) -> + do_remove_reporter(Reporter, true). + +do_remove_reporter(Reporter, Terminate) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [#reporter{} = R] -> + case Terminate of + true -> + terminate_reporter(R); + false -> + ok + end, + ets:delete(?EXOMETER_REPORTERS, Reporter), + purge_subscriptions(Reporter), + ok; + [] -> + {error, not_found} + end. + +change_reporter_status(Reporter, New) -> + case ets:lookup(?EXOMETER_REPORTERS, Reporter) of + [R] -> do_change_reporter_status(R, New); + [] -> {error, not_found} + end. + +do_change_reporter_status(#reporter{name = Reporter, + status = Old} = R, New) -> + case {Old, New} of + {disabled, enabled} -> + restart_reporter(R); + {enabled, disabled} -> + cancel_subscr_timers(Reporter), + terminate_reporter(R), + ets:update_element(?EXOMETER_REPORTERS, + Reporter, [{#reporter.status, disabled}]); + {Old, Old} -> + ok + end, + ok. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_logger.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_logger.erl new file mode 100644 index 0000000..cf62610 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_logger.erl @@ -0,0 +1,424 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @doc Exometer report collector and logger. +%% +%% This module implements a behavior for collecting reporting data and +%% handling it (logging to disk or ets, printing to tty, etc.) +%% +%% The logger has built-in support for receiving input via UDP, TCP or +%% internal Erlang messaging, as well as a plugin API for custom input +%% handling. Correspondingly, it has support for output to TTY or ets, as +%% well as a plugin API for custom output. +%% +%% An example of how the logger can be used can be found in +%% `test/exometer_test_udp_reporter.erl', which implements a UDP-based +%% reporter as well as an input plugin and an output plugin. This reporter +%% is used by `test/exometer_report_SUITE.erl'. +%% +%% Loggers can be combined, e.g. by creating one logger that receives Erlang +%% messages, and other loggers that receive from different sources, prefix +%% their reports and pass them on to the first logger. +%% +%%

Input plugins

+%% +%% An input plugin is initiated by `Module:logger_init_input(State)', where +%% `State' is whatever was given as a `state' option (default: `undefined'). +%% The function must create a process and return `{ok, Pid}'. `Pid' is +%% responsible for setting up whatever input channel is desired, and passes +%% on incoming data to the logger via Erlang messages `{plugin, Pid, Data}'. +%% +%%

Output Chaining

+%% +%% Each incoming data item is passed through the list of output operators. +%% Each output operator is able to modify the data (the `tty' and `ets' +%% operators leave the data unchanged). Output plugins receive the data +%% in `Module:logger_handle_data(Data, State)', which must return +%% `{NewData, NewState}'. The state is private to the plugin, while `NewData' +%% will be passed along to the next output operator. +%% +%%

Flow control

+%% +%% The logger will handle flow control automatically for `udp' and `tcp' +%% inputs. If `{active,once}' or `{active, false}', the logger will trigger +%% `{active, once}' each time it has handled an incoming message. +%% If `{active, N}', it will "refill" the port each time it receives an +%% indication that it has become passive. +%% +%% Input plugins create a process in `Module:logger_init_input/1'. This process +%% can mimick the behavior of Erlang ports by sending a `{plugin_passive, Pid}' +%% message to the logger. The logger will reply with a message, +%% `{plugin_active, N}', where `N' is the value given by the `active' option. +%% @end +-module(exometer_report_logger). + +-behaviour(gen_server). + +-export([new/1]). +-export([start_link/1]). + +-export([info/0, + info/1]). + +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-record(st, {id, + input, + output}). + +-include_lib("parse_trans/include/exprecs.hrl"). +-export_records([tcp, udp, tty, ets, int]). + +%% input records +-record(tcp, {socket, port, options = [], active = once}). +-record(udp, {socket, port, options = [], active = true}). +%% output records +-record(tty, {prefix = []}). +-record(ets, {tab}). +%% both input and output +-record(int, {process}). +-record(plugin, {module, mod_state, process, active = once}). + +-type proplist() :: [{atom(), any()}]. + +-type logger_info() :: {id, any()} + | {input, proplist()} + | {output, proplist()}. + +-type plugin_state() :: any(). + +-callback logger_init_input(any()) -> + {ok, pid()}. +-callback logger_init_output(any()) -> + {ok, plugin_state()}. +-callback logger_handle_data(binary(), plugin_state()) -> + {binary(), plugin_state()}. + +-spec new([{id, any()} | {input, list()} | {output, list()}]) -> {ok,pid()}. +%% @doc Create a new logger instance. +%% +%% This function creates a logger process with the given input and output +%% parameters. +%% +%% * `{id, ID}' is mainly for documentation and simplifying identification +%% of instances returned by {@link info/0}. +%% * `{input, PropList}' specifies what the logger listens to. Only the first +%% `input' entry is regarded, but the option is mandatory. +%% * `{output, PropList}' specifies what the logger should to with received +%% data. Multiple `output' entries are allowed, and they will be processed +%% in the order given. +%% +%% Valid input options: +%% +%% * `{mode, udp | tcp | internal | plugin}' defines the protocol +%% * `{active, false | true | once | N}' provides flow control. Default: `true'. +%% * (mode-specific options) +%% +%% Valid output options: +%% +%% * `{mode, tty | ets | plugin | internal}' defines output types +%% * (output-specific options) +%% +%% Mode-specific options, `udp': +%% +%% * `{port, integer()}' - UDP port number +%% * `{options, list()}' - Options to pass to {@link gen_udp:open/2} +%% +%% Mode-specific options, `tcp': +%% +%% * `{port, integer()}' - TCP port number +%% * `{options, list()}' - Options to pass to {@link gen_tcp:listen/2} +%% +%% Mode-specific options, `tty': +%% +%% * `{prefix, iolist()}' - Prefix string inserted before the data, which is +%% printed as-is (note that any delimiter would need to be part of the prefix) +%% +%% Mode-specific options, `ets': +%% * `{table, ets:table()}' - Ets table identifier. If not specified, an +%% ordered-set table will be created by the logger process. The incoming +%% data will be inserted as `{os:timestamp(), Data}'. +%% +%% Mode-specific options, `internal': +%% * `{process, PidOrRegname}' specifies another logger instance, which is to +%% receive data from this logger (if used in output), or which is allowed +%% to send to this logger (if used in input). If no process is given for +%% input, any process can send data (on the form +%% `{exometer_report_logger, Pid, Data}') to this logger. +%% +%% Mode-specific options, `plugin': +%% +%% * `{module, Module}' - name of callback module +%% (behaviour: `exometer_report_logger') +%% * `{state, State}' - Passed as initial argument to +%% `Module:logger_init_input/1' or `Module:logger_init_output/1', depending +%% on whether the plugin is specified as input or output. +%% @end +new(Options) -> + supervisor:start_child(exometer_report_logger_sup, [Options]). + +-spec start_link(proplist()) -> {ok, pid()}. +%% @doc Start function for logger instance. +%% +%% This function is the start function eventually called as a result from +%% {@link new/1}, but whereas `new/1' creates a supervised instance, this +%% function simply creates the process. It would normally not be used directly. +%% @end +start_link(Options) -> + ID = exometer_util:get_opt(id, Options, undefined), + Input = get_input(Options), + Output = get_output(Options), + gen_server:start_link(?MODULE, {ID, Input, Output}, []). + +-spec info() -> [{pid(), [logger_info()]}]. +%% @doc List active logger instances. +%% +%% This function lists the instances started via {@link new/1}, along with their +%% respective settings as nested property lists. +%% @end +info() -> + [{P, info(P)} || {_, P, _, _} <- supervisor:which_children( + exometer_report_logger_sup)]. +-spec info(pid()) -> [logger_info()]. +%% @doc Lists the settings of a given logger instance. +info(P) -> + gen_server:call(P, info). + +%% Client-side check +get_input(Opts) -> + L = exometer_util:get_opt(input, Opts), + get_input(exometer_util:get_opt(mode, L), L). + +get_input(tcp, L) -> + Port = exometer_util:get_opt(port, L), + Options = exometer_util:get_opt(options, L, []), + Active = exometer_util:get_opt( + active, L, get_opt_active(Options)), + #tcp{port = Port, options = Options, active = Active}; +get_input(udp, L) -> + Port = exometer_util:get_opt(port, L), + Options = exometer_util:get_opt(options, L, []), + Active = exometer_util:get_opt( + active, L, get_opt_active(Options)), + #udp{port = Port, options = Options, active = Active}; +get_input(internal, L) -> + P = exometer_util:get_opt(process, L, undefined), + #int{process = P}; +get_input(plugin, L) -> + Mod = exometer_util:get_opt(module, L), + St = exometer_util:get_opt(state, L, undefined), + Active = exometer_util:get_opt(active, L, true), + #plugin{module = Mod, mod_state = St, active = Active}. + + +get_opt_active(Opts) -> + case lists:keyfind(active, 1, Opts) of + {_, true} -> true; + {_, N} when is_integer(N) -> N; + _ -> once + end. + +%% Client-side check +get_output(Opts) -> + [get_output(exometer_util:get_opt(mode, O), O) || {output, O} <- Opts]. + +get_output(tty, O) -> + Prefix = exometer_util:get_opt(prefix, O, []), + #tty{prefix = Prefix}; +get_output(ets, O) -> + Tab = exometer_util:get_opt(tab, O, undefined), + #ets{tab = Tab}; +get_output(internal, O) -> + P = exometer_util:get_opt(process, O), + #int{process = P}; +get_output(plugin, O) -> + Mod = exometer_util:get_opt(module, O), + St = exometer_util:get_opt(state, O, undefined), + #plugin{module = Mod, mod_state = St}. + + +%% Gen_server callbacks + +%% @private +init({ID, Input, Output}) -> + NewL = init_input(Input), + NewO = init_output(Output), + {ok, #st{id = ID, + input = NewL, + output = NewO}}. + +%% @private +handle_call(info, _, #st{id = ID, input = I, output = O} = S) -> + {reply, info_(ID, I, O), S}; +handle_call(_Req, _From, St) -> + {reply, {error, unsupported}, St}. + +%% @private +handle_cast({socket, Socket}, #st{input = L} = S) -> + case L of + #tcp{active = N} = T -> + case inet:getopts(Socket, [active]) of + {ok, [{active, false}]} -> + inet:setopts(Socket, [{active, N}]); + _ -> + ok + end, + {noreply, S#st{input = T#tcp{socket = Socket}}}; + _ -> + {noreply, S} + end; +handle_cast(_Msg, St) -> + {noreply, St}. + +%% @private +handle_info({tcp, Socket, Data}, #st{input = #tcp{socket = Socket, + active = Active}, + output = Out} = S) -> + handle_data(Data, Out), + check_active(Socket, Active), + {noreply, S}; +handle_info({udp, Socket, _Host, _Port, Data}, + #st{input = #udp{socket = Socket}, output = Out} = S) -> + Out1 = handle_data(Data, Out), + {noreply, S#st{output = Out1}}; +handle_info({plugin, Pid, Data}, #st{input = #plugin{process = Pid}, + output = Out} = S) -> + Out1 = handle_data(Data, Out), + {noreply, S#st{output = Out1}}; +handle_info({tcp_passive, Socket}, #st{input = #tcp{socket = Socket, + active = Active}} = S) -> + inet:setopts(Socket, Active), + {noreply, S}; +handle_info({udp_passive, Socket}, #st{input = #udp{socket = Socket, + active = Active}} = S) -> + inet:setopts(Socket, Active), + {noreply, S}; +handle_info({plugin_passive, Pid}, #st{input = #plugin{process = Pid, + active = Active}} = S) -> + Pid ! {plugin_active, Active}, + {noreply, S}; +handle_info({?MODULE, P, Data}, #st{input = #int{process = Pl}, + output = Out} = S) + when Pl =:= P; Pl =:= undefined -> + Out1 = handle_data(Data, Out), + {noreply, S#st{output = Out1}}; +handle_info(_, S) -> + {noreply, S}. + +%% @private +terminate(_, _) -> + ok. + +%% @private +code_change(_FromVsn, S, _Extra) -> + {ok, S}. + +%% End gen_server callbacks + +info_(ID, I, O) -> + [{id, ID}, + {input, ensure_list(pp(I))}, + {output, ensure_list(pp(O))}]. + +ensure_list(I) when is_tuple(I) -> + [I]; +ensure_list(I) when is_list(I) -> + I. + +%% Copied from git:uwiger/jobs/src/jobs_info.erl +pp(L) when is_list(L) -> + [pp(X) || X <- L]; +pp(X) -> + case '#is_record-'(X) of + true -> + RecName = element(1,X), + {RecName, lists:zip( + '#info-'(RecName,fields), + pp(tl(tuple_to_list(X))))}; + false -> + if is_tuple(X) -> + list_to_tuple(pp(tuple_to_list(X))); + true -> + X + end + end. + +init_input(#tcp{port = Port, + options = Opts} = T) -> + _ = spawn_tcp_acceptor(Port, Opts), + T; +init_input(#udp{port = Port, options = Opts} = U) -> + {ok, Socket} = gen_udp:open(Port, Opts), + U#udp{socket = Socket}; +init_input(#plugin{module = Mod, mod_state = St} = P) -> + case Mod:logger_init_input(St) of + {ok, Pid} when is_pid(Pid) -> + P#plugin{process = Pid} + end; +init_input(#int{} = I) -> + I. + + +spawn_tcp_acceptor(Port, Opts) -> + Parent = self(), + spawn_link(fun() -> + {ok, LSock} = gen_tcp:listen(Port, Opts), + {ok, Socket} = gen_tcp:accept(LSock), + ok = gen_tcp:controlling_process(Socket, Parent), + gen_server:cast(Parent, {socket, Socket}) + end). + +init_output(Out) -> + [init_output_(O) || O <- Out]. + +init_output_(#tty{} = TTY) -> TTY; +init_output_(#int{} = Int) -> Int; +init_output_(#ets{tab = T} = E) -> + Tab = case T of + undefined -> + ets:new(?MODULE, [ordered_set]); + _ -> + T + end, + E#ets{tab = Tab}; +init_output_(#plugin{module = Mod, mod_state = St} = P) -> + {ok, St1} = Mod:logger_init_output(St), + P#plugin{mod_state = St1}. + +check_active(Socket, once) -> + inet:setopts(Socket, [{active, once}]); +check_active(_Socket, _) -> + ok. + +handle_data(Data, Out) -> + handle_data(Data, Out, []). + +handle_data(Data, [H|T], Acc) -> + {Data1, H1} = handle_data_(Data, H), + handle_data(Data1, T, [H1|Acc]); +handle_data(_, [], Acc) -> + lists:reverse(Acc). + +handle_data_(Data, #tty{prefix = Pfx} = Out) -> + io:fwrite(iolist_to_binary([Pfx, Data, $\n])), + {Data, Out}; +handle_data_(Data, #ets{tab = T} = Out) -> + ets:insert(T, {os:timestamp(), Data}), + {Data, Out}; +handle_data_(Data, #int{process = P} = Out) -> + try P ! {?MODULE, self(), Data} catch _:_ -> error end, + {Data, Out}; +handle_data_(Data, #plugin{module = Mod, mod_state = ModSt} = Out) -> + {Data1, ModSt1} = Mod:logger_handle_data(Data, ModSt), + {Data1, Out#plugin{mod_state = ModSt1}}. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_logger_sup.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_logger_sup.erl new file mode 100644 index 0000000..0031ced --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_logger_sup.erl @@ -0,0 +1,27 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2015 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @private +-module(exometer_report_logger_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). + +start_link() -> + supervisor:start_link({local,?MODULE}, ?MODULE, []). + + +init(_) -> + {ok, {{simple_one_for_one, 3, 10}, + [?CHILD(exometer_report_logger, worker)]}}. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_tty.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_tty.erl new file mode 100644 index 0000000..b08ae97 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_report_tty.erl @@ -0,0 +1,139 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +%% @doc Custom reporting probe for Hosted Graphite. +%% +%% Collectd unix socket integration. +%% All data subscribed to by the plugin (through exosense_report:subscribe()) +%% will be reported to collectd. +%% @end + +%% We have to do this as a gen server since collectd expects periodical +%% metrics "refreshs", even if the values have not changed. We do this +%% through erlang:send_after() calls with the metrics / value update +%% to emit. +%% +%% Please note that exometer_report_collectd is still also a +%% exometer_report implementation. +-module(exometer_report_tty). + +-behaviour(exometer_report). + +-export( + [ + exometer_init/1, + exometer_info/2, + exometer_cast/2, + exometer_call/3, + exometer_report/5, + exometer_subscribe/5, + exometer_unsubscribe/4, + exometer_newentry/2, + exometer_setopts/4, + exometer_terminate/2 + ]). + +-include_lib("hut/include/hut.hrl"). +-include("exometer.hrl"). + +-define(SERVER, ?MODULE). +%% calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}). +-define(UNIX_EPOCH, 62167219200). + +-record(st, {type_map = []}). + +%%%=================================================================== +%%% exometer_report callback API +%%%=================================================================== + +exometer_init(Opts) -> + ?log(info, "~p(~p): Starting~n", [?MODULE, Opts]), + TypeMap = proplists:get_value(type_map, Opts, []), + {ok, #st{type_map = TypeMap}}. + +exometer_subscribe(_Metric, _DataPoint, _Interval, _Extra, St) -> + {ok, St}. + +exometer_unsubscribe(_Metric, _DataPoint, _Extra, St) -> + {ok, St}. + +%% Invoked through the remote_exometer() function to +%% send out an update. +exometer_report(Metric, DataPoint, Extra, Value, St) -> + ?log(debug, "Report metric ~p_~p = ~p~n", [Metric, DataPoint, Value]), + %% Report the value and setup a new refresh timer. + Key = Metric ++ [DataPoint], + Type = case exometer_util:report_type(Key, Extra, St#st.type_map) of + {ok, T} -> T; + error -> unknown + end, + Str = [?MODULE_STRING, ": ", name(Metric, DataPoint), $\s, + timestamp(), ":", value(Value), io_lib:format(" (~w)", [Type]), $\n], + io:put_chars(lists:flatten(Str)), + {ok, St}. + +exometer_call(Unknown, From, St) -> + ?log(info, "Unknown call ~p from ~p", [Unknown, From]), + {ok, St}. + +exometer_cast(Unknown, St) -> + ?log(info, "Unknown cast: ~p", [Unknown]), + {ok, St}. + +exometer_info(Unknown, St) -> + ?log(info, "Unknown info: ~p", [Unknown]), + {ok, St}. + +exometer_newentry(_Entry, St) -> + {ok, St}. + +exometer_setopts(_Metric, _Options, _Status, St) -> + {ok, St}. + +exometer_terminate(_, _) -> + ignore. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +%% Add metric and datapoint within metric +name(Metric, Datapoint) when is_integer(Datapoint) -> + metric_to_string(Metric) ++ "_" ++ integer_to_list(Datapoint); +name(Metric, DataPoint) -> + metric_to_string(Metric) ++ "_" ++ atom_to_list(DataPoint). + +metric_to_string([Final]) -> + metric_elem_to_list(Final); +metric_to_string([H | T]) -> + metric_elem_to_list(H) ++ "_" ++ metric_to_string(T). + +metric_elem_to_list(E) when is_atom(E) -> + atom_to_list(E); +metric_elem_to_list(E) when is_list(E) -> + E; +metric_elem_to_list(E) when is_binary(E) -> + [E]; +metric_elem_to_list(E) when is_integer(E) -> + integer_to_list(E). + +%% Add value, int or float, converted to list +value(V) when is_integer(V) -> integer_to_list(V); +value(V) when is_float(V) -> io_lib:format("~f", [V]); +value(_) -> "0". + +timestamp() -> + integer_to_list(unix_time()). + +unix_time() -> + datetime_to_unix_time(erlang:universaltime()). + +datetime_to_unix_time({{_,_,_},{_,_,_}} = DateTime) -> + calendar:datetime_to_gregorian_seconds(DateTime) - ?UNIX_EPOCH. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_shallowtree.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_shallowtree.erl new file mode 100644 index 0000000..354bbf0 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_shallowtree.erl @@ -0,0 +1,134 @@ +%% @doc Size-constrained leftist tree +%% Inspired by Leftist Trees by Sartaj Sahni. +%% +%% The purpose of this module is to efficiently store a limited number of +%% values in e.g. a lossy histogram (ex. {@link exometer_slot_slide}). The +%% complexity of insert operations is log(N), but once the tree is full, +%% only values higher than the minimum value already in the tree will be +%% inserted, and the old minimum is deleted - i.e. two O(log N) operations. +%% For other values, the cost will be only two comparisons, since the +%% top node in the tree always contains the minimum. +%% @end +-module(exometer_shallowtree). + +-export([new/1, + insert/3, + take_min/1, + to_list/1, + filter/2, + size/1, + limit/1]). + +-export([fill/1, fill1/2]). + +-export_type([tree/0]). + +-record(t, {size = 0, + limit = 10, + tree = []}). + +-type tree() :: #t{}. + +-spec new(pos_integer()) -> tree(). +%% @doc Create an empty tree limited to `Size'. +new(Size) when is_integer(Size), Size > 0 -> + #t{limit = Size}. + +-spec size(tree()) -> non_neg_integer(). +%% @doc Returns the number of values stored in the given tree. +size(#t{size = Sz}) -> + Sz. + +-spec limit(tree()) -> non_neg_integer(). +%% @doc Returns the maximum number of values for the given tree. +limit(#t{limit = L}) -> + L. + +-spec insert(number(), any(), tree()) -> tree(). +%% @doc Insert value `V' into tree `T'. +%% +%% If the tree is full and `V' is smaller than the minimum, this function +%% will return immediately, leaving the tree unchanged. +%% @end +insert(K, V, #t{size = X, limit = X, tree = Tr} = T) when is_number(K) -> + case K =< element(1, Tr) of + true -> + T; + false -> + {_, _, Tr1} = take_min_(Tr), + T#t{tree = insert_(K, V, Tr1)} + end; +insert(K, V, #t{size = Sz, tree = Tr} = T) when is_number(K) -> + T#t{size = Sz+1, tree = insert_(K, V, Tr)}. + +insert_(K, V, []) -> mknode(K, V); +insert_(K, V, T ) -> meld(mknode(K, V), T). + +-spec take_min(tree()) -> {number(), any(), tree()} | error. +%% @doc Extract the smallest value from the tree `T'. +%% +%% If the tree is empty, `error' is returned, otherwise `{Minimum, NewTree}'. +%% @end +take_min(#t{size = Sz, tree = Tr} = T) -> + case take_min_(Tr) of + error -> error; + {K, V, Tr1} -> + {K, V, T#t{size = Sz-1, tree = Tr1}} + end. + +take_min_([]) -> error; +take_min_({K,V,_,L,R}) -> {K, V, meld(L, R)}. + +-spec to_list(tree()) -> [{number(), any()}]. +%% @doc Converts a tree to a list. +%% +%% The list will not be ordered, since the aim is to produce the list as +%% quickly as possible. Also, `lists:sort(to_list(Tree))', if to_list/1 +%% uses brute force, seems faster than most approaches for extracting +%% values in order. +%% @end +to_list(#t{tree = T}) -> to_list_([T]). + +to_list_([]) -> []; +to_list_([{K,V,_,L,R}|T]) -> [{K,V}|to_list_([L,R|T])]; +to_list_([[]|T]) -> to_list_(T). + + +filter(F, #t{tree = T}) -> filter_(F, [T]). + +filter_(_, []) -> []; +filter_(F, [{K,V,_,L,R}|T]) -> + case F(K,V) of false -> filter_(F, [L,R|T]); + {true, Keep} -> [Keep|filter_(F, [L,R|T])] + end; +filter_(F, [[]|T]) -> filter_(F, T). + +meld({K1,V1, _, L1, R1} = T1, {K2,V2, _, L2, R2} = T2) -> + case K1 < K2 of + true -> + mknode(K1,V1, L1, meld(R1, T2)); + false -> + mknode(K2,V2, L2, meld(R2, T1)) + end; +meld([], T2) -> T2; +meld(T1, []) -> T1; +meld([], []) -> []. + +mknode(K,V) -> {K,V,1,[],[]}. + +mknode(K,V,{_,_,S1,_,_} = T1, {_,_,S2,_,_} = T2) when S1 < S2 -> + {K,V, S1+1, T2, T1}; +mknode(K,V, [], [] ) -> {K,V, 1 , [], []}; +mknode(K,V, [], {_,_,S2,_,_} = T2) -> {K,V, S2+1, T2, []}; +mknode(K,V, {_,_,S1,_,_} = T1, []) -> {K,V, S1+1, T1, []}; +mknode(K,V, T1, {_,_,S2,_,_} = T2) -> {K,V, S2+1, T1, T2}. + +fill(Size) -> + L = lists:seq(1,Size), + T0 = new(Size), + timer:tc(?MODULE, fill1, [L, T0]). + +fill1([H|T], Tree) -> + fill1(T, insert(H, x, Tree)); +fill1([], Tree) -> + Tree. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_slide.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_slide.erl new file mode 100644 index 0000000..a712431 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_slide.erl @@ -0,0 +1,208 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% +%% @author Tony Rogvall +%% @author Ulf Wiger +%% @author Magnus Feuer +%% +%% @doc Efficient sliding-window buffer +%% +%% Initial implementation: 29 Sep 2009 by Tony Rogvall +%% +%% This module implements an efficient sliding window, maintaining +%% two lists - a primary and a secondary. Values are paired with a +%% timestamp (millisecond resolution, see `exometer_util:timestamp/0') +%% and prepended to the primary list. When the time span between the oldest +%% and the newest entry in the primary list exceeds the given window size, +%% the primary list is shifted into the secondary list position, and the +%% new entry is added to a new (empty) primary list. +%% +%% The window can be converted to a list using `to_list/1' or folded +%% over using `foldl/3'. +%% @end +-module(exometer_slide). + +-export([new/2, new/5, + reset/1, + add_element/2, + add_element/3, + add_element/4, + to_list/1, + foldl/3, + foldl/4]). + +-compile(inline). +-compile(inline_list_funcs). + +-import(lists, [reverse/1]). +-import(exometer_util, [timestamp/0]). + +-type value() :: any(). +-type cur_state() :: any(). +-type timestamp() :: exometer_util:timestamp(). +-type sample_fun() :: fun((timestamp(), value(), cur_state()) -> + cur_state()). +-type transform_fun() :: fun((timestamp(), cur_state()) -> + cur_state()). + +-type fold_acc() :: any(). +-type fold_fun() :: fun(({timestamp(),value()}, fold_acc()) -> fold_acc()). + +%% Fixed size event buffer +-record(slide, {size = 0 :: integer(), % ms window + n = 0 :: integer(), % number of elements in buf1 + max_n :: undefined | integer(), % max no of elements + last = 0 :: integer(), % millisecond timestamp + buf1 = [] :: list(), + buf2 = [] :: list()}). + +-spec new(integer(), integer(), + sample_fun(), transform_fun(), list()) -> #slide{}. +%% @doc Callback function for exometer_histogram +%% +%% This function is not intended to be used directly. The arguments +%% `_SampleFun' and `_TransformFun' are ignored. +%% @end +new(Size, _Period, _SampleFun, _TransformFun, Opts) -> + new(Size, Opts). + +-spec new(_Size::integer(), _Options::list()) -> #slide{}. +%% @doc Create a new sliding-window buffer. +%% +%% `Size' determines the size in milliseconds of the sliding window. +%% The implementation prepends values into a primary list until the oldest +%% element in the list is `Size' ms older than the current value. It then +%% swaps the primary list into a secondary list, and starts prepending to +%% a new primary list. This means that more data than fits inside the window +%% will be kept - upwards of twice as much. On the other hand, updating the +%% buffer is very cheap. +%% @end +new(Size, Opts) -> + #slide{size = Size, + max_n = proplists:get_value(max_n, Opts, infinity), + last = timestamp(), + buf1 = [], + buf2 = []}. + +-spec reset(#slide{}) -> #slide{}. +%% @doc Empty the buffer +%% +reset(Slide) -> + Slide#slide{n = 0, buf1 = [], buf2 = [], last = 0}. + +-spec add_element(value(), #slide{}) -> #slide{}. +%% @doc Add an element to the buffer, tagging it with the current time. +%% +%% Note that the buffer is a sliding window. Values will be discarded as they +%% move out of the specified time span. +%% @end +%% +add_element(Evt, Slide) -> + add_element(timestamp(), Evt, Slide, false). + +-spec add_element(timestamp(), value(), #slide{}) -> #slide{}. +%% @doc Add an element to the buffer, tagged with the given timestamp. +%% +%% Apart from the specified timestamp, this function works just like +%% {@link add_element/2}. +%% @end +%% +add_element(TS, Evt, Slide) -> + add_element(TS, Evt, Slide, false). + +-spec add_element(timestamp(), value(), #slide{}, true) -> + {boolean(), #slide{}}; + (timestamp(), value(), #slide{}, false) -> + #slide{}. +%% @doc Add an element to the buffer, optionally indicating if a swap occurred. +%% +%% This function works like {@link add_element/3}, but will also indicate +%% whether the sliding window buffer swapped lists (this means that the +%% 'primary' buffer list became full and was swapped to 'secondary', starting +%% over with an empty primary list. If `Wrap == true', the return value will be +%% `{Bool,Slide}', where `Bool==true' means that a swap occurred, and +%% `Bool==false' means that it didn't. +%% +%% If `Wrap == false', this function works exactly like {@link add_element/3}. +%% +%% One possible use of the `Wrap == true' option could be to keep a sliding +%% window buffer of values that are pushed e.g. to an external stats service. +%% The swap indication could be a trigger point where values are pushed in order +%% to not lose entries. +%% @end +%% +add_element(_TS, _Evt, Slide, Wrap) when Slide#slide.size == 0 -> + add_ret(Wrap, false, Slide); +add_element(TS, Evt, #slide{last = Last, size = Sz, + n = N, max_n = MaxN, + buf1 = Buf1} = Slide, Wrap) -> + N1 = N+1, + if TS - Last > Sz; N1 > MaxN -> + %% swap + add_ret(Wrap, true, Slide#slide{last = TS, + n = 1, + buf1 = [{TS, Evt}], + buf2 = Buf1}); + true -> + add_ret(Wrap, false, Slide#slide{n = N1, buf1 = [{TS, Evt} | Buf1]}) + end. + +add_ret(false, _, Slide) -> + Slide; +add_ret(true, Flag, Slide) -> + {Flag, Slide}. + + +-spec to_list(#slide{}) -> [{timestamp(), value()}]. +%% @doc Convert the sliding window into a list of timestamped values. +%% @end +to_list(#slide{size = Sz}) when Sz == 0 -> + []; +to_list(#slide{size = Sz, n = N, max_n = MaxN, buf1 = Buf1, buf2 = Buf2}) -> + Start = timestamp() - Sz, + take_since(Buf2, Start, n_diff(MaxN, N), reverse(Buf1)). + +-spec foldl(timestamp(), fold_fun(), fold_acc(), #slide{}) -> fold_acc(). +%% @doc Fold over the sliding window, starting from `Timestamp'. +%% +%% The fun should as `fun({Timestamp, Value}, Acc) -> NewAcc'. +%% The values are processed in order from oldest to newest. +%% @end +foldl(_Timestamp, _Fun, _Acc, #slide{size = Sz}) when Sz == 0 -> + []; +foldl(Timestamp, Fun, Acc, #slide{size = Sz, n = N, max_n = MaxN, + buf1 = Buf1, buf2 = Buf2}) -> + Start = Timestamp - Sz, + lists:foldr( + Fun, lists:foldl(Fun, Acc, take_since( + Buf2, Start, n_diff(MaxN,N), [])), Buf1). + +-spec foldl(fold_fun(), fold_acc(), #slide{}) -> fold_acc(). +%% @doc Fold over all values in the sliding window. +%% +%% The fun should as `fun({Timestamp, Value}, Acc) -> NewAcc'. +%% The values are processed in order from oldest to newest. +%% @end +foldl(Fun, Acc, Slide) -> + foldl(timestamp(), Fun, Acc, Slide). + +take_since([{TS,_} = H|T], Start, N, Acc) when TS >= Start, N > 0 -> + take_since(T, Start, decr(N), [H|Acc]); +take_since(_, _, _, Acc) -> + %% Don't reverse; already the wanted order. + Acc. + +decr(N) when is_integer(N) -> + N-1. + +n_diff(A, B) when is_integer(A) -> + A - B; +n_diff(_, B) -> + B. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_slot_slide.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_slot_slide.erl new file mode 100644 index 0000000..e3ac953 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_slot_slide.erl @@ -0,0 +1,421 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + + +%% @doc +%% +%% == Introduction == +%% This module defines a sliding time-window histogram with execution +%% cost control. +%% +%% The problem with traditional histograms is that every sample is +%% stored and processed, no matter what the desired resolution is. +%% +%% If a histogram has a sliding window of 100 seconds, and we have a +%% sample rate of 100Hz will give us 10000 elements to process every time +%% we want to calculate that average, which is expensive. +%% The same goes for min/max finds, percentile calculations, etc. +%% +%% The solution is to introduce cost-control, where we can balance +%% execution time against sample resolution. +%% +%% The obvious implementation is to lower the sample rate by throwing +%% away samples and just store evey N samples. However, this will +%% mean potentially missed min/max values, and other extreme data +%% that is seen by the edge code but is thrown away. +%% +%% A slotted slide histogram will define a number of time slots, each +%% spanning a fixed number of milliseconds. The slider then stores +%% slots that cover a given timespan into the past (a rolling +%% historgram). All slots older than the timespan are discarded. +%% +%% The "current" slot is defined as the time period between the time +%% stamp of the last stored slot and the time when it is time to store +%% the next slot. If the slot period (size) is 100ms, and the last +%% slot was stored in the histogram at msec 1200, the current slot +%% period ends at msec 1300. +%% +%% All samples received during the current slot are processed by a +%% low-cost fun that updates the current slot state. When the current +%% slot ends, another fun is used to transform the current slot state +%% to a value that is stored in the histogram. +%% +%% If a simple average is to be calculated for all samples received, +%% the sample-processing fun will add to the sum of the received +%% samples, and increment sample counter. When the current slot +%% expires, the result of SampleSum / SampleCount is stored in the +%% slot. +%% +%% If min/max are to be stored by the slotted histograms, the current +%% slot state would have a { Min, Max } tuple that is upadted with the +%% smallest and largest values received during the period. At slot +%% expiration the min/max tuple is simply stored, by the +%% transformation fun, in the histogram slots. +%% +%% By adjusting the slot period and the total histogram duration, the +%% cost of analysing the entire histogram can be balanced against +%% the resolution of that analysis. +%% +%% +%% == SLOT HISTOGRAM MANAGEMENT == +%% +%% The slide state maintains a list of { TimeStamp, SlotElem } +%% slot tuples. TimeStamp is the time period (in monotonic ms), +%% rounded down to the resolution of the slot period, and SlotElem is +%% the tuple generated by the current slot transformation MFA. The +%% list is sorted on descending time stamps (newest slot first). +%% +%% Normally each element in the list has a timestamp that is +%% SlotPeriod milliseconds newer than the next slot in the +%% list. However, if no samples are received during the current slot, +%% no slot for that time stamp will be stored, leaving a hole in the +%% list. Normally, the the slot list would look like this (with a 100 +%% msec slot period and a simple average value): +%% +%%
+%%     [ { 1400, 23.2 }, { 1300, 23.1 }, { 1200, 22.8 }, { 1100, 23.0 } ]
+%% 
+%% +%% If no samples were received during the period between 1200 and 1300 +%% (ms), no slot would be stored at that time stamp, yielding the +%% following list: +%% +%%
+%%     [ { 1400, 23.2 }, { 1300, 23.1 }, { 1100, 23.0 } ]
+%% 
+%% +%% This means that the total length of the slot list may vary, even +%% if it always covers the same time span into the past. +%% +%% == SLOT LISTS == +%% +%% The slotted slider stores its slots in two lists, list1, and list2. +%% list1 contains the newest slots. Once the oldest element in list1 +%% is older than the time span covered by the histogram, the entire +%% content of list1 is shifted into list2, and list1 is set to []. +%% The old content of list2, if any, is discarded during the shift. +%% +%% When the content of the histogram is to be retrieved (through +%% fold{l,r}(), or to_list()), the entire content of list1 is prepended to +%% the part of list2 that is within than the time span covered by the +%% histogram. +%% +%% If the time span of the histogram is 5 seconds, with a 1 second +%% slot period, list1 can look like this : +%% +%%
+%%     list1 = [ {5000, 1.2}, {4000, 2.1}, {3000, 2.0}, {2000, 2.3}, {1000, 2.8} ]
+%% 
+%% +%% When the next slot is stored in the list, add_slot() will detect +%% that the list is full since the oldest element ({1000, 20.8}) will +%% fall outside the time span covered by the histogram. List1 will +%% shifted to List2, and List1 will be set to the single new slot that +%% is to be stored: +%% +%%
+%%     list1 = [ {6000, 1.8} ]
+%%     list2 = [ {5000, 1.2}, {4000, 2.1}, {3000, 2.0}, {2000, 2.3}, {1000, 2.8} ]
+%% 
+%% +%% To_list() and fold{l,r}() will return list1, and the first four elements +%% of list2 in order to get a complete histogram covering the entire +%% time span: +%% +%%
+%%     [ {6000, 1.8}, {5000, 1.2}, {4000, 2.1}, {3000, 2.0}, {2000, 2.3} ]
+%% 
+%% +%% +%% == SAMPLE PROCESSING AND TRANSFORMATION FUN == +%% +%% Two funs are provided to the new() function of the slotted slide +%% histogram. The processing function is called by add_element() and +%% will take the same sample value provided to that function together +%% with the current timestamp and slot state as arguments. The +%% function will return the new current slot state. +%% +%%
+%%     M:F(TimeStamp, Value, State) -> NewState
+%% 
+%% +%% The first call to the sample processing fun when the current slot +%% is newly reset (just after a slot has been added to the histogram), +%% state will be set to 'undefined' +%% +%%
+%%     M:F(TimeStamp, Value, undefined) -> NewState
+%% 
+%% +%% The transformation fun is called when the current slot has expired +%% and is to be stored in the histogram. It will receive the current +%% timestamp and slot state as arguments and returns the element to +%% be stored (together with a slot timestamp) in the slot histogram. +%% +%%
+%%     M:F(TimeStamp, State) -> Element
+%% 
+%% +%% Element will present in the lists returned by to_list() and fold{l,r}(). +%% If the transformation MFA cannot do its job, for example because +%% no samples have been processed by the sample processing fun, +%% the transformation fun should return 'undefined' +%% +%% See new/2 and its avg_sample() and avg_transform() functions for an +%% example of a simple average value implementation. +%% +%% @end +-module(exometer_slot_slide). + +-export([new/2, new/4, new/5, + add_element/2, + add_element/3, + add_element/4, + reset/1, + to_list/1, + foldl/3, + foldl/4, + foldr/3, + foldr/4]). + + +-compile(inline). + +-type(timestamp() :: integer()). +-type(value() :: any()). +-type(cur_state() :: any()). + +-type(sample_fun() :: fun((timestamp(), value(), cur_state()) -> cur_state())). +-type transform_fun() :: fun((timestamp(), cur_state()) -> cur_state()). + +%% Fixed size event buffer +%% A slot is indexed by taking the current timestamp (in ms) divided by the slot_period +%% +-record(slide, {timespan = 0 :: integer(), % How far back in time do we go, in slot period increments. + sample_fun :: sample_fun(), + transform_fun :: transform_fun(), + slot_period :: integer(), % Period, in ms, of each slot + cur_slot = 0 :: integer(), % Current slot as in + cur_state = undefined :: any(), % Total for the current slot + list1_start_slot = 0 ::integer(), % Slot of the first list1 element + list1 = [] :: list(), + list2 = [] :: list()}). + +-spec new(integer(), integer()) -> #slide{}. +new(HistogramTimeSpan, SlotPeriod) -> + new(HistogramTimeSpan, SlotPeriod, fun avg_sample/3, fun avg_transform/2). + +-spec new(integer(), integer(), sample_fun(), transform_fun()) -> #slide{}. +new(HistogramTimeSpan, SlotPeriod, SampleF, TransformF) -> + new(HistogramTimeSpan, SlotPeriod, SampleF, TransformF, []). + +-spec new(integer(), integer(), sample_fun(), transform_fun(), list()) -> #slide{}. +new(HistogramTimeSpan, SlotPeriod, SampleF, TransformF, _Options) + when is_function(SampleF, 3), is_function(TransformF, 2) -> + #slide{timespan = trunc(HistogramTimeSpan / SlotPeriod), + sample_fun = SampleF, + transform_fun = TransformF, + slot_period = SlotPeriod, + cur_slot = trunc(timestamp() / SlotPeriod), + cur_state = undefined, + list1_start_slot = 0, + list1 = [], + list2 = []}. + +-spec add_element(any(), #slide{}) -> #slide{}. + +add_element(Val, Slide) -> + add_element(timestamp(), Val, Slide, false). + +add_element(TS, Val, Slide) -> + add_element(TS, Val, Slide, false). + +add_element(TS, Val, #slide{cur_slot = CurrentSlot, + sample_fun = SampleF} = Slide, Wrap) -> + + TSSlot = get_slot(TS, Slide), + + %% We have two options here: + %% 1. We have moved into a new slot since last call to add_element(). + %% In this case, we invoke the transform MFA for the now completed slot + %% and add the returned element to the slider using the add_slot() function. + %% Once that is done, we start with fresh 'undefined' current slot state + %% that the next call to the sample MFA will receive. + %% + %% 2. We are in the same slot as during the last call to add_element(). + %% In this case, we will simply call the sample MFA to update the + %% current slot state. + %% + %% + {Flag, Slide1} = + if TSSlot =/= CurrentSlot -> + add_slot(TS, Slide); + true -> + {false, Slide} + end, + + %% + %% Invoke the sample MFA to get a new state to work with + %% + ret(Wrap, Flag, + Slide1#slide {cur_state = SampleF(TS, Val, Slide1#slide.cur_state)}). + +ret(false, _, Slide) -> + Slide; +ret(true, Flag, Slide) -> + {Flag, Slide}. + + + +-spec to_list(#slide{}) -> list(). +%% +to_list(#slide{timespan = TimeSpan}) when TimeSpan == 0 -> + []; + +%% Convert the whole histograms, in both buffers, to a list. +to_list(#slide{timespan = TimeSpan } = Slide) -> + Oldest = get_slot(Slide) - TimeSpan, + take_since(Oldest, Slide). + +-spec reset(#slide{}) -> #slide{}. + +reset(#slide{} = Slide) -> + Slide#slide { cur_state = undefined, + cur_slot = 0, + list1 = [], + list2 = [], + list1_start_slot = 0}. + + +foldl(TS, Fun, Acc, #slide{timespan = TimeSpan} = Slide) -> + Oldest = get_slot(TS, Slide) - TimeSpan, + lists:foldl(Fun, Acc, take_since(Oldest, Slide)). + +foldl(Fun, Acc, Slide) -> + foldl(timestamp(), Fun, Acc, Slide). + + +foldr(TS, Fun, Acc, #slide{timespan = TimeSpan} = Slide) -> + Oldest = get_slot(TS, Slide) - TimeSpan, + lists:foldr(Fun, Acc, take_since(Oldest, Slide)). + +foldr(Fun, Acc, Slide) -> + foldr(timestamp(), Fun, Acc, Slide). + +%% Collect all elements in the histogram with a timestamp that falls +%% within the timespan ranging from Oldest up to the current timme. +%% +take_since(Oldest, #slide{%% cur_slot = CurrentSlot, + cur_state = CurrentState, + slot_period = SlotPeriod } =Slide) -> + + %% Check if we need to add a slot for the current time period + %% before we start to grab data. + %% We add the current slot if it has a sample (cur_state =/= undefined) + %% and the slot period has expired. + TS = timestamp(), + %% TSSlot = get_slot(TS, Slide), + #slide { list1 = List1, + list2 = List2} = + %% if TSSlot =/= CurrentSlot, CurrentState =/= undefined -> + if CurrentState =/= undefined -> + {_, Sl} = add_slot(TS, Slide), + Sl; + true -> + Slide + end, + + take_since(List2, Oldest, SlotPeriod, take_since(List1, Oldest, SlotPeriod, [])). + +take_since([{TS,Element} |T], Oldest, SlotPeriod, Acc) when TS >= Oldest -> + take_since(T, Oldest, SlotPeriod, [{TS * SlotPeriod, Element} |Acc]); + +take_since(_, _, _, Acc) -> + %% Don't reverse; already the wanted order. + Acc. + + +%% +get_slot(Slide) -> + get_slot(timestamp(), Slide). + +get_slot(TS, #slide{slot_period = Period}) -> + trunc(TS / Period). + +%% +%% Calculate the average data sampled during the current slot period +%% and push that average to the new slot. +%% +add_slot(TS, #slide{timespan = TimeSpan, + slot_period = SlotPeriod, + cur_slot = CurrentSlot, + cur_state = CurrentState, + transform_fun = TransformF, + list1 = List1, + list1_start_slot = StartSlot} = Slide) -> + + %% Transform current slot state to an element to be deposited + %% in the histogram list + TSSlot = trunc(TS / SlotPeriod), + case TransformF(TS, CurrentState) of + undefined -> %% Transformation function could not produce an element + %% Reset the time slot to the current slot. Reset state./ + {false, Slide#slide{ cur_slot = TSSlot, + cur_state = undefined}}; + + %% The transform function produced an element to store + Element -> + %% + %% Check if it is time to do a buffer swap. + %% If the oldset element of list1, + %% + if StartSlot < TSSlot - TimeSpan -> + %% Shift list1 into list2. + %% Add the new slot as the initial element of list1 + {true, Slide#slide{ list1 = [{ CurrentSlot, Element }], + list2 = List1, + list1_start_slot = CurrentSlot, + cur_slot = TSSlot, + cur_state = undefined}}; + true -> + %% No shift necessary. Tack on the new slot to list1. + {false, + Slide#slide{list1 = [{CurrentSlot, Element} | List1], + cur_slot = TSSlot, + cur_state = undefined}} + end + end. + + + +%% Simple sample processor that maintains the average value +%% of all samples received during the current slot. +avg_sample(_TS, Value, undefined) -> + { 1, Value }; + +avg_sample(_TS, Value, {Count, Total}) -> + { Count + 1, Total + Value }. + +%% If avg_sample() has not been called for the current time slot, +%% then the provided state will still be 'undefined' +avg_transform(_TS, undefined) -> + undefined; + +%% Calculate the average of all received samples during the current slot +%% and return it as the element to be stored in the histogram. +avg_transform(_TS, { Count, Total }) -> + Total / Count. %% Return the average value. + + +timestamp() -> + %% Invented epoc is {1258,0,0}, or 2009-11-12, 4:26:40 + %% Millisecond resolution + {MS,S,US} = os:timestamp(), + (MS-1258)*1000000000 + S*1000 + US div 1000. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_spiral.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_spiral.erl new file mode 100644 index 0000000..ec06737 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_spiral.erl @@ -0,0 +1,134 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-module(exometer_spiral). +-behaviour(exometer_probe). + +%% exometer_entry callbacks +-export([behaviour/0, + probe_init/3, + probe_terminate/1, + probe_setopts/3, + probe_update/2, + probe_get_value/2, + probe_get_datapoints/1, + probe_reset/1, + probe_code_change/3, + probe_sample/1, + probe_handle_msg/2]). + +%% exometer_proc callback +-export([count_sample/3, + count_transform/2]). + +-compile(inline). + +%% -compile({parse_transform, exometer_igor}). +%% -compile({igor, [{files, ["src/exometer_util.erl" +%% , "src/exometer_proc.erl" +%% , "src/exometer_slot_slide.erl" +%% ]}]}). +%% -compile({igor, [{verbose, true}]}). + +-include("exometer.hrl"). +-import(netlink_stat, [get_value/1]). +-record(st, {name, + slide = undefined, %% + slot_period = 1000, %% msec + time_span = 60000, %% msec + total = 0, + opts = []}). + +-define(DATAPOINTS, [ count, one ]). + +-spec behaviour() -> exometer:behaviour(). +behaviour()-> + probe. + +probe_init(Name, _Type, Options) -> + St = process_opts(#st{name = Name}, [{time_span, 60000}, + {slot_period, 1000}] ++ Options), + Slide = exometer_slot_slide:new(St#st.time_span, + St#st.slot_period, + fun count_sample/3, + fun count_transform/2, + Options), + {ok, St#st{slide = Slide}}. + +probe_terminate(_St) -> + ok. + +probe_get_value(DataPoints, St) -> + {ok, [get_single_value(St, DataPoint) || DataPoint <- DataPoints]}. + +probe_get_datapoints(_St) -> + {ok, ?DATAPOINTS}. + +probe_setopts(_Entry, _Options, _St) -> + ok. + +probe_update(Increment, #st{slide = Slide, total = Total} = St) -> + {ok, St#st{ + slide = exometer_slot_slide:add_element(Increment, Slide), + total = Total + Increment}}. + +probe_reset(#st{slide = Slide} = St) -> + {ok, St#st{total = 0, slide = exometer_slot_slide:reset(Slide)}}. + + +probe_sample(_St) -> + {error, unsupported}. + +probe_handle_msg(_, S) -> + {ok, S}. + +probe_code_change(_, S, _) -> + {ok, S}. + +process_opts(St, Options) -> + exometer_proc:process_options(Options), + lists:foldl( + fun + %% Sample interval. + ({time_span, Val}, St1) -> St1#st{time_span = Val}; + ({slot_period, Val}, St1) -> St1#st{slot_period = Val}; + + %% Unknown option, pass on to State options list, replacing + %% any earlier versions of the same option. + ({Opt, Val}, St1) -> + St1#st{opts = [{Opt, Val} + | lists:keydelete(Opt, 1, St1#st.opts)]} + end, St, Options). + +%% Simple sample processor that maintains a counter. +%% of all +count_sample(_TS, Increment, undefined) -> + Increment; +count_sample(_TS, Increment, Total) -> + Total + Increment. + +%% If count_sample() has not been called for the current time slot, +%% then the provided state will still be 'undefined' +count_transform(_TS, undefined) -> + 0; +%% Return the calculated total for the slot and return it as the +%% element to be stored in the histogram. +count_transform(_TS, Total) -> + %% Return the sum of all counter increments received during this slot. + Total. + +get_single_value(St, count) -> + {count, St#st.total}; + +get_single_value(St, one) -> + {one, exometer_slot_slide:foldl(fun({_TS, Val}, Acc) -> Acc + Val end, + 0, St#st.slide) }; +get_single_value(_St, Unsupported) -> + {Unsupported, {error, unsupported}}. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_uniform.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_uniform.erl new file mode 100644 index 0000000..7f1be16 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_uniform.erl @@ -0,0 +1,133 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- + +-module(exometer_uniform). +-behaviour(exometer_probe). + + +%% exometer_probe callbacks +-export([behaviour/0, + probe_init/3, + probe_terminate/1, + probe_get_value/2, + probe_get_datapoints/1, + probe_update/2, + probe_reset/1, + probe_sample/1, + probe_setopts/3, + probe_handle_msg/2, + probe_code_change/3]). + + +-include("exometer.hrl"). + +-record(st, {name, + size = 1028, + cur_sz = 0, + percentiles = [ 99.0 ], + ets_ref = undefined, + opts = []}). + +-record(elem, { slot = 0, + val = undefined } ). + +-define(DATAPOINTS, + [ min, max, median, mean, 50, 75, 90, 95, 99, 999 ]). + + +%% +%% exometer_probe callbacks +%% +behaviour() -> + probe. + + +probe_init(Name, _Type, Options) -> + St = process_opts(#st { name = Name }, [ {percentiles, [ 50, 75, 90, 95, 99, 999 ]} ] ++ Options), + EtsRef = ets:new(uniform, [ set, { keypos, 2 } ]), + + %% Setup random seed, if not already done. + case get(random_seed) of + undefined -> exometer_util:seed(os:timestamp()); + _ -> true + end, + {ok, St#st{ ets_ref = EtsRef }}. + + +probe_terminate(ModSt) -> + ets:delete(ModSt#st.ets_ref), + ok. + +probe_get_value(DataPoints, St) -> + {Length, Total, Lst} = ets:foldl( + fun(#elem { val = Val }, {Length, Total, List}) -> + { Length + 1, Total + Val, [ Val | List ]} end, + {0, 0.0, []}, St#st.ets_ref), + + Sorted = lists:sort(Lst), + Mean = case Length of + 0 -> 0.0; + N -> Total/N + end, + Results = exometer_util:get_statistics2(Length, Sorted, Total, Mean), + {ok, [get_dp(Results, DataPoint) || DataPoint <- DataPoints]}. + +get_dp(L, D) -> + case lists:keyfind(D, 1, L) of + false -> + {D, 0}; + DP -> + DP + end. + +probe_get_datapoints(_St) -> + { ok, ?DATAPOINTS }. + +probe_setopts(_Entry, _Opts, _St) -> + ok. + +probe_update(Value, St) when St#st.cur_sz < St#st.size -> + NewSz = St#st.cur_sz + 1, + ets:insert(St#st.ets_ref, #elem { slot = NewSz, val = Value }), + { ok, St#st { cur_sz = NewSz} }; + +probe_update(Value, St) -> + Slot = exometer_util:uniform(St#st.size), + ets:insert(St#st.ets_ref, #elem { slot = Slot, val = Value }), + ok. + +probe_reset(St) -> + ets:delete(St#st.ets_ref), + EtsRef = ets:new(uniform, [ set, { keypos, 2 } ]), + {ok, St#st { ets_ref = EtsRef, cur_sz = 0 }}. + +probe_sample(_St) -> + error(unsupported). + +probe_code_change(_From, ModSt, _Extra) -> + {ok, ModSt}. + +process_opts(St, Options) -> + lists:foldl( + fun + %% Sample interval. + ({size, Val}, St1) -> St1#st { size = Val }; + ({percentiles, Val}, St1) -> St1#st { percentiles = Val }; + + %% Unknown option, pass on to State options list, replacing + %% any earlier versions of the same option. + ({Opt, Val}, St1) -> + St1#st{ opts = [ {Opt, Val} + | lists:keydelete(Opt, 1, St1#st.opts) ] } + end, St, Options). + + +probe_handle_msg(_, S) -> + {ok, S}. diff --git a/ptrans_deporder/_checkouts/exometer_core/src/exometer_util.erl b/ptrans_deporder/_checkouts/exometer_core/src/exometer_util.erl new file mode 100644 index 0000000..b4dab4d --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_core/src/exometer_util.erl @@ -0,0 +1,442 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. +%% +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at http://mozilla.org/MPL/2.0/. +%% +%% ------------------------------------------------------------------- +%% @doc Exometer utility functions. +%% @end +-module(exometer_util). + +-export( + [ + timestamp/0, + timestamp_to_datetime/1, + get_opt/2, + get_opt/3, + get_env/2, + tables/0, + table/0, + get_statistics/3, + get_statistics2/4, + pick_items/2, + perc/2, + histogram/1, + histogram/2, + drop_duplicates/1, + report_type/3, + get_datapoints/1, + set_call_count/2, set_call_count/3, + get_status/1, + set_status/2, + set_event_flag/2, + clear_event_flag/2, + test_event_flag/2, + ensure_all_started/1, + seed/0, + seed0/0, + seed/1, + uniform/0, + uniform/1 + ]). + +-export_type([timestamp/0]). + +-include("exometer.hrl"). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-type timestamp() :: non_neg_integer(). + +-spec timestamp() -> timestamp(). +%% @doc Generate a millisecond-resolution timestamp. +%% +%% This timestamp format is used e.g. by the `exometer_slide' and +%% `exometer_histogram' implementations. +%% @end +timestamp() -> + %% Invented epoc is {1258,0,0}, or 2009-11-12, 4:26:40 + %% Millisecond resolution + {MS,S,US} = os:timestamp(), + (MS-1258)*1000000000 + S*1000 + US div 1000. + +-spec timestamp_to_datetime(timestamp()) -> + {calendar:datetime(), non_neg_integer()}. +%% @doc Convert timestamp to a regular datetime. +%% +%% The timestamp is expected +timestamp_to_datetime(TS) when TS >= 0 -> + %% Our internal timestamps are relative to Now = {1258,0,0} + %% It doesn't really matter much how we construct a now()-like tuple, + %% as long as the weighted sum of the three numbers is correct. + S = TS div 1000, + MS = TS rem 1000, + %% return {Datetime, Milliseconds} + {calendar:now_to_datetime({1258,S,0}), MS}. + +get_env(Key, Default) -> + case get_env1(exometer, Key) of + {ok, Value} -> + Value; + _ -> + case get_env1(exometer_core, Key) of + {ok, CoreValue} -> + CoreValue; + _ -> + Default + end + end. + +get_env1(App, Key) -> + case application:get_env(App, Key) of + {ok, undefined} -> undefined; + Other -> Other + end. + +get_opt(K, Opts) -> + case lists:keyfind(K, 1, Opts) of + {_, V} -> V; + false -> + error({required, K}) + end. + +get_opt(K, Opts, Default) -> + case lists:keyfind(K, 1, Opts) of + {_, V} -> V; + false -> + if is_function(Default,0) -> Default(); + true -> Default + end + end. + +tables() -> + [table(S) || S <- lists:seq(1,erlang:system_info(schedulers))]. + +table() -> + table(erlang:system_info(scheduler_id)). + +table(1) -> exometer_1; +table(2) -> exometer_2; +table(3) -> exometer_3; +table(4) -> exometer_4; +table(5) -> exometer_5; +table(6) -> exometer_6; +table(7) -> exometer_7; +table(8) -> exometer_8; +table(9) -> exometer_9; +table(10) -> exometer_10; +table(11) -> exometer_11; +table(12) -> exometer_12; +table(13) -> exometer_13; +table(14) -> exometer_14; +table(15) -> exometer_15; +table(16) -> exometer_16; +table(17) -> exometer_17; +table(18) -> exometer_18; +table(19) -> exometer_19; +table(20) -> exometer_20; +table(21) -> exometer_21; +table(22) -> exometer_22; +table(23) -> exometer_23; +table(24) -> exometer_24; +table(25) -> exometer_25; +table(26) -> exometer_26; +table(27) -> exometer_27; +table(28) -> exometer_28; +table(29) -> exometer_29; +table(30) -> exometer_30; +table(31) -> exometer_31; +table(32) -> exometer_32; +table(33) -> exometer_33; +table(34) -> exometer_34; +table(35) -> exometer_35; +table(36) -> exometer_36; +table(37) -> exometer_37; +table(38) -> exometer_38; +table(39) -> exometer_39; +table(40) -> exometer_40; +table(41) -> exometer_41; +table(42) -> exometer_42; +table(43) -> exometer_43; +table(44) -> exometer_44; +table(45) -> exometer_45; +table(46) -> exometer_46; +table(47) -> exometer_47; +table(48) -> exometer_48; +table(49) -> exometer_49; +table(50) -> exometer_50; +table(51) -> exometer_51; +table(52) -> exometer_52; +table(53) -> exometer_53; +table(54) -> exometer_54; +table(55) -> exometer_55; +table(56) -> exometer_56; +table(57) -> exometer_57; +table(58) -> exometer_58; +table(59) -> exometer_59; +table(60) -> exometer_60; +table(61) -> exometer_61; +table(62) -> exometer_62; +table(63) -> exometer_63; +table(64) -> exometer_64; +table(N) when is_integer(N), N > 20 -> + list_to_atom("exometer_" ++ integer_to_list(N)). + +%% @doc +%% `drop_duplicates/1' will drop all duplicate elements from a list of tuples identified by their first element. +%% Elements which are not tuples will be dropped as well. +%% If called with a non-list argument, the argument is returned as is. +%% @end +-spec drop_duplicates(List0 :: [tuple()]) -> [tuple()]. +drop_duplicates(List0) when is_list(List0) -> + List1 = lists:foldl( + fun + (Elem, Acc) when is_tuple(Elem) -> + case lists:keymember(element(1, Elem), 1, Acc) of + true -> + Acc; + false -> + [Elem | Acc] + end; + (_, Acc) -> + Acc + end, [], List0), + lists:reverse(List1); +drop_duplicates(Any) -> + Any. + +histogram(Values) -> + Sorted = lists:sort(Values), + Len = length(Sorted), + Total = lists:foldl(fun(I,Acc) -> Acc + I end, 0, Sorted), + get_statistics(Len, Total, Sorted). + +histogram(Values, default) -> + histogram(Values); +histogram(Values, DataPoints) -> + H = histogram(Values), + [DP || {K,_} = DP <- H, + lists:member(K, DataPoints)]. + +-spec get_statistics(Length::non_neg_integer(), + Total::non_neg_integer(), + Sorted::list()) -> [{atom(), number()}]. +%% @doc Calculate statistics from a sorted list of values. +%% +%% This function assumes that you have already sorted the list, and +%% now the number and sum of the elements in the list. +%% +%% The stats calculated are min, max, mean, median and the 50th, +%% 75th, 90th, 95th, 99th, and 99.9th percentiles (note that the +%% 99.9th percentile is labeled 999). +%% +%% This function is similar to `bear:get_statistics_subset/2'. +%% `mean' refers to the arithmetic mean. +%% +%% Fulpatchad med min/max av Magnus Feuer. +%% @end + +get_statistics(_, _, []) -> + []; +get_statistics(L, Total, Sorted) -> + get_statistics2(L, Sorted, Total, Total / L). + +%% +%% Special case when we get called with an empty histogram. +get_statistics2(_L, [], _Total, _Mean) -> + []; + +get_statistics2(L, Sorted, Total, Mean) -> + P50 = perc(0.5, L), + Items = [{min,1}, {50, P50}, {median, P50}, {75, perc(0.75,L)}, + {90, perc(0.9,L)}, {95, perc(0.95,L)}, {99, perc(0.99,L)}, + {999, perc(0.999,L)}, {max,L}], + [{n,L}, {mean, Mean}, {total, Total} | pick_items(Sorted, Items)]. + +-spec pick_items([number()], [{atom() | integer(), integer()}]) -> + [{atom(), number()}]. +%% @doc Pick values from specified positions in a sorted list of numbers. +%% +%% This function is used to extract datapoints (usually percentiles) from +%% a sorted list of values. `Items' is a list of `{Datapoint, Position}' +%% entries. +%% @end +pick_items(Vals, Items) -> + pick_items(Vals, 1, Items). + +pick_items([H|_] = L, P, [{Tag,P}|Ps]) -> + [{Tag,H} | pick_items(L, P, Ps)]; + +pick_items([_|T], P, Ps) -> + pick_items(T, P+1, Ps); + +pick_items([], _, Ps) -> + [{Tag, 0.0} || {Tag,_} <- Ps]. + +perc(P, Len) when P > 1.0 -> + round((P / 10) * Len); + +perc(P, Len) -> + round(P * Len). + + +report_type(Key, Extra, TypeMap) when is_list(Extra) -> + case lists:keyfind(report_type, 1, Extra) of + {_, Type} -> {ok, Type}; + false -> check_type(Key, TypeMap) + end; +report_type(Key, _, TypeMap) -> + check_type(Key, TypeMap). + +check_type(K, [{K, Type}|_]) -> + {ok, Type}; +check_type(K, [{KPat, Type}|T]) -> + case key_match(KPat, K) of + true -> {ok, Type}; + false -> check_type(K, T) + end; +check_type(_, []) -> + error. + +key_match([H|T], [H|T1]) -> + key_match(T, T1); +key_match(['_'|T], [_|T1]) -> + key_match(T, T1); +key_match([], []) -> true; +key_match('_', _) -> true; +key_match(_, _) -> false. + + +get_datapoints(#exometer_entry{module = exometer, + type = T}) when T==counter; + T==fast_counter; + T==gauge -> + [value, ms_since_reset]; +get_datapoints(#exometer_entry{behaviour = entry, + name = Name, module = M, + type = Type, ref = Ref}) -> + M:get_datapoints(Name, Type, Ref); +get_datapoints(#exometer_entry{behaviour = probe, + name = Name, type = Type, ref = Ref}) -> + exometer_probe:get_datapoints(Name, Type, Ref). + +set_call_count({M, F}, Bool) -> + set_call_count(M, F, Bool). + +set_call_count(M, F, Bool) when is_atom(M), is_atom(F), is_boolean(Bool) -> + erlang:trace_pattern({M, F, 0}, Bool, [call_count]). + +get_status(enabled) -> enabled; +get_status(disabled) -> disabled; +get_status(St) when is_integer(St) -> + if St band 2#1 == 1 -> + enabled; + true -> + disabled + end. + +set_status(enabled , enabled ) -> 1; +set_status(enabled , disabled) -> 1; +set_status(enabled , St ) -> St bor 2#1; +set_status(disabled, enabled ) -> 0; +set_status(disabled, disabled) -> 0; +set_status(disabled, St ) -> St band 2#11111110. + +set_event_flag(update, St) when is_integer(St) -> + St bor 2#10; +set_event_flag(update, enabled ) -> 2#11; +set_event_flag(update, disabled) -> 2#10. + + +clear_event_flag(update, St) when is_integer(St) -> + St band 2#11111101; +clear_event_flag(update, enabled ) -> 1; +clear_event_flag(update, disabled) -> 0. + +test_event_flag(update, St) when St band 2#10 =:= 2#10 -> true; +test_event_flag(update, _) -> false. + +%% This implementation is originally from Basho's Webmachine. On +%% older versions of Erlang, we don't have +%% application:ensure_all_started, so we use this wrapper function to +%% either use the native implementation or our own version, depending +%% on what's available. +-spec ensure_all_started(atom()) -> {ok, [atom()]} | {error, term()}. +ensure_all_started(App) -> + %% Referencing application:ensure_all_started/1 will anger Xref + %% in earlier R16B versions of OTP + ensure_all_started(App, []). + +-ifdef(rand_module). +seed() -> + {0, 0, 0}. + +seed0() -> + {0, 0, 0}. + +seed({A, B, C}) -> + %% rand in the erlang version 18 or above, use a different seed methond, does not use the seed like that any more + {A, B, C}. + +uniform() -> + rand:uniform(). + +uniform(N) -> + rand:uniform(N). + +-else. +seed() -> + random:seed(). + +seed0() -> + random:seed0(). + +seed({A, B, C}) -> + random:seed({A, B, C}). + +uniform() -> + random:uniform(). + +uniform(N) -> + random:uniform(N). + +-endif. + +%% This implementation is originally from Basho's +%% Webmachine. Reimplementation of ensure_all_started. NOTE this does +%% not behave the same as the native version in all cases, but as a +%% quick hack it works well enough for our purposes. Eventually I +%% assume we'll drop support for older versions of Erlang and this can +%% be eliminated. +ensure_all_started(App, Apps0) -> + case application:start(App) of + ok -> + {ok, lists:reverse([App | Apps0])}; + {error,{already_started,App}} -> + {ok, lists:reverse(Apps0)}; + {error,{not_started,BaseApp}} -> + {ok, Apps} = ensure_all_started(BaseApp, Apps0), + ensure_all_started(App, [BaseApp|Apps]) + end. + + +%% EUnit tests +-ifdef(TEST). + +key_match_test() -> + {ok,yes} = report_type([a,b,c], [], [{'_',yes}]), + {ok,yes} = report_type([a,b,c], [], [{[a,b], no}, + {[a,b,c], yes}, + {[a,b,c], no}]), % match on first + {ok,yes} = report_type([a,b,c], [], [{[a,b,'_'], yes}]), + {ok,yes} = report_type([a,b,c], [], [{[a,'_',c], yes}]), + {ok,yes} = report_type([a,b,c], [], [{[a,b|'_'], yes}]), + {ok,yes} = report_type([a,b,c], [{report_type,yes}], [{[a,b,c], no}]), + ok. + +-endif. diff --git a/ptrans_deporder/_checkouts/exometer_fetch/README.md b/ptrans_deporder/_checkouts/exometer_fetch/README.md new file mode 100644 index 0000000..ebc0ecc --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/README.md @@ -0,0 +1,79 @@ +# exometer_fetch [![Build Status](https://travis-ci.org/travelping/exometer_fetch.svg)](https://travis-ci.org/travelping/exometer_fetch) + +This reporter acts as subscription handler such that other services (e.g. http server) can fetch metrics from it. + +### Usage + +Add exometer_fetch to your list of dependencies in rebar.config: + +```erlang +{deps, [ + {exometer_fetch, ".*", {git, "https://github.com/travelping/exometer_fetch.git", "master"}} +]}. +``` + +Ensure exometer_fetch is started before your application: + +```erlang +{applications, [exometer_fetch]}. +``` + +Configure it: + + +```erlang +{exometer, + {reporters, [ + {exometer_report_fetch, [ + {autosubscribe, true}, + {subscriptions_module, exometer_fetch_subscribe_mod} + ]} + ]} +}. +``` + +It is possible to create a subscription automatically for each newly created metric entry. By default this is disabled. You can enable it in the reporter options. +You must also provide a callback module which handles the entries. Apart from that there are no further options. + +The callback module may look like: + +```erlang +-module(exometer_fetch_subscribe_mod). +-export([subscribe/2]). + +subscribe([test, metric] = Metric, histogram) -> + {Metric, [max, min], [{key, <<"some_key">>}]}; +subscribe(_, _) -> []. +``` + +`subscribe/2` calls for each new entry and it should return a (possibly empty) list or just one subscription. Here a single subscription has the following layout: + +```erlang +{exometer_report:metric(), exometer_report:datapoints(), exometer_report:extra()} +``` + +### Subscription examples: + +```erlang +exometer_report:subscribe(exometer_report_fetch, [erlang, memory], total, manual, [{key, <<"some_key">>}]). +``` + +Check if everything is working: + +```erlang +exometer_fetch:fetch(<<"some_key">>). +``` + +Further it is possible to return only the value of a specific datapoint: + +```erlang +exometer_fetch:fetch(<<"some_key">>, total). +``` + +Keys always need to be given as binary. If a key is not found then all metrics which have this key as prefix will be returned (or an error if nothing is found). + +#### About subscription time intervals: + +The report interval in subscriptions should be set to `manual` such that the metric is actually never reported using time interval triggers. +The metric value will be retrieved from exometer directly when one sends a request to the corresponding key. + diff --git a/ptrans_deporder/_checkouts/exometer_fetch/ebin/exometer_fetch.app b/ptrans_deporder/_checkouts/exometer_fetch/ebin/exometer_fetch.app new file mode 100644 index 0000000..cdb0621 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/ebin/exometer_fetch.app @@ -0,0 +1,11 @@ +{application,exometer_fetch, + [{description,"Fetch helper for exometer"}, + {vsn,"0.1.0"}, + {registered,[]}, + {applications,[kernel,stdlib,exometer_core]}, + {env,[]}, + {maintainers,["Yury Gargay"]}, + {licenses,["MPL 2.0"]}, + {links,[{"Github", + "https://github.com/travelping/exometer_fetch"}]}, + {modules,[exometer_fetch,exometer_report_fetch]}]}. diff --git a/ptrans_deporder/_checkouts/exometer_fetch/hex_metadata.config b/ptrans_deporder/_checkouts/exometer_fetch/hex_metadata.config new file mode 100644 index 0000000..194ab24 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/hex_metadata.config @@ -0,0 +1,18 @@ +{<<"name">>,<<"exometer_fetch">>}. +{<<"version">>,<<"0.1.0">>}. +{<<"requirements">>, + #{<<"exometer_core">> => + #{<<"app">> => <<"exometer_core">>,<<"optional">> => false, + <<"requirement">> => <<"1.5.2">>}}}. +{<<"app">>,<<"exometer_fetch">>}. +{<<"maintainers">>,[<<"Yury Gargay">>]}. +{<<"precompiled">>,false}. +{<<"description">>,<<"Fetch helper for exometer">>}. +{<<"files">>, + [<<"src/exometer_fetch.app.src">>,<<"README.md">>,<<"rebar.config">>, + <<"rebar.lock">>,<<"src/exometer_fetch.erl">>, + <<"src/exometer_report_fetch.erl">>]}. +{<<"licenses">>,[<<"MPL 2.0">>]}. +{<<"links">>, + [{<<"Github">>,<<"https://github.com/travelping/exometer_fetch">>}]}. +{<<"build_tools">>,[<<"rebar3">>]}. diff --git a/ptrans_deporder/_checkouts/exometer_fetch/rebar.config b/ptrans_deporder/_checkouts/exometer_fetch/rebar.config new file mode 100644 index 0000000..ea91590 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/rebar.config @@ -0,0 +1,3 @@ +{deps, [ + {exometer_core, "1.5.2"} +]}. diff --git a/ptrans_deporder/_checkouts/exometer_fetch/rebar.lock b/ptrans_deporder/_checkouts/exometer_fetch/rebar.lock new file mode 100644 index 0000000..84bb409 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/rebar.lock @@ -0,0 +1,20 @@ +{"1.1.0", +[{<<"bear">>,{pkg,<<"bear">>,<<"0.8.5">>},2}, + {<<"exometer_core">>,{pkg,<<"exometer_core">>,<<"1.5.2">>},0}, + {<<"folsom">>,{pkg,<<"folsom">>,<<"0.8.5">>},1}, + {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},2}, + {<<"hut">>,{pkg,<<"hut">>,<<"1.2.0">>},1}, + {<<"lager">>,{pkg,<<"lager">>,<<"3.5.1">>},1}, + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.0.0">>},1}, + {<<"setup">>,{pkg,<<"setup">>,<<"1.8.2">>},1}]}. +[ +{pkg_hash,[ + {<<"bear">>, <<"E95FCA1627CD9E15BAF93CE0A52AFF16917BAF325F0EE65B88CD715376CD2344">>}, + {<<"exometer_core">>, <<"62A99A361BA8A14D53857D4C716A191E810299D2F43C5C981EB7B086C0BFCCE1">>}, + {<<"folsom">>, <<"94A027B56FE84FEED264F9B33CB4C6AC9A801FAD84B87DBDA0836CE83C3B8D69">>}, + {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, + {<<"hut">>, <<"0089DF0FAA2827C605BBADA88153F24FFF5EA7A4BE32ECF0250A7FDC2719CAFB">>}, + {<<"lager">>, <<"63897A61AF646C59BB928FEE9756CE8BDD02D5A1A2F3551D4A5E38386C2CC071">>}, + {<<"parse_trans">>, <<"9E96B1C9C3A0DF54E7B76F8F685D38BFA1EB21B31E042B1D1A5A70258E4DB1E3">>}, + {<<"setup">>, <<"14E66C480EA51D938527247C1D92C3EA5298826CF7FA7769C936BAD29E2C040E">>}]} +]. diff --git a/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_fetch.app.src b/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_fetch.app.src new file mode 100644 index 0000000..e962b10 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_fetch.app.src @@ -0,0 +1,10 @@ +{application,exometer_fetch, + [{description,"Fetch helper for exometer"}, + {vsn,"0.1.0"}, + {registered,[]}, + {applications,[kernel,stdlib,exometer_core]}, + {env,[]}, + {maintainers,["Yury Gargay"]}, + {licenses,["MPL 2.0"]}, + {links,[{"Github", + "https://github.com/travelping/exometer_fetch"}]}]}. diff --git a/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_fetch.erl b/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_fetch.erl new file mode 100644 index 0000000..d6d06a3 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_fetch.erl @@ -0,0 +1,13 @@ +-module(exometer_fetch). + +-export([fetch/1, fetch/2]). + + +fetch(Key) -> + call(Key). + +fetch(Key, Datapoint) -> + call({Key, Datapoint}). + +call(Request) -> + exometer_report:call_reporter(exometer_report_fetch, {request, Request}). diff --git a/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_report_fetch.erl b/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_report_fetch.erl new file mode 100644 index 0000000..b60a404 --- /dev/null +++ b/ptrans_deporder/_checkouts/exometer_fetch/src/exometer_report_fetch.erl @@ -0,0 +1,185 @@ +-module(exometer_report_fetch). + +-behaviour(exometer_report). + +%% gen_server callbacks +-export([exometer_init/1, + exometer_info/2, + exometer_cast/2, + exometer_call/3, + exometer_report/5, + exometer_subscribe/5, + exometer_unsubscribe/4, + exometer_newentry/2, + exometer_setopts/4, + exometer_terminate/2]). + + +-include_lib("exometer_core/include/exometer.hrl"). + +-define(DEFAULT_AUTOSUBSCRIBE, false). +-define(DEFAULT_SUBSCRIPTIONS_MOD, undefined). +-define(DEFAULT_KEY_PREFIX, <<"">>). + +-record(state, {autosubscribe :: boolean(), + subscriptions_module :: module(), + subscriptions :: map(), + key_prefix :: binary()}). + + +%% =================================================================== +%% Public API +%% =================================================================== +exometer_init(Opts) -> + Autosubscribe = proplists:get_value(autosubscribe, Opts, ?DEFAULT_AUTOSUBSCRIBE), + SubscriptionsMod = proplists:get_value(subscriptions_module, Opts, ?DEFAULT_SUBSCRIPTIONS_MOD), + KeyPrefix = proplists:get_value(key_prefix, Opts, ?DEFAULT_KEY_PREFIX), + State = #state{autosubscribe = Autosubscribe, + subscriptions_module = SubscriptionsMod, + subscriptions = maps:new(), + key_prefix = KeyPrefix}, + {ok, State}. + +exometer_subscribe(Metric, DataPoints, _Interval, + Opts, #state{subscriptions=Subscriptions, + key_prefix = Prefix} = State) + when is_list(Opts) -> + case proplists:get_value(key, Opts, undefined) of + undefined -> + {{error, key_missing}, State}; + Key when is_binary(Key) /= true -> + {{error, key_not_binary}, State}; + Key -> + PrefixKey = <>, + case maps:is_key(PrefixKey, Subscriptions) of + true -> + {ok, State}; + false -> + DataPoints1 = case is_list(DataPoints) of + true -> DataPoints; + false -> [DataPoints] + end, + NewSubscriptions = maps:put(PrefixKey, {Metric, DataPoints1}, Subscriptions), + {ok, State#state{subscriptions=NewSubscriptions}} + end + end; +exometer_subscribe(_Metric, _DataPoint, _Interval, _Opts, State) -> + {{error, invalid_options}, State}. + +exometer_unsubscribe(Metric, _DataPoint, _Extra, #state{subscriptions=Subscriptions} = State) -> + Pred = fun(_Key, Value) -> + case Value of + {Metric, _} -> true; + _ -> false + end + end, + KeyToDelete = hd(maps:keys(maps:filter(Pred, Subscriptions))), + NewSubscriptions = maps:remove(KeyToDelete, Subscriptions), + {ok, State#state{subscriptions=NewSubscriptions}}. + +exometer_call({request, {Key, DataPoint}}, _From, #state{subscriptions=Subscriptions} = State) -> + {reply, get_metrics(Key, format_datapoint(DataPoint), Subscriptions), State}; +exometer_call({request, Key}, From, State) -> + exometer_call({request, {Key, undefined}}, From, State); +exometer_call(_Req, _From, State) -> + {ok, State}. + +exometer_newentry(#exometer_entry{name = Name, type = Type}, + #state{autosubscribe = true, + subscriptions_module = Module} = State) + when is_atom(Module); Module /= undefined -> + subscribe(Module:subscribe(Name, Type)), + {ok, State}; +exometer_newentry(_Entry, State) -> + {ok, State}. + +exometer_report(_Metric, _DataPoint, _Extra, _Value, State) -> {ok, State}. +exometer_cast(_Unknown, State) -> {ok, State}. +exometer_info(_Info, State) -> {ok, State}. +exometer_setopts(_Metric, _Options, _Status, State) -> {ok, State}. +exometer_terminate(_Reason, _) -> ignore. + + +%% =================================================================== +%% Internal functions +%% =================================================================== +subscribe(Subscriptions) when is_list(Subscriptions) -> + [subscribe(Subscription) || Subscription <- Subscriptions]; +subscribe({Name, DataPoints, Extra}) -> + exometer_report:subscribe(?MODULE, Name, DataPoints, manual, Extra, false); +subscribe(_Name) -> []. + +binarize_list(List) when is_list(List) -> list_to_binary(List); +binarize_list(Term) -> Term. + +format_datapoint(DP) when is_atom(DP) -> DP; +format_datapoint(DP) when is_list(DP) -> format_datapoint(list_to_binary(DP)); +format_datapoint(<<"undefined">>) -> undefined; +format_datapoint(<<"">>) -> undefined; +format_datapoint(Binary) -> + case re:run(Binary, "^[0-9]*$") of + {match, _} -> binary_to_integer(Binary); + _NoMatch -> binary_to_atom(Binary, latin1) + end. + +get_metrics(Key, DataPoint, Subscriptions) -> + case maps:get(Key, Subscriptions, undefined) of + undefined when DataPoint =:= undefined -> + find_metrics(Key, Subscriptions); + undefined -> + {error, not_found}; + MetricInfo when DataPoint =:= undefined -> + proceed_single_metric(Key, MetricInfo, false); + {Metric, DataPoints} -> + case lists:member(DataPoint, DataPoints) of + true -> + NewMetricInfo = {Metric, [DataPoint]}, + case proceed_single_metric(Key, NewMetricInfo, false) of + {ok, [{_, FinalPayload}]} -> {ok, FinalPayload}; + _Error -> {error, not_found} + end; + false -> + {error, datapoint_not_found} + end + end. + +proceed_single_metric(Key, {Metric, DataPoints}, KeyFlag) -> + case exometer:get_value(Metric, DataPoints) of + {ok, DataPointValues} -> + DataPoints1 = [{DataPoint, binarize_list(Value)} || + {DataPoint, Value} <- DataPointValues], + Payload = maybe_add_more(Key, DataPoints1, KeyFlag), + {ok, Payload}; + _Error -> + {error, not_found} + end. + +maybe_add_more(_Key, DataPoints, false) -> DataPoints; +maybe_add_more(Key, DataPoints, true) -> + [{key, Key}] ++ [{datapoints, DataPoints}]. + +find_metrics(Key, Subscriptions) -> + PrefixKeys = get_prefix_keys(Key, maps:keys(Subscriptions)), + Pred = fun(K,_V) -> lists:member(K, PrefixKeys) end, + FilteredSubsciptions = maps:filter(Pred, Subscriptions), + case accumulate_metrics(maps:to_list(FilteredSubsciptions)) of + [] -> {error, not_found}; + Metrics -> {ok, Metrics} + end. + +get_prefix_keys(Key, Keys) -> + ByteSize = byte_size(Key), + [ SingleKey || SingleKey <- Keys, ByteSize==binary:longest_common_prefix([Key, SingleKey]) ]. + +accumulate_metrics(Subscriptions) -> + accumulate_metrics(Subscriptions, []). + +accumulate_metrics([], Metrics) -> Metrics; +accumulate_metrics([{Key, MetricInfo} | Subscriptions], Metrics) -> + case proceed_single_metric(Key, MetricInfo, true) of + {ok, Metric} -> + accumulate_metrics(Subscriptions, Metrics ++ [Metric]); + {error, _Reason} -> + accumulate_metrics(Subscriptions, Metrics) + end. + diff --git a/ptrans_deporder/_checkouts/parse_trans/LICENSE b/ptrans_deporder/_checkouts/parse_trans/LICENSE new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/LICENSE @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/ptrans_deporder/_checkouts/parse_trans/README.md b/ptrans_deporder/_checkouts/parse_trans/README.md new file mode 100644 index 0000000..286daea --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/README.md @@ -0,0 +1,41 @@ + + +# The parse_trans application # + +__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)). + +Parse_transform utilities + +[![Build Status](https://travis-ci.org/uwiger/parse_trans.svg)](https://travis-ci.com/github/uwiger/parse_trans) +[![Hex pm](http://img.shields.io/hexpm/v/parse_trans.svg?style=flat)](https://hex.pm/packages/parse_trans) + + +## Introduction ## + +Parse_trans was written in order to capture some useful patterns in parse transformation +and code generation for Erlang. + +Most notably, perhaps, the module [`exprecs`](http://github.com/uwiger/parse_trans/blob/master/doc/exprecs.md) generates standardized accessor +functions for records, and [`ct_expand`](http://github.com/uwiger/parse_trans/blob/master/doc/ct_expand.md) makes it possible to evaluate an +expression at compile-time and substitute the result as a compile-time constant. + +Less known modules, perhaps: +* [`parse_trans_pp`](http://github.com/uwiger/parse_trans/blob/master/doc/parse_trans_pp.md) can be called with escript to pretty-print source from + debug-compiled .beam files. +* [`parse_trans_codegen`](http://github.com/uwiger/parse_trans/blob/master/doc/parse_trans_codegen.md) provides pseudo-functions that can be used for + simple code generation. +* [`parse_trans`](http://github.com/uwiger/parse_trans/blob/master/doc/parse_trans.md) provides various helper functions for traversing code and + managing complex parse transforms + + +## Modules ## + + + + + + + + +
ct_expand
exprecs
parse_trans
parse_trans_codegen
parse_trans_mod
parse_trans_pp
+ diff --git a/ptrans_deporder/_checkouts/parse_trans/ebin/parse_trans.app b/ptrans_deporder/_checkouts/parse_trans/ebin/parse_trans.app new file mode 100644 index 0000000..d67397b --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/ebin/parse_trans.app @@ -0,0 +1,11 @@ +{application,parse_trans, + [{description,"Parse transform library"}, + {vsn,"3.4.1"}, + {registered,[]}, + {applications,[kernel,stdlib,syntax_tools]}, + {env,[]}, + {maintainers,["Ulf Wiger"]}, + {licenses,["Apache 2.0"]}, + {links,[{"Github","https://github.com/uwiger/parse_trans"}]}, + {modules,[ct_expand,exprecs,parse_trans,parse_trans_codegen, + parse_trans_mod,parse_trans_pp]}]}. diff --git a/ptrans_deporder/_checkouts/parse_trans/hex_metadata.config b/ptrans_deporder/_checkouts/parse_trans/hex_metadata.config new file mode 100644 index 0000000..e70ed9f --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/hex_metadata.config @@ -0,0 +1,14 @@ +{<<"app">>,<<"parse_trans">>}. +{<<"build_tools">>,[<<"rebar3">>]}. +{<<"description">>,<<"Parse transform library">>}. +{<<"files">>, + [<<"LICENSE">>,<<"README.md">>,<<"include/codegen.hrl">>, + <<"include/exprecs.hrl">>,<<"rebar.config">>,<<"rebar.lock">>, + <<"src/ct_expand.erl">>,<<"src/exprecs.erl">>,<<"src/parse_trans.app.src">>, + <<"src/parse_trans.erl">>,<<"src/parse_trans_codegen.erl">>, + <<"src/parse_trans_mod.erl">>,<<"src/parse_trans_pp.erl">>]}. +{<<"licenses">>,[<<"Apache 2.0">>]}. +{<<"links">>,[{<<"Github">>,<<"https://github.com/uwiger/parse_trans">>}]}. +{<<"name">>,<<"parse_trans">>}. +{<<"requirements">>,[]}. +{<<"version">>,<<"3.4.1">>}. diff --git a/ptrans_deporder/_checkouts/parse_trans/include/codegen.hrl b/ptrans_deporder/_checkouts/parse_trans/include/codegen.hrl new file mode 100644 index 0000000..a1dfd49 --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/include/codegen.hrl @@ -0,0 +1,25 @@ +%%% The contents of this file are subject to the Erlang Public License, +%%% Version 1.1, (the "License"); you may not use this file except in +%%% compliance with the License. You may obtain a copy of the License at +%%% http://www.erlang.org/EPLICENSE +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is parse_trans-2.0. +%% +%% Copyright (c) 2014 Ericsson AB +%% +%% Contributor(s): ______________________________________. + +%%------------------------------------------------------------------- +%% File : codegen.hrl +%% @author : Ulf Wiger +%% @end +%% Description : +%% +%% Created : 25 Feb 2010 by Ulf Wiger +%%------------------------------------------------------------------- +-compile({parse_transform, parse_trans_codegen}). diff --git a/ptrans_deporder/_checkouts/parse_trans/include/exprecs.hrl b/ptrans_deporder/_checkouts/parse_trans/include/exprecs.hrl new file mode 100644 index 0000000..6d92609 --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/include/exprecs.hrl @@ -0,0 +1,25 @@ +%%% The contents of this file are subject to the Erlang Public License, +%%% Version 1.1, (the "License"); you may not use this file except in +%%% compliance with the License. You may obtain a copy of the License at +%%% http://www.erlang.org/EPLICENSE +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is exprecs-0.2. +%% +%% Copyright (c) 2014 Ericsson AB +%% +%% Contributor(s): ______________________________________. + +%%------------------------------------------------------------------- +%% File : exprecs.hrl +%% @author : Ulf Wiger +%% @end +%% Description : +%% +%% Created : 25 Feb 2010 by Ulf Wiger +%%------------------------------------------------------------------- +-compile({parse_transform, exprecs}). diff --git a/ptrans_deporder/_checkouts/parse_trans/rebar.config b/ptrans_deporder/_checkouts/parse_trans/rebar.config new file mode 100644 index 0000000..f8f42cf --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/rebar.config @@ -0,0 +1,43 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -------------------------------------------------- +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% -------------------------------------------------- + +{minimum_otp_vsn, "21.0"}. + +{erl_first_files, ["src/parse_trans.erl", + "src/parse_trans_pp.erl", + "src/parse_trans_codegen.erl"]}. + +{erl_opts, [debug_info + ]}. +{xref_checks, [undefined_function_calls]}. + +{profiles, + [{docs, + [ + {deps, [{edown, "0.8.1"}]}, + {edoc_opts, [{doclet, edown_doclet}, + {top_level_readme, + {"./README.md", + "http://github.com/uwiger/parse_trans", + "master"}}]} + ]}, + {test, + [ + {extra_src_dirs, ["examples"]} + ] + } + ]}. diff --git a/ptrans_deporder/_checkouts/parse_trans/rebar.lock b/ptrans_deporder/_checkouts/parse_trans/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/ptrans_deporder/_checkouts/parse_trans/src/ct_expand.erl b/ptrans_deporder/_checkouts/parse_trans/src/ct_expand.erl new file mode 100644 index 0000000..ce41b45 --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/src/ct_expand.erl @@ -0,0 +1,249 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -------------------------------------------------- +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% -------------------------------------------------- +%% File : ct_expand.erl +%% @author : Ulf Wiger +%% @end +%% Created : 7 Apr 2010 by Ulf Wiger +%%------------------------------------------------------------------- + +%% @doc Compile-time expansion utility +%% +%% This module serves as an example of parse_trans-based transforms, +%% but might also be a useful utility in its own right. +%% The transform searches for calls to the pseudo-function +%% `ct_expand:term(Expr)', and then replaces the call site with the +%% result of evaluating `Expr' at compile-time. +%% +%% For example, the line +%% +%% `ct_expand:term(lists:sort([3,5,2,1,4]))' +%% +%% would be expanded at compile-time to `[1,2,3,4,5]'. +%% +%% ct_expand has now been extended to also evaluate calls to local functions. +%% See examples/ct_expand_test.erl for some examples. +%% +%% A debugging facility exists: passing the option {ct_expand_trace, Flags} as an option, +%% or adding a compiler attribute -ct_expand_trace(Flags) will enable a form of call trace. +%% +%% `Flags' can be `[]' (no trace) or `[F]', where `F' is `c' (call trace), +%% `r' (return trace), or `x' (exception trace)'. +%% +%% @end +-module(ct_expand). +-export([parse_transform/2]). + +-export([extract_fun/3, + lfun_rewrite/2]). + +-type form() :: any(). +-type forms() :: [form()]. +-type options() :: [{atom(), any()}]. + + +-spec parse_transform(forms(), options()) -> + forms(). +parse_transform(Forms, Options) -> + Trace = ct_trace_opt(Options, Forms), + case parse_trans:depth_first(fun(T,F,C,A) -> + xform_fun(T,F,C,A,Forms, Trace) + end, [], Forms, Options) of + {error, Es} -> + Es ++ Forms; + {NewForms, _} -> + parse_trans:revert(NewForms) + end. + +ct_trace_opt(Options, Forms) -> + case proplists:get_value(ct_expand_trace, Options) of + undefined -> + case [Opt || {attribute,_,compile,{ct_expand_trace,Opt}} <- Forms] of + [] -> + []; + [_|_] = L -> + lists:last(L) + end; + Flags when is_list(Flags) -> + Flags + end. + +xform_fun(application, Form, _Ctxt, Acc, Forms, Trace) -> + MFA = erl_syntax_lib:analyze_application(Form), + case MFA of + {?MODULE, {term, 1}} -> + LFH = fun(Name, Args, Bs) -> + eval_lfun( + extract_fun(Name, length(Args), Forms), + Args, Bs, Forms, Trace) + end, + Args = erl_syntax:application_arguments(Form), + RevArgs = parse_trans:revert(Args), + case erl_eval:exprs(RevArgs, [], {eval, LFH}) of + {value, Value,[]} -> + {abstract(Value), Acc}; + Other -> + parse_trans:error(cannot_evaluate,?LINE, + [{expr, RevArgs}, + {error, Other}]) + end; + _ -> + {Form, Acc} + end; +xform_fun(_, Form, _Ctxt, Acc, _, _) -> + {Form, Acc}. + +extract_fun(Name, Arity, Forms) -> + case [F_ || {function,_,N_,A_,_Cs} = F_ <- Forms, + N_ == Name, A_ == Arity] of + [] -> + erlang:error({undef, [{Name, Arity}]}); + [FForm] -> + FForm + end. + +eval_lfun({function,L,F,_,Clauses}, Args, Bs, Forms, Trace) -> + Line = erl_anno:line(L), + try + {ArgsV, Bs1} = lists:mapfoldl( + fun(A, Bs_) -> + {value,AV,Bs1_} = + erl_eval:expr(A, Bs_, lfh(Forms, Trace)), + {abstract(AV), Bs1_} + end, Bs, Args), + Expr = {call, L, {'fun', L, {clauses, lfun_rewrite(Clauses, Forms)}}, ArgsV}, + call_trace(Trace =/= [], Line, F, ArgsV), + {value, Ret, _} = + erl_eval:expr(Expr, erl_eval:new_bindings(), lfh(Forms, Trace)), + ret_trace(lists:member(r, Trace) orelse lists:member(x, Trace), + Line, F, Args, Ret), + %% restore bindings + {value, Ret, Bs1} + catch + error:Err -> + exception_trace(lists:member(x, Trace), Line, F, Args, Err), + error(Err) + end. + +lfh(Forms, Trace) -> + {eval, fun(Name, As, Bs1) -> + eval_lfun( + extract_fun(Name, length(As), Forms), + As, Bs1, Forms, Trace) + end}. + +call_trace(false, _, _, _) -> ok; +call_trace(true, L, F, As) -> + io:fwrite("ct_expand (~w): call ~s~n", [L, pp_function(F, As)]). + +pp_function(F, []) -> + atom_to_list(F) ++ "()"; +pp_function(F, [A|As]) -> + lists:flatten([atom_to_list(F), "(", + [pp_term(A) | + [[",", pp_term(A_)] || A_ <- As]], + ")"]). + +pp_term({'fun',_, {clauses,_}} = F) -> + %% erl_parse:normalise/1 doesn't handle this + io_lib:fwrite("~s", [erl_prettypr:format(F)]); +pp_term(F) -> + io_lib:fwrite("~p", [erl_parse:normalise(F)]). + +ret_trace(false, _, _, _, _) -> ok; +ret_trace(true, L, F, Args, Res) -> + io:fwrite("ct_expand (~w): returned from ~w/~w: ~w~n", + [L, F, length(Args), Res]). + +exception_trace(false, _, _, _, _) -> ok; +exception_trace(true, L, F, Args, Err) -> + io:fwrite("ct_expand (~w): exception from ~w/~w: ~p~n", [L, F, length(Args), Err]). + + +lfun_rewrite(Exprs, Forms) -> + parse_trans:plain_transform( + fun({'fun',L,{function,F,A}}) -> + {function,_,_,_,Cs} = extract_fun(F, A, Forms), + {'fun',L,{clauses, Cs}}; + (_) -> + continue + end, Exprs). + + +%% abstract/1 - modified from erl_eval:abstract/1: +-type abstract_expr() :: term(). +-spec abstract(Data) -> AbsTerm when + Data :: term(), + AbsTerm :: abstract_expr(). +abstract(T) -> + abstract(T, erl_anno:new(0)). + +abstract(T, A) when is_function(T) -> + case erlang:fun_info(T, module) of + {module, erl_eval} -> + case erl_eval:fun_data(T) of + {fun_data, _Imports, Clauses} -> + {'fun', A, {clauses, Clauses}}; + false -> + erlang:error(function_clause) % mimicking erl_parse:abstract(T) + end; + _ -> + erlang:error(function_clause) + end; +abstract(T, A) when is_integer(T) -> {integer,A,T}; +abstract(T, A) when is_float(T) -> {float,A,T}; +abstract(T, A) when is_atom(T) -> {atom,A,T}; +abstract([], A) -> {nil,A}; +abstract(B, A) when is_bitstring(B) -> + {bin, A, [abstract_byte(Byte, A) || Byte <- bitstring_to_list(B)]}; +abstract([C|T], A) when is_integer(C), 0 =< C, C < 256 -> + abstract_string(T, [C], A); +abstract([H|T], A) -> + {cons,A,abstract(H, A),abstract(T, A)}; +abstract(Map, A) when is_map(Map) -> + {map,A,abstract_map(Map, A)}; +abstract(Tuple, A) when is_tuple(Tuple) -> + {tuple,A,abstract_list(tuple_to_list(Tuple), A)}. + +abstract_string([C|T], String, A) when is_integer(C), 0 =< C, C < 256 -> + abstract_string(T, [C|String], A); +abstract_string([], String, A) -> + {string, A, lists:reverse(String)}; +abstract_string(T, String, A) -> + not_string(String, abstract(T, A), A). + +not_string([C|T], Result, A) -> + not_string(T, {cons, A, {integer, A, C}, Result}, A); +not_string([], Result, _A) -> + Result. + +abstract_list([H|T], A) -> + [abstract(H, A)|abstract_list(T, A)]; +abstract_list([], _A) -> + []. + +abstract_map(Map, A) -> + [{map_field_assoc,A,abstract(K, A),abstract(V, A)} + || {K,V} <- lists:sort(maps:to_list(Map)) + ]. + +abstract_byte(Byte, Line) when is_integer(Byte) -> + {bin_element, Line, {integer, Line, Byte}, default, default}; +abstract_byte(Bits, Line) -> + Sz = bit_size(Bits), + <> = Bits, + {bin_element, Line, {integer, Line, Val}, {integer, Line, Sz}, default}. + diff --git a/ptrans_deporder/_checkouts/parse_trans/src/exprecs.erl b/ptrans_deporder/_checkouts/parse_trans/src/exprecs.erl new file mode 100755 index 0000000..c7d567e --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/src/exprecs.erl @@ -0,0 +1,1626 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -------------------------------------------------- +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% -------------------------------------------------- +%% File : exprecs.erl +%% @author : Ulf Wiger +%% @end +%% Description : +%% +%% Created : 13 Feb 2006 by Ulf Wiger +%% Rewritten: Jan-Feb 2010 by Ulf Wiger +%%------------------------------------------------------------------- + +%% @doc Parse transform for generating record access functions. +%%

This parse transform can be used to reduce compile-time +%% dependencies in large systems.

+%%

In the old days, before records, Erlang programmers often wrote +%% access functions for tuple data. This was tedious and error-prone. +%% The record syntax made this easier, but since records were implemented +%% fully in the pre-processor, a nasty compile-time dependency was +%% introduced.

+%%

This module automates the generation of access functions for +%% records. While this method cannot fully replace the utility of +%% pattern matching, it does allow a fair bit of functionality on +%% records without the need for compile-time dependencies.

+%%

Whenever record definitions need to be exported from a module, +%% inserting a compiler attribute, +%% export_records([RecName|...]) causes this transform +%% to lay out access functions for the exported records:

+%% +%% As an example, consider the following module: +%%
+%% -module(test_exprecs).
+%%
+%% -record(r,{a = 0 :: integer(),b = 0 :: integer(),c = 0 :: integer()}).
+%% -record(s,{a}).
+%% -record(t,{}).
+%%
+%% -export_records([r,s,t]).
+%%
+%% -export_type(['#prop-r'/0,
+%%               '#attr-r'/0,
+%%               '#prop-s'/0,
+%%               '#attr-s'/0,
+%%               '#prop-t'/0,
+%%               '#attr-t'/0]).
+%%
+%% -type '#prop-s'() :: {a, any()}.
+%%
+%% -type '#attr-s'() :: a.
+%%
+%% -type '#prop-r'() :: {a, any()} | {b, any()} | {c, any()}.
+%%
+%% -type '#attr-r'() :: a | b | c.
+%%
+%% -type '#prop-t'() :: any().
+%%
+%% -type '#attr-t'() :: any().
+%%
+%% -spec '#exported_records-'() -> [r | s | t].
+%%
+%% -spec '#new-'(r) -> #r{};
+%%              (s) -> #s{};
+%%              (t) -> #t{}.
+%%
+%% -spec '#info-'(r) -> ['#attr-r'()];
+%%               (s) -> ['#attr-s'()];
+%%               (t) -> ['#attr-t'()].
+%%
+%% -spec '#info-'(r, size) -> 4;
+%%               (r, fields) -> ['#attr-r'()];
+%%               (s, size) -> 2;
+%%               (s, fields) -> ['#attr-s'()];
+%%               (t, size) -> 1;
+%%               (t, fields) -> ['#attr-t'()].
+%%
+%% -spec '#pos-'(r, a) -> 1;
+%%              (r, b) -> 2;
+%%              (r, c) -> 3;
+%%              (s, a) -> 1.
+%%
+%% -spec '#is_record-'(any()) -> boolean().
+%%
+%% -spec '#is_record-'(any(), any()) -> boolean().
+%%
+%% -spec '#get-'(a, #s{}) -> any();
+%%              (a, #r{}) -> any();
+%%              (b, #r{}) -> any();
+%%              (c, #r{}) -> any();
+%%              (['#attr-t'()], #t{}) -> [];
+%%              (['#attr-s'()], #s{}) -> [any()];
+%%              (['#attr-r'()], #r{}) -> [any()].
+%%
+%% -spec '#set-'(['#prop-r'()], #r{}) -> #r{};
+%%              (['#prop-s'()], #s{}) -> #s{};
+%%              (['#prop-t'()], #t{}) -> #t{}.
+%%
+%% -spec '#fromlist-'(['#prop-r'()], #r{}) -> #r{};
+%%                   (['#prop-s'()], #s{}) -> #s{};
+%%                   (['#prop-t'()], #t{}) -> #t{}.
+%%
+%% -spec '#frommap-'(#{a => any(), b => any(), c => any()}, #r{}) -> #r{};
+%%                  (#{a => any()}, #s{}) -> #s{};
+%%                  (#{}, #t{}) -> #t{}.
+%%
+%% -spec '#lens-'('#attr-r'(), r) ->
+%%                   {fun((#r{}) -> any()), fun((any(), #r{}) -> #r{})};
+%%               ('#attr-s'(), s) ->
+%%                   {fun((#s{}) -> any()), fun((any(), #s{}) -> #s{})};
+%%               ('#attr-t'(), t) ->
+%%                   {fun((#t{}) -> any()), fun((any(), #t{}) -> #t{})}.
+%%
+%% -spec '#new-r'() -> #r{}.
+%%
+%% -spec '#new-r'(['#prop-r'()]) -> #r{}.
+%%
+%% -spec '#get-r'(a, #r{}) -> any();
+%%               (b, #r{}) -> any();
+%%               (c, #r{}) -> any();
+%%               (['#attr-r'()], #r{}) -> [any()].
+%%
+%% -spec '#set-r'(['#prop-r'()], #r{}) -> #r{}.
+%%
+%% -spec '#fromlist-r'(['#prop-r'()]) -> #r{}.
+%%
+%% -spec '#fromlist-r'(['#prop-r'()], #r{}) -> #r{}.
+%%
+%% -spec '#frommap-r'(#{a => any(), b => any(), c => any()}) -> #r{}.
+%%
+%% -spec '#frommap-r'(#{a => any(), b => any(), c => any()}, #r{}) -> #r{}.
+%%
+%% -spec '#pos-r'('#attr-r'() | atom()) -> integer().
+%%
+%% -spec '#info-r'(fields) -> [a | b | c];
+%%                (size) -> 4.
+%%
+%% -spec '#lens-r'('#attr-r'()) ->
+%%                    {fun((#r{}) -> any()), fun((any(), #r{}) -> #r{})}.
+%%
+%% -spec '#new-s'() -> #s{}.
+%%
+%% -spec '#new-s'(['#prop-s'()]) -> #s{}.
+%%
+%% -spec '#get-s'(a, #s{}) -> any();
+%%               (['#attr-s'()], #s{}) -> [any()].
+%%
+%% -spec '#set-s'(['#prop-s'()], #s{}) -> #s{}.
+%%
+%% -spec '#fromlist-s'(['#prop-s'()]) -> #s{}.
+%%
+%% -spec '#fromlist-s'(['#prop-s'()], #s{}) -> #s{}.
+%%
+%% -spec '#frommap-s'(#{a => any()}) -> #s{}.
+%%
+%% -spec '#frommap-s'(#{a => any()}, #s{}) -> #s{}.
+%%
+%% -spec '#pos-s'('#attr-s'() | atom()) -> integer().
+%%
+%% -spec '#info-s'(fields) -> [a];
+%%                (size) -> 2.
+%%
+%% -spec '#lens-s'('#attr-s'()) ->
+%%                    {fun((#s{}) -> any()), fun((any(), #s{}) -> #s{})}.
+%%
+%% -spec '#new-t'() -> #t{}.
+%%
+%% -spec '#new-t'(['#prop-t'()]) -> #t{}.
+%%
+%% -spec '#get-t'(['#attr-t'()], #t{}) -> [any()].
+%%
+%% -spec '#set-t'(['#prop-t'()], #t{}) -> #t{}.
+%%
+%% -spec '#fromlist-t'(['#prop-t'()]) -> #t{}.
+%%
+%% -spec '#fromlist-t'(['#prop-t'()], #t{}) -> #t{}.
+%%
+%% -spec '#frommap-t'(#{}) -> #t{}.
+%%
+%% -spec '#frommap-t'(#{}, #t{}) -> #t{}.
+%%
+%% -spec '#pos-t'('#attr-t'() | atom()) -> integer().
+%%
+%% -spec '#info-t'(fields) -> [];
+%%                (size) -> 1.
+%%
+%% -spec '#lens-t'('#attr-t'()) ->
+%%                    {fun((#t{}) -> any()), fun((any(), #t{}) -> #t{})}.
+%%
+%% -file("c:/git/etp/_checkouts/parse_trans/examples/test_exprecs.erl", 1).
+%%
+%% '#exported_records-'() ->
+%%     [r,s,t].
+%%
+%% '#new-'(r) ->
+%%     '#new-r'();
+%% '#new-'(s) ->
+%%     '#new-s'();
+%% '#new-'(t) ->
+%%     '#new-t'().
+%%
+%% '#info-'(RecName) ->
+%%     '#info-'(RecName, fields).
+%%
+%% '#info-'(r, Info) ->
+%%     '#info-r'(Info);
+%% '#info-'(s, Info) ->
+%%     '#info-s'(Info);
+%% '#info-'(t, Info) ->
+%%     '#info-t'(Info).
+%%
+%% '#pos-'(r, Attr) ->
+%%     '#pos-r'(Attr);
+%% '#pos-'(s, Attr) ->
+%%     '#pos-s'(Attr);
+%% '#pos-'(t, Attr) ->
+%%     '#pos-t'(Attr).
+%%
+%% '#is_record-'(X) ->
+%%     if
+%%         is_record(X, r, 4) ->
+%%             true;
+%%         is_record(X, s, 2) ->
+%%             true;
+%%         is_record(X, t, 1) ->
+%%             true;
+%%         true ->
+%%             false
+%%     end.
+%%
+%% '#is_record-'(t, Rec) when tuple_size(Rec) == 1, element(1, Rec) == t ->
+%%     true;
+%% '#is_record-'(s, Rec) when tuple_size(Rec) == 2, element(1, Rec) == s ->
+%%     true;
+%% '#is_record-'(r, Rec) when tuple_size(Rec) == 4, element(1, Rec) == r ->
+%%     true;
+%% '#is_record-'(_, _) ->
+%%     false.
+%%
+%% '#get-'(Attrs, {r,_,_,_} = Rec) when true ->
+%%     '#get-r'(Attrs, Rec);
+%% '#get-'(Attrs, {s,_} = Rec) when true ->
+%%     '#get-s'(Attrs, Rec);
+%% '#get-'(Attrs, {t} = Rec) when true ->
+%%     '#get-t'(Attrs, Rec).
+%%
+%% '#set-'(Vals, {r,_,_,_} = Rec) when true ->
+%%     '#set-r'(Vals, Rec);
+%% '#set-'(Vals, {s,_} = Rec) when true ->
+%%     '#set-s'(Vals, Rec);
+%% '#set-'(Vals, {t} = Rec) when true ->
+%%     '#set-t'(Vals, Rec).
+%%
+%% '#fromlist-'(Vals, {r,_,_,_} = Rec) when true ->
+%%     '#fromlist-r'(Vals, Rec);
+%% '#fromlist-'(Vals, {s,_} = Rec) when true ->
+%%     '#fromlist-s'(Vals, Rec);
+%% '#fromlist-'(Vals, {t} = Rec) when true ->
+%%     '#fromlist-t'(Vals, Rec).
+%%
+%% '#frommap-'(Vals, {r,_,_,_} = Rec) when true ->
+%%     '#frommap-r'(Vals, Rec);
+%% '#frommap-'(Vals, {s,_} = Rec) when true ->
+%%     '#frommap-s'(Vals, Rec);
+%% '#frommap-'(Vals, {t} = Rec) when true ->
+%%     '#frommap-t'(Vals, Rec).
+%%
+%% '#lens-'(Attr, r) ->
+%%     '#lens-r'(Attr);
+%% '#lens-'(Attr, s) ->
+%%     '#lens-s'(Attr);
+%% '#lens-'(Attr, t) ->
+%%     '#lens-t'(Attr).
+%%
+%% '#new-r'() ->
+%%     {r,0,0,0}.
+%%
+%% '#new-r'(Vals) ->
+%%     '#set-r'(Vals, {r,0,0,0}).
+%%
+%% '#get-r'(Attrs, R) when is_list(Attrs) ->
+%%     [
+%%      '#get-r'(A, R) ||
+%%          A <- Attrs
+%%     ];
+%% '#get-r'(a, R) ->
+%%     case R of
+%%         {r,rec0,_,_} ->
+%%             rec0;
+%%         _ ->
+%%             error({badrecord,r})
+%%     end;
+%% '#get-r'(b, R) ->
+%%     case R of
+%%         {r,_,rec1,_} ->
+%%             rec1;
+%%         _ ->
+%%             error({badrecord,r})
+%%     end;
+%% '#get-r'(c, R) ->
+%%     case R of
+%%         {r,_,_,rec2} ->
+%%             rec2;
+%%         _ ->
+%%             error({badrecord,r})
+%%     end;
+%% '#get-r'(Attr, R) ->
+%%     error(bad_record_op, ['#get-r',Attr,R]).
+%%
+%% '#set-r'(Vals, Rec) ->
+%%     F = % fun-info: {0,0,'-#set-r/2-fun-0-'}
+%%         fun([], R, _F1) ->
+%%                R;
+%%            ([{a,V}|T], R, F1) when is_list(T) ->
+%%                F1(T,
+%%                   begin
+%%                       rec3 = R,
+%%                       case rec3 of
+%%                           {r,_,_,_} ->
+%%                               setelement(2, rec3, V);
+%%                           _ ->
+%%                               error({badrecord,r})
+%%                       end
+%%                   end,
+%%                   F1);
+%%            ([{b,V}|T], R, F1) when is_list(T) ->
+%%                F1(T,
+%%                   begin
+%%                       rec4 = R,
+%%                       case rec4 of
+%%                           {r,_,_,_} ->
+%%                               setelement(3, rec4, V);
+%%                           _ ->
+%%                               error({badrecord,r})
+%%                       end
+%%                   end,
+%%                   F1);
+%%            ([{c,V}|T], R, F1) when is_list(T) ->
+%%                F1(T,
+%%                   begin
+%%                       rec5 = R,
+%%                       case rec5 of
+%%                           {r,_,_,_} ->
+%%                               setelement(4, rec5, V);
+%%                           _ ->
+%%                               error({badrecord,r})
+%%                       end
+%%                   end,
+%%                   F1);
+%%            (Vs, R, _) ->
+%%                error(bad_record_op, ['#set-r',Vs,R])
+%%         end,
+%%     F(Vals, Rec, F).
+%%
+%% '#fromlist-r'(Vals) when is_list(Vals) ->
+%%     '#fromlist-r'(Vals, '#new-r'()).
+%%
+%% '#fromlist-r'(Vals, Rec) ->
+%%     AttrNames = [{a,2},{b,3},{c,4}],
+%%     F = % fun-info: {0,0,'-#fromlist-r/2-fun-0-'}
+%%         fun([], R, _F1) ->
+%%                R;
+%%            ([{H,Pos}|T], R, F1) when is_list(T) ->
+%%                case lists:keyfind(H, 1, Vals) of
+%%                    false ->
+%%                        F1(T, R, F1);
+%%                    {_,Val} ->
+%%                        F1(T, setelement(Pos, R, Val), F1)
+%%                end
+%%         end,
+%%     F(AttrNames, Rec, F).
+%%
+%% '#frommap-r'(Vals) when is_map(Vals) ->
+%%     '#frommap-r'(Vals, '#new-r'()).
+%%
+%% '#frommap-r'(Vals, Rec) ->
+%%     List = maps:to_list(Vals),
+%%     '#fromlist-r'(List, Rec).
+%%
+%% '#pos-r'(a) ->
+%%     2;
+%% '#pos-r'(b) ->
+%%     3;
+%% '#pos-r'(c) ->
+%%     4;
+%% '#pos-r'(A) when is_atom(A) ->
+%%     0.
+%%
+%% '#info-r'(fields) ->
+%%     [a,b,c];
+%% '#info-r'(size) ->
+%%     4.
+%%
+%% '#lens-r'(a) ->
+%%     {% fun-info: {0,0,'-#lens-r/1-fun-0-'}
+%%      fun(R) ->
+%%             '#get-r'(a, R)
+%%      end,
+%%      % fun-info: {0,0,'-#lens-r/1-fun-1-'}
+%%      fun(X, R) ->
+%%             '#set-r'([{a,X}], R)
+%%      end};
+%% '#lens-r'(b) ->
+%%     {% fun-info: {0,0,'-#lens-r/1-fun-2-'}
+%%      fun(R) ->
+%%             '#get-r'(b, R)
+%%      end,
+%%      % fun-info: {0,0,'-#lens-r/1-fun-3-'}
+%%      fun(X, R) ->
+%%             '#set-r'([{b,X}], R)
+%%      end};
+%% '#lens-r'(c) ->
+%%     {% fun-info: {0,0,'-#lens-r/1-fun-4-'}
+%%      fun(R) ->
+%%             '#get-r'(c, R)
+%%      end,
+%%      % fun-info: {0,0,'-#lens-r/1-fun-5-'}
+%%      fun(X, R) ->
+%%             '#set-r'([{c,X}], R)
+%%      end};
+%% '#lens-r'(Attr) ->
+%%     error(bad_record_op, ['#lens-r',Attr]).
+%%
+%% '#new-s'() ->
+%%     {s,undefined}.
+%%
+%% '#new-s'(Vals) ->
+%%     '#set-s'(Vals, {s,undefined}).
+%%
+%% '#get-s'(Attrs, R) when is_list(Attrs) ->
+%%     [
+%%      '#get-s'(A, R) ||
+%%          A <- Attrs
+%%     ];
+%% '#get-s'(a, R) ->
+%%     case R of
+%%         {s,rec6} ->
+%%             rec6;
+%%         _ ->
+%%             error({badrecord,s})
+%%     end;
+%% '#get-s'(Attr, R) ->
+%%     error(bad_record_op, ['#get-s',Attr,R]).
+%%
+%% '#set-s'(Vals, Rec) ->
+%%     F = % fun-info: {0,0,'-#set-s/2-fun-0-'}
+%%         fun([], R, _F1) ->
+%%                R;
+%%            ([{a,V}|T], R, F1) when is_list(T) ->
+%%                F1(T,
+%%                   begin
+%%                       rec7 = R,
+%%                       case rec7 of
+%%                           {s,rec8} ->
+%%                               {s,V};
+%%                           _ ->
+%%                               error({badrecord,s})
+%%                       end
+%%                   end,
+%%                   F1);
+%%            (Vs, R, _) ->
+%%                error(bad_record_op, ['#set-s',Vs,R])
+%%         end,
+%%     F(Vals, Rec, F).
+%%
+%% '#fromlist-s'(Vals) when is_list(Vals) ->
+%%     '#fromlist-s'(Vals, '#new-s'()).
+%%
+%% '#fromlist-s'(Vals, Rec) ->
+%%     AttrNames = [{a,2}],
+%%     F = % fun-info: {0,0,'-#fromlist-s/2-fun-0-'}
+%%         fun([], R, _F1) ->
+%%                R;
+%%            ([{H,Pos}|T], R, F1) when is_list(T) ->
+%%                case lists:keyfind(H, 1, Vals) of
+%%                    false ->
+%%                        F1(T, R, F1);
+%%                    {_,Val} ->
+%%                        F1(T, setelement(Pos, R, Val), F1)
+%%                end
+%%         end,
+%%     F(AttrNames, Rec, F).
+%%
+%% '#frommap-s'(Vals) when is_map(Vals) ->
+%%     '#frommap-s'(Vals, '#new-s'()).
+%%
+%% '#frommap-s'(Vals, Rec) ->
+%%     List = maps:to_list(Vals),
+%%     '#fromlist-s'(List, Rec).
+%%
+%% '#pos-s'(a) ->
+%%     2;
+%% '#pos-s'(A) when is_atom(A) ->
+%%     0.
+%%
+%% '#info-s'(fields) ->
+%%     [a];
+%% '#info-s'(size) ->
+%%     2.
+%%
+%% '#lens-s'(a) ->
+%%     {% fun-info: {0,0,'-#lens-s/1-fun-0-'}
+%%      fun(R) ->
+%%             '#get-s'(a, R)
+%%      end,
+%%      % fun-info: {0,0,'-#lens-s/1-fun-1-'}
+%%      fun(X, R) ->
+%%             '#set-s'([{a,X}], R)
+%%      end};
+%% '#lens-s'(Attr) ->
+%%     error(bad_record_op, ['#lens-s',Attr]).
+%%
+%% '#new-t'() ->
+%%     {t}.
+%%
+%% '#new-t'(Vals) ->
+%%     '#set-t'(Vals, {t}).
+%%
+%% '#get-t'(Attrs, R) when is_list(Attrs) ->
+%%     [
+%%      '#get-t'(A, R) ||
+%%          A <- Attrs
+%%     ];
+%% '#get-t'(Attr, R) ->
+%%     error(bad_record_op, ['#get-t',Attr,R]).
+%%
+%% '#set-t'(Vals, Rec) ->
+%%     F = % fun-info: {0,0,'-#set-t/2-fun-0-'}
+%%         fun([], R, _F1) ->
+%%                R;
+%%            (Vs, R, _) ->
+%%                error(bad_record_op, ['#set-t',Vs,R])
+%%         end,
+%%     F(Vals, Rec, F).
+%%
+%% '#fromlist-t'(Vals) when is_list(Vals) ->
+%%     '#fromlist-t'(Vals, '#new-t'()).
+%%
+%% '#fromlist-t'(Vals, Rec) ->
+%%     AttrNames = [],
+%%     F = % fun-info: {0,0,'-#fromlist-t/2-fun-0-'}
+%%         fun([], R, _F1) ->
+%%                R;
+%%            ([{H,Pos}|T], R, F1) when is_list(T) ->
+%%                case lists:keyfind(H, 1, Vals) of
+%%                    false ->
+%%                        F1(T, R, F1);
+%%                    {_,Val} ->
+%%                        F1(T, setelement(Pos, R, Val), F1)
+%%                end
+%%         end,
+%%     F(AttrNames, Rec, F).
+%%
+%% '#frommap-t'(Vals) when is_map(Vals) ->
+%%     '#frommap-t'(Vals, '#new-t'()).
+%%
+%% '#frommap-t'(Vals, Rec) ->
+%%     List = maps:to_list(Vals),
+%%     '#fromlist-t'(List, Rec).
+%%
+%% '#pos-t'(A) when is_atom(A) ->
+%%     0.
+%%
+%% '#info-t'(fields) ->
+%%     [];
+%% '#info-t'(size) ->
+%%     1.
+%%
+%% '#lens-t'(Attr) ->
+%%     error(bad_record_op, ['#lens-t',Attr]).
+%%
+%% f() ->
+%%     foo.
+%% 
+%% +%% It is possible to modify the naming rules of exprecs, through the use +%% of the following attributes (example reflecting the current rules): +%% +%%
+%% -exprecs_prefix(["#", operation, "-"]).
+%% -exprecs_fname([prefix, record]).
+%% -exprecs_vfname([fname, "__", version]).
+%% 
+%% +%% The lists must contain strings or any of the following control atoms: +%%
    +%%
  • in `exprecs_prefix': `operation'
  • +%%
  • in `exprecs_fname': `operation', `record', `prefix'
  • +%%
  • in `exprecs_vfname': `operation', `record', `prefix', `fname', `version' +%%
  • +%%
+%% +%% Exprecs will substitute the control atoms with the string values of the +%% corresponding items. The result will then be flattened and converted to an +%% atom (a valid function or type name). +%% +%% `operation' is one of: +%%
+%%
`new'
Creates a new record
+%%
`get'
Retrieves given attribute values from a record
+%%
`set'
Sets given attribute values in a record
+%%
`fromlist'
Creates a record from a key-value list
+%%
`info'
Equivalent to record_info/2
+%%
`pos'
Returns the position of a given attribute
+%%
`is_record'
Tests if a value is a specific record
+%%
`convert'
Converts an old record to the current version
+%%
`prop'
Used only in type specs
+%%
`attr'
Used only in type specs
+%%
`lens'
Returns a 'lens' (an accessor pair) as described in +%% [http://github.com/jlouis/erl-lenses]
+%%
+%% +%% @end + +-module(exprecs). + +-export([parse_transform/2, + format_error/1, +% transform/3, + context/2]). + +-record(context, {module, + function, + arity}). + +-record(pass1, {exports = [], + generated = false, + records = [], + record_types = [], + versions = orddict:new(), + inserted = false, + prefix = ["#", operation, "-"], + fname = [prefix, record], + vfname = [fname, "__", version]}). + +-include("../include/codegen.hrl"). + +-define(HERE, {?MODULE, ?LINE}). + +-define(ERROR(R, F, I), + begin + rpt_error(R, F, I), + throw({error,get_pos(I),{unknown,R}}) + end). + +-type form() :: any(). +-type forms() :: [form()]. +-type options() :: [{atom(), any()}]. + + +get_pos(I) -> + case proplists:get_value(form, I) of + undefined -> + 0; + Form -> + Anno = erl_syntax:get_pos(Form), + erl_anno:location(Anno) + end. + +-spec parse_transform(forms(), options()) -> + forms(). +parse_transform(Forms, Options) -> + parse_trans:top(fun do_transform/2, Forms, Options). + +do_transform(Forms, Context) -> + Acc1 = versioned_records( + add_untyped_recs( + parse_trans:do_inspect(fun inspect_f/4, #pass1{}, + Forms, Context))), + {Forms2, Acc2} = + parse_trans:do_transform(fun generate_f/4, Acc1, Forms, Context), + parse_trans:revert(verify_generated(Forms2, Acc2, Context)). + +add_untyped_recs(#pass1{records = Rs, + record_types = RTypes, + exports = Es} = Acc) -> + Untyped = + [{R, Def} || {R, Def} <- Rs, + lists:member(R, Es), + not lists:keymember(R, 1, RTypes)], + RTypes1 = [{R, lists:map( + fun({record_field,L,{atom,_,A}}) -> {A, t_any(L)}; + ({record_field,L,{atom,_,A},_}) -> {A, t_any(L)}; + ({typed_record_field, + {record_field,L,{atom,_,A}},_}) -> {A, t_any(L)}; + ({typed_record_field, + {record_field,L,{atom,_,A},_},_}) -> {A, t_any(L)} + end, Def)} || {R, Def} <- Untyped], + Acc#pass1{record_types = RTypes ++ RTypes1}. + +inspect_f(attribute, {attribute,_L,exprecs_prefix,Pattern}, _Ctxt, Acc) -> + {false, Acc#pass1{prefix = Pattern}}; +inspect_f(attribute, {attribute,_L,exprecs_fname,Pattern}, _Ctxt, Acc) -> + {false, Acc#pass1{fname = Pattern}}; +inspect_f(attribute, {attribute,_L,exprecs_vfname,Pattern}, _Ctxt, Acc) -> + {false, Acc#pass1{vfname = Pattern}}; +inspect_f(attribute, {attribute,_L,record,RecDef}, _Ctxt, Acc) -> + Recs0 = Acc#pass1.records, + {false, Acc#pass1{records = [RecDef|Recs0]}}; +inspect_f(attribute, {attribute,_L,export_records, E}, _Ctxt, Acc) -> + Exports0 = Acc#pass1.exports, + NewExports = Exports0 ++ E, + {false, Acc#pass1{exports = NewExports}}; +inspect_f(attribute, {attribute, _L, type, + {{record, R}, RType,_}}, _Ctxt, Acc) -> + Type = lists:map( + fun({typed_record_field, {record_field,_,{atom,_,A}}, T}) -> + {A, T}; + ({typed_record_field, {record_field,_,{atom,_,A},_}, T}) -> + {A, T}; + ({record_field, _, {atom,L,A}, _}) -> + {A, t_any(L)}; + ({record_field, _, {atom,L,A}}) -> + {A, t_any(L)} + end, RType), + {false, Acc#pass1{record_types = [{R, Type}|Acc#pass1.record_types]}}; +inspect_f(_Type, _Form, _Context, Acc) -> + {false, Acc}. + +generate_f(attribute, {attribute,L,export_records,_} = Form, _Ctxt, + #pass1{exports = [_|_] = Es, versions = Vsns, + inserted = false} = Acc) -> + case check_record_names(Es, L, Acc) of + ok -> continue; + {error, Bad} -> + ?ERROR(invalid_record_exports, ?HERE, Bad) + end, + Exports = [{fname(exported_records, Acc), 0}, + {fname(new, Acc), 1}, + {fname(info, Acc), 1}, + {fname(info, Acc), 2}, + {fname(pos, Acc), 2}, + {fname(is_record, Acc), 1}, + {fname(is_record, Acc), 2}, + {fname(get, Acc), 2}, + {fname(set, Acc), 2}, + {fname(fromlist, Acc), 2}, + {fname(frommap, Acc), 2}, + {fname(lens, Acc), 2} | + lists:flatmap( + fun(Rec) -> + RecS = atom_to_list(Rec), + FNew = fname(new, RecS, Acc), + [{FNew, 0}, {FNew,1}, + {fname(get, RecS, Acc), 2}, + {fname(set, RecS, Acc), 2}, + {fname(pos, RecS, Acc), 1}, + {fname(fromlist, RecS, Acc), 1}, + {fname(frommap, RecS, Acc), 1}, + {fname(fromlist, RecS, Acc), 2}, + {fname(frommap, RecS, Acc), 2}, + {fname(info, RecS, Acc), 1}, + {fname(lens, RecS, Acc), 1}] + end, Es)] ++ version_exports(Vsns, Acc), + TypeExports = + lists:flatmap( + fun(Rec) -> + [{fname(prop, Rec, Acc), 0}, + {fname(attr, Rec, Acc), 0}] + end, Es), + {[], Form, + [{attribute,L,export,Exports}, + {attribute,L,ignore_xref,Exports}, + {attribute,L,export_type, TypeExports}], + false, Acc#pass1{inserted = true}}; +generate_f(function, Form, _Context, #pass1{generated = false} = Acc) -> + % Layout record funs before first function + Anno = erl_syntax:get_pos(Form), + L = erl_anno:location(Anno), + Forms = generate_specs_and_accessors(L, Acc), + {Forms, Form, [], false, Acc#pass1{generated = true}}; +generate_f(_Type, Form, _Ctxt, Acc) -> + {Form, false, Acc}. + +generate_specs_and_accessors(L, #pass1{exports = [_|_] = Es, + record_types = Ts} = Acc) -> + Specs = generate_specs(L, [{R,T} || {R,T} <- Ts, lists:member(R, Es)], Acc), + Funs = generate_accessors(L, Acc), + Specs ++ Funs; +generate_specs_and_accessors(_, _) -> + []. + +verify_generated(Forms, #pass1{} = Acc, _Context) -> + case (Acc#pass1.generated == true) orelse (Acc#pass1.exports == []) of + true -> + Forms; + false -> + % should be re-written to use the parse_trans helper...? + [{eof,Last}|RevForms] = lists:reverse(Forms), + Anno = erl_anno:new(Last), + [{function, NewLast, _, _, _}|_] = RevAs = + lists:reverse(generate_specs_and_accessors(Anno, Acc)), + Loc = erl_anno:location(NewLast), + lists:reverse([{eof, increment_line(Loc)} | RevAs] ++ RevForms) + end. + +increment_line(L) when is_integer(L) -> + L + 1; +increment_line({L, C}) when is_integer(L), is_integer(C) -> + {L + 1, C}. + +check_record_names(Es, L, #pass1{records = Rs}) -> + case [E || E <- Es, + not(lists:keymember(E, 1, Rs))] of + [] -> + ok; + Bad -> + {error, [{L,E} || E <- Bad]} + end. + +versioned_records(#pass1{exports = Es, records = Rs} = Pass1) -> + case split_recnames(Rs) of + [] -> + Pass1#pass1{versions = []}; + [_|_] = Versions -> + Exp_vsns = + lists:foldl( + fun(Re, Acc) -> + case orddict:find(atom_to_list(Re), Versions) of + {ok, Vs} -> + orddict:store(Re, Vs, Acc); + error -> + Acc + end + end, orddict:new(), Es), + Pass1#pass1{versions = Exp_vsns} + end. + +version_exports([], _Acc) -> + []; +version_exports([_|_] = _Vsns, Acc) -> + [{list_to_atom(fname_prefix(info, Acc)), 3}, + {list_to_atom(fname_prefix(convert, Acc)), 2}]. + + +version_accessors(_L, #pass1{versions = []}) -> + []; +version_accessors(L, #pass1{versions = Vsns} = Acc) -> + Flat_vsns = flat_versions(Vsns), + [f_convert(Vsns, L, Acc), + f_info_3(Vsns, L, Acc)] + ++ [f_info_1(Rname, Acc, L, V) || {Rname,V} <- Flat_vsns]. + +flat_versions(Vsns) -> + lists:flatmap(fun({R,Vs}) -> + [{R,V} || V <- Vs] + end, Vsns). + +split_recnames(Rs) -> + lists:foldl( + fun({R,_As}, Acc) -> + case re:split(atom_to_list(R), "__", [{return, list}]) of + [Base, V] -> + orddict:append(Base,V,Acc); + [_] -> + Acc + end + end, orddict:new(), Rs). + +generate_specs(L, Specs, Acc) -> + [[ + {attribute, L, type, + {fname(prop, R, Acc), + {type, L, union, + [{type, L, tuple, [{atom,L,A},T]} || {A,T} <- Attrs]}, []}}, + {attribute, L, type, + {fname(attr, R, Acc), + {type, L, union, + [{atom, L, A} || {A,_} <- Attrs]}, []}} + ] || {R, Attrs} <- Specs, Attrs =/= []] ++ + [[{attribute, L, type, + {fname(prop, R, Acc), + {type, L, any, []}, []}}, + {attribute, L, type, + {fname(attr, R, Acc), + {type, L, any, []}, []}}] || {R, []} <- Specs]. + + +generate_accessors(L, Acc) -> + lists:flatten( + [f_exported_recs(Acc, L), + f_new_(Acc, L), + f_info(Acc, L), + f_info_2(Acc, L), + f_pos_2(Acc, L), + f_isrec_1(Acc, L), + f_isrec_2(Acc, L), + f_get(Acc, L), + f_set(Acc, L), + f_fromlist(Acc, L), + f_frommap(Acc, L), + f_lens_(Acc, L)| + lists:append( + lists:map( + fun(Rname) -> + Fields = get_flds(Rname, Acc), + [f_new_0(Rname, L, Acc), + f_new_1(Rname, L, Acc), + f_get_2(Rname, Fields, L, Acc), + f_set_2(Rname, Fields, L, Acc), + f_fromlist_1(Rname, L, Acc), + f_fromlist_2(Rname, Fields, L, Acc), + f_frommap_1(Rname, L, Acc), + f_frommap_2(Rname, L, Acc), + f_pos_1(Rname, Fields, L, Acc), + f_info_1(Rname, Acc, L), + f_lens_1(Rname, Fields, L, Acc)] + end, Acc#pass1.exports))] ++ version_accessors(L, Acc)). + +get_flds(Rname, #pass1{records = Rs}) -> + {_, Flds} = lists:keyfind(Rname, 1, Rs), + lists:map( + fun({record_field,_, {atom,_,N}}) -> N; + ({record_field,_, {atom,_,N}, _}) -> N; + ({typed_record_field,{record_field,_,{atom,_,N}},_}) -> N; + ({typed_record_field,{record_field,_,{atom,_,N},_},_}) -> N + end, Flds). + + +fname_prefix(Op, #pass1{prefix = Pat}) -> + lists:flatten( + lists:map(fun(operation) -> str(Op); + (X) -> str(X) + end, Pat)). +%% fname_prefix(Op, #pass1{} = Acc) -> +%% case Op of +%% new -> "#new-"; +%% get -> "#get-"; +%% set -> "#set-"; +%% fromlist -> "#fromlist-"; +%% info -> "#info-"; +%% pos -> "#pos-"; +%% is_record -> "#is_record-"; +%% convert -> "#convert-"; +%% prop -> "#prop-"; +%% attr -> "#attr-" +%% end. + +%% fname_prefix(Op, Rname, Acc) -> +%% fname_prefix(Op, Acc) ++ str(Rname). + +str(A) when is_atom(A) -> + atom_to_list(A); +str(S) when is_list(S) -> + S. + +fname(Op, #pass1{} = Acc) -> + list_to_atom(fname_prefix(Op, Acc)). + %% list_to_atom(fname_prefix(Op, Acc)). + +fname(Op, Rname, #pass1{fname = FPat} = Acc) -> + Prefix = fname_prefix(Op, Acc), + list_to_atom( + lists:flatten( + lists:map(fun(prefix) -> str(Prefix); + (record) -> str(Rname); + (operation) -> str(Op); + (X) -> str(X) + end, FPat))). + %% list_to_atom(fname_prefix(Op, Rname, Acc)). + +fname(Op, Rname, V, #pass1{vfname = VPat} = Acc) -> + list_to_atom( + lists:flatten( + lists:map(fun(prefix) -> fname_prefix(Op, Acc); + (operation) -> str(Op); + (record) -> str(Rname); + (version) -> str(V); + (fname) -> str(fname(Op, Rname, Acc)); + (X) -> str(X) + end, VPat))). + %% list_to_atom(fname_prefix(Op, Rname, Acc) ++ "__" ++ V). + + +%%% Meta functions + +f_exported_recs(#pass1{exports = Es} = Acc, L) -> + Fname = fname(exported_records, Acc), + [funspec(L, Fname, [], + t_list(L, [t_union(L, [t_atom(L, E) || E <- Es])])), + {function, L, Fname, 0, + [{clause, L, [], [], + [erl_parse:abstract(Es, L)]}]} + ]. + +%%% Accessor functions +%%% +f_new_(#pass1{exports = Es} = Acc, L) -> + Fname = fname(new, Acc), + [funspec(L, Fname, [ {[t_atom(L, E)], t_record(L, E)} || + E <- Es ]), + {function, L, fname(new, Acc), 1, + [{clause, L, [{atom, L, Re}], [], + [{call, L, {atom, L, fname(new, Re, Acc)}, []}]} + || Re <- Es]} + ]. + +f_new_0(Rname, L, Acc) -> + Fname = fname(new, Rname, Acc), + [funspec(L, Fname, [], t_record(L, Rname)), + {function, L, fname(new, Rname, Acc), 0, + [{clause, L, [], [], + [{record, L, Rname, []}]}]} + ]. + + +f_new_1(Rname, L, Acc) -> + Fname = fname(new, Rname, Acc), + [funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)])], + t_record(L, Rname)), + {function, L, Fname, 1, + [{clause, L, [{var, L, 'Vals'}], [], + [{call, L, {atom, L, fname(set, Rname, Acc)}, + [{var, L, 'Vals'}, + {record, L, Rname, []} + ]}] + }]}]. + +funspec(L, Fname, [{H,_} | _] = Alts) -> + Arity = length(H), + {attribute, L, spec, + {{Fname, Arity}, + [{type, L, 'fun', [{type, L, product, Head}, Ret]} || + {Head, Ret} <- Alts, + no_empty_union(Head)]}}. + +no_empty_union({type,_,union,[]}) -> + false; +no_empty_union(T) when is_tuple(T) -> + no_empty_union(tuple_to_list(T)); +no_empty_union([H|T]) -> + no_empty_union(H) andalso no_empty_union(T); +no_empty_union(_) -> + true. + + + + +funspec(L, Fname, Head, Returns) -> + Arity = length(Head), + {attribute, L, spec, + {{Fname, Arity}, + [{type, L, 'fun', + [{type, L, product, Head}, Returns]}]}}. + + +t_prop(L, Rname, Acc) -> {user_type, L, fname(prop, Rname, Acc), []}. +t_attr(L, Rname, Acc) -> {user_type, L, fname(attr, Rname, Acc), []}. +t_union(L, Alt) -> {type, L, union, lists:usort(Alt)}. +t_any(L) -> {type, L, any, []}. +t_atom(L) -> {type, L, atom, []}. +t_atom(L, A) -> {atom, L, A}. +t_integer(L) -> {type, L, integer, []}. +t_integer(L, I) -> {integer, L, I}. +t_list(L, Es) -> {type, L, list, Es}. +t_fun(L, As, Res) -> {type, L, 'fun', [{type, L, product, As}, Res]}. +t_tuple(L, Es) -> {type, L, tuple, Es}. +t_boolean(L) -> {type, L, boolean, []}. +t_record(L, A) -> {type, L, record, [{atom, L, A}]}. +t_map(L, Rname, Acc) -> {type, L, map, + [{type, L, map_field_assoc, [t_atom(L, F), t_any(L)]} + || F <- get_flds(Rname, Acc) + ] + }. + +f_set_2(Rname, Flds, L, Acc) -> + Fname = fname(set, Rname, Acc), + TRec = t_record(L, Rname), + [funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)]), TRec], TRec), + {function, L, Fname, 2, + [{clause, L, [{var, L, 'Vals'}, {var, L, 'Rec'}], [], + [{match, L, {var, L, 'F'}, + {'fun', L, + {clauses, + [{clause, L, [{nil,L}, + {var,L,'R'}, + {var,L,'_F1'}], + [], + [{var, L, 'R'}]} | + [{clause, L, + [{cons, L, {tuple, L, [{atom, L, Attr}, + {var, L, 'V'}]}, + {var, L, 'T'}}, + {var, L, 'R'}, + {var, L, 'F1'}], + [[{call, L, {atom, L, is_list}, [{var, L, 'T'}]}]], + [{call, L, {var, L, 'F1'}, + [{var,L,'T'}, + {record, L, {var,L,'R'}, Rname, + [{record_field, L, + {atom, L, Attr}, + {var, L, 'V'}}]}, + {var, L, 'F1'}]}]} || Attr <- Flds] + ++ [{clause, L, [{var, L, 'Vs'}, {var,L,'R'},{var,L,'_'}], + [], + [bad_record_op(L, Fname, 'Vs', 'R')]}] + ]}}}, + {call, L, {var, L, 'F'}, [{var, L, 'Vals'}, + {var, L, 'Rec'}, + {var, L, 'F'}]}]}]}]. + +bad_record_op(L, Fname, Val) -> + {call, L, {remote, L, {atom,L,erlang}, {atom,L,error}}, + [{atom,L,bad_record_op}, {cons, L, {atom, L, Fname}, + {cons, L, {var, L, Val}, + {nil, L}}}]}. + +bad_record_op(L, Fname, Val, R) -> + {call, L, {remote, L, {atom,L,erlang}, {atom,L,error}}, + [{atom,L,bad_record_op}, {cons, L, {atom, L, Fname}, + {cons, L, {var, L, Val}, + {cons, L, {var, L, R}, + {nil, L}}}}]}. + + +f_pos_1(Rname, Flds, L, Acc) -> + Fname = fname(pos, Rname, Acc), + FieldList = lists:zip(Flds, lists:seq(2, length(Flds)+1)), + [ + funspec(L, Fname, [t_union(L, [t_attr(L, Rname, Acc), + t_atom(L)])], + t_integer(L)), + {function, L, Fname, 1, + [{clause, L, + [{atom, L, FldName}], + [], + [{integer, L, Pos}]} || {FldName, Pos} <- FieldList] ++ + [{clause, L, + [{var, L, 'A'}], + [[{call, L, {atom, L, is_atom}, [{var, L, 'A'}]}]], + [{integer, L, 0}]}] + }]. + +f_frommap_1(Rname, L, Acc) -> + Fname = fname(frommap, Rname, Acc), + [ + funspec(L, Fname, [t_map(L, Rname, Acc)], + t_record(L, Rname)), + {function, L, Fname, 1, + [{clause, L, [{var, L, 'Vals'}], + [[ {call, L, {atom, L, is_map}, [{var, L, 'Vals'}]} ]], + [{call, L, {atom, L, Fname}, + [{var, L, 'Vals'}, + {call, L, {atom, L, fname(new, Rname, Acc)}, []}]} + ]} + ]}]. + +f_fromlist_1(Rname, L, Acc) -> + Fname = fname(fromlist, Rname, Acc), + [ + funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)])], + t_record(L, Rname)), + {function, L, Fname, 1, + [{clause, L, [{var, L, 'Vals'}], + [[ {call, L, {atom, L, is_list}, [{var, L, 'Vals'}]} ]], + [{call, L, {atom, L, Fname}, + [{var, L, 'Vals'}, + {call, L, {atom, L, fname(new, Rname, Acc)}, []}]} + ]} + ]}]. + +f_frommap_2(Rname, L, Acc) -> + Fname = fname(frommap, Rname, Acc), + TRec = t_record(L, Rname), + [ + funspec(L, Fname, [t_map(L, Rname, Acc), TRec], + TRec), + {function, L, Fname, 2, + [{clause, L, [{var, L, 'Vals'}, {var, L, 'Rec'}], [], + [{match, L, {var, L, 'List'}, + {call, L, {remote, L, {atom, L, maps}, {atom, L, to_list}}, + [{var, L, 'Vals'}] + } + }, + {call, L, {atom, L, fname(fromlist, Rname, Acc)}, + [{var, L, 'List'}, {var, L, 'Rec'}] + } + ]} + ]}]. + +f_fromlist_2(Rname, Flds, L, Acc) -> + Fname = fname(fromlist, Rname, Acc), + FldList = field_list(Flds), + TRec = t_record(L, Rname), + [ + funspec(L, Fname, [t_list(L, [t_prop(L, Rname, Acc)]), TRec], + TRec), + {function, L, Fname, 2, + [{clause, L, [{var, L, 'Vals'}, {var, L, 'Rec'}], [], + [{match, L, {var, L, 'AttrNames'}, FldList}, + {match, L, {var, L, 'F'}, + {'fun', L, + {clauses, + [{clause, L, [{nil, L}, + {var, L,'R'}, + {var, L,'_F1'}], + [], + [{var, L, 'R'}]}, + {clause, L, [{cons, L, + {tuple, L, [{var, L, 'H'}, + {var, L, 'Pos'}]}, + {var, L, 'T'}}, + {var, L, 'R'}, {var, L, 'F1'}], + [[{call, L, {atom, L, is_list}, [{var, L, 'T'}]}]], + [{'case', L, {call, L, {remote, L, + {atom,L,lists},{atom,L,keyfind}}, + [{var,L,'H'},{integer,L,1},{var,L,'Vals'}]}, + [{clause, L, [{atom,L,false}], [], + [{call, L, {var, L, 'F1'}, [{var, L, 'T'}, + {var, L, 'R'}, + {var, L, 'F1'}]}]}, + {clause, L, [{tuple, L, [{var,L,'_'},{var,L,'Val'}]}], + [], + [{call, L, {var, L, 'F1'}, + [{var, L, 'T'}, + {call, L, {atom, L, 'setelement'}, + [{var, L, 'Pos'}, {var, L, 'R'}, {var, L, 'Val'}]}, + {var, L, 'F1'}]}]} + ]} + ]} + ]}}}, + {call, L, {var, L, 'F'}, [{var, L, 'AttrNames'}, + {var, L, 'Rec'}, + {var, L, 'F'}]} + ]} + ]}]. + +field_list(Flds) -> + erl_parse:abstract( + lists:zip(Flds, lists:seq(2, length(Flds)+1))). + + + +f_get_2(R, Flds, L, Acc) -> + FName = fname(get, R, Acc), + {_, Types} = lists:keyfind(R, 1, Acc#pass1.record_types), + [funspec(L, FName, + [{[t_atom(L, A), t_record(L, R)], T} + || {A, T} <- Types] + ++ [{[t_list(L, [t_attr(L, R, Acc)]), t_record(L, R)], + t_list(L, [t_any(L)])}] + ), + {function, L, FName, 2, + [{clause, L, [{var, L, 'Attrs'}, {var, L, 'R'}], + [[{call, L, {atom, L, is_list}, [{var, L, 'Attrs'}]}]], + [{lc, L, {call, L, {atom, L, FName}, [{var, L, 'A'}, {var, L, 'R'}]}, + [{generate, L, {var, L, 'A'}, {var, L, 'Attrs'}}]}] + } | + [{clause, L, [{atom, L, Attr}, {var, L, 'R'}], [], + [{record_field, L, {var, L, 'R'}, R, {atom, L, Attr}}]} || + Attr <- Flds]] ++ + [{clause, L, [{var, L, 'Attr'}, {var, L, 'R'}], [], + [bad_record_op(L, FName, 'Attr', 'R')]}] + }]. + + +f_info(Acc, L) -> + Fname = list_to_atom(fname_prefix(info, Acc)), + [funspec(L, Fname, + [{[t_atom(L, R)], + t_list(L, [t_attr(L, R, Acc)])} + || R <- Acc#pass1.exports]), + {function, L, Fname, 1, + [{clause, L, + [{var, L, 'RecName'}], [], + [{call, L, {atom, L, Fname}, [{var, L, 'RecName'}, {atom, L, fields}]}] + }]} + ]. + +f_isrec_2(#pass1{records = Rs, exports = Es} = Acc, L) -> + Fname = list_to_atom(fname_prefix(is_record, Acc)), + Info = [{R,length(As) + 1} || {R,As} <- Rs, lists:member(R, Es)], + [%% This contract is correct, but is ignored by Dialyzer because it + %% has overlapping domains: + %% funspec(L, Fname, + %% [{[t_atom(L, R), t_record(L, R)], t_atom(L, true)} + %% || R <- Es] ++ + %% [{[t_any(L), t_any(L)], t_atom(L, false)}]), + %% This is less specific, but more useful to Dialyzer: + funspec(L, Fname, [{[t_any(L), t_any(L)], t_boolean(L)}]), + {function, L, Fname, 2, + lists:map( + fun({R, Ln}) -> + {clause, L, + [{atom, L, R}, {var, L, 'Rec'}], + [[{op,L,'==', + {call, L, {atom,L,tuple_size},[{var,L,'Rec'}]}, + {integer, L, Ln}}, + {op,L,'==', + {call,L,{atom,L,element},[{integer,L,1}, + {var,L,'Rec'}]}, + {atom, L, R}}]], + [{atom, L, true}]} + end, Info) ++ + [{clause, L, [{var,L,'_'}, {var,L,'_'}], [], + [{atom, L, false}]}]} + ]. + + +f_info_2(Acc, L) -> + Fname = list_to_atom(fname_prefix(info, Acc)), + [funspec(L, Fname, + lists:flatmap( + fun(Rname) -> + Flds = get_flds(Rname, Acc), + TRec = t_atom(L, Rname), + [{[TRec, t_atom(L, size)], t_integer(L, length(Flds)+1)}, + {[TRec, t_atom(L, fields)], + t_list(L, [t_attr(L, Rname, Acc)])}] + end, Acc#pass1.exports)), + {function, L, Fname, 2, + [{clause, L, + [{atom, L, R}, + {var, L, 'Info'}], + [], + [{call, L, {atom, L, fname(info, R, Acc)}, [{var, L, 'Info'}]}]} || + R <- Acc#pass1.exports]} + ]. + +f_info_3(Versions, L, Acc) -> + Fname = list_to_atom(fname_prefix(info, Acc)), + [ + {function, L, Fname, 3, + [{clause, L, + [{atom, L, R}, + {var, L, 'Info'}, + {string, L, V}], + [], + [{call, L, {atom, L, fname(info,R,V,Acc)}, [{var, L, 'Info'}]}]} || + {R,V} <- flat_versions(Versions)]} + ]. + +f_pos_2(#pass1{exports = Es} = Acc, L) -> + Fname = list_to_atom(fname_prefix(pos, Acc)), + [ + funspec(L, Fname, lists:flatmap( + fun(R) -> + Flds = get_flds(R, Acc), + %% PFlds = lists:zip( + %% lists:seq(2, length(Flds)+1), Flds), + Ps = lists:seq(2, length(Flds)+1), + [{[t_atom(L, R), t_union( + L, ([t_atom(L, F) + || F <- Flds] + ++ [t_atom(L)]))], + t_union(L, ([t_integer(L, P) || P <- Ps] + ++ [t_integer(L, 0)]))}] + %% [{[t_atom(L, R), t_atom(L, A)], + %% t_integer(L, P)} || {P,A} <- PFlds] + %% ++ [{[t_atom(L, R), t_any(L)], + %% t_integer(L, 0)}] + end, Es)), + {function, L, Fname, 2, + [{clause, L, + [{atom, L, R}, + {var, L, 'Attr'}], + [], + [{call, L, {atom, L, fname(pos, R, Acc)}, [{var, L, 'Attr'}]}]} || + R <- Acc#pass1.exports]} + ]. + +f_isrec_1(Acc, L) -> + Fname = list_to_atom(fname_prefix(is_record, Acc)), + [%% This contract is correct, but is ignored by Dialyzer because it + %% has overlapping domains: + %% funspec(L, Fname, + %% [{[t_record(L, R)], t_atom(L, true)} + %% || R <- Acc#pass1.exports] + %% ++ [{[t_any(L)], t_atom(L, false)}]), + %% This is less specific, but more useful to Dialyzer: + funspec(L, Fname, [{[t_any(L)], t_boolean(L)}]), + {function, L, Fname, 1, + [{clause, L, + [{var, L, 'X'}], + [], + [{'if',L, + [{clause, L, [], [[{call, L, {atom,L,is_record}, + [{var,L,'X'},{atom,L,R}]}]], + [{atom,L,true}]} || R <- Acc#pass1.exports] ++ + [{clause,L, [], [[{atom,L,true}]], + [{atom, L, false}]}]}]} + ]} + ]. + + + +f_get(#pass1{record_types = RTypes, exports = Es} = Acc, L) -> + Fname = list_to_atom(fname_prefix(get, Acc)), + [funspec(L, Fname, + lists:append( + [[{[t_atom(L, A), t_record(L, R)], T} + || {A, T} <- Types] + || {R, Types} <- RTypes, lists:member(R, Es)]) + ++ [{[t_list(L, [t_attr(L, R, Acc)]), t_record(L, R)], + t_list(L, [t_union(L, [Ts || {_, Ts} <- Types])])} + || {R, Types} <- RTypes, lists:member(R, Es)] + ), + {function, L, Fname, 2, + [{clause, L, + [{var, L, 'Attrs'}, + {var, L, 'Rec'}], + [[{call, L, + {atom, L, is_record}, + [{var, L, 'Rec'}, {atom, L, R}]}]], + [{call, L, {atom, L, fname(get, R, Acc)}, [{var, L, 'Attrs'}, + {var, L, 'Rec'}]}]} || + R <- Es]} + ]. + + +f_set(Acc, L) -> + Fname = list_to_atom(fname_prefix(set, Acc)), + [funspec(L, Fname, + lists:map( + fun(Rname) -> + TRec = t_record(L, Rname), + {[t_list(L, [t_prop(L, Rname, Acc)]), TRec], TRec} + end, Acc#pass1.exports)), + {function, L, Fname, 2, + [{clause, L, + [{var, L, 'Vals'}, + {var, L, 'Rec'}], + [[{call, L, + {atom, L, is_record}, + [{var, L, 'Rec'}, {atom, L, R}]}]], + [{call, L, {atom, L, fname(set, R, Acc)}, [{var, L, 'Vals'}, + {var, L, 'Rec'}]}]} || + R <- Acc#pass1.exports]} + ]. + +f_fromlist(Acc, L) -> + Fname = list_to_atom(fname_prefix(fromlist, Acc)), + [funspec(L, Fname, + lists:map( + fun(Rname) -> + TRec = t_record(L, Rname), + {[t_list(L, [t_prop(L, Rname, Acc)]), TRec], TRec} + end, Acc#pass1.exports)), + {function, L, Fname, 2, + [{clause, L, + [{var, L, 'Vals'}, + {var, L, 'Rec'}], + [[{call, L, + {atom, L, is_record}, + [{var, L, 'Rec'}, {atom, L, R}]}]], + [{call, L, {atom, L, fname(fromlist, R, Acc)}, [{var, L, 'Vals'}, + {var, L, 'Rec'}]}]} || + R <- Acc#pass1.exports]} + ]. + +f_frommap(Acc, L) -> + Fname = list_to_atom(fname_prefix(frommap, Acc)), + [funspec(L, Fname, + lists:map( + fun(Rname) -> + TRec = t_record(L, Rname), + {[t_map(L, Rname, Acc), TRec], TRec} + end, Acc#pass1.exports)), + {function, L, Fname, 2, + [{clause, L, + [{var, L, 'Vals'}, + {var, L, 'Rec'}], + [[{call, L, + {atom, L, is_record}, + [{var, L, 'Rec'}, {atom, L, R}]}]], + [{call, L, {atom, L, fname(frommap, R, Acc)}, [{var, L, 'Vals'}, + {var, L, 'Rec'}]}]} || + R <- Acc#pass1.exports]} + ]. + +f_info_1(Rname, Acc, L) -> + Fname = fname(info, Rname, Acc), + Flds = get_flds(Rname, Acc), + [funspec(L, Fname, [{[t_atom(L, fields)], + t_list(L, [t_union(L, [t_atom(L,F) || F <- Flds])])}, + {[t_atom(L, size)], t_integer(L, length(Flds)+1)}]), + {function, L, Fname, 1, + [{clause, L, [{atom, L, fields}], [], + [{call, L, {atom, L, record_info}, + [{atom, L, fields}, {atom, L, Rname}]}] + }, + {clause, L, [{atom, L, size}], [], + [{call, L, {atom, L, record_info}, + [{atom, L, size}, {atom, L, Rname}]}] + }]} + ]. + +f_info_1(Rname, Acc, L, V) -> + f_info_1(recname(Rname, V), Acc, L). + +recname(Rname, V) -> + list_to_atom(lists:concat([Rname,"__",V])). + +f_convert(_Vsns, L, Acc) -> + {function, L, fname(convert, Acc), 2, + [{clause, L, + [{var, L, 'FromVsn'}, + {var, L, 'Rec'}], + [[{call,L,{atom, L, is_tuple}, + [{var, L, 'Rec'}]}]], + [{match, L, {var, L, 'Rname'}, + {call, L, {atom, L, element}, + [{integer, L, 1}, {var, L, 'Rec'}]}}, + {match,L,{var,L,'Size'}, + {call, L, {atom, L, fname(info, Acc)}, + [{var,L,'Rname'}, {atom, L, size}, {var,L,'FromVsn'}]}}, + {match, L, {var, L, 'Size'}, + {call, L, {atom, L, size}, + [{var, L, 'Rec'}]}}, + %% + %% {match, L, {var, L, 'Old_fields'}, + %% {call, L, {atom,L,fname(info, Acc)}, + %% [{var,L,'Rname'},{atom,L,fields},{var,L,'FromVsn'}]}}, + {match, L, {var, L, 'New_fields'}, + {call, L, {atom,L,fname(info, Acc)}, + [{var,L,'Rname'},{atom,L,fields}]}}, + + {match, L, {var, L, 'Values'}, + {call, L, {remote, L, {atom, L, lists}, {atom, L, zip}}, + [{call, L, {atom,L,fname(info, Acc)}, + [{var,L,'Rname'},{atom,L,fields},{var,L,'FromVsn'}]}, + {call, L, {atom, L, 'tl'}, + [{call, L, {atom, L, tuple_to_list}, + [{var, L, 'Rec'}]}]}]}}, + {match, L, {tuple, L, [{var, L, 'Matching'}, + {var, L, 'Discarded'}]}, + {call, L, {remote, L, {atom, L, lists}, {atom, L, partition}}, + [{'fun',L, + {clauses, + [{clause,L, + [{tuple,L,[{var,L,'F'},{var,L,'_'}]}], + [], + [{call,L, + {remote,L,{atom,L,lists},{atom,L,member}}, + [{var, L, 'F'}, {var,L,'New_fields'}]}]}]}}, + {var, L, 'Values'}]}}, + {tuple, L, [{call, L, {atom, L, fname(set, Acc)}, + [{var, L, 'Matching'}, + {call, L, {atom, L, fname(new, Acc)}, + [{var, L, 'Rname'}]}]}, + {var, L, 'Discarded'}]}] + }]}. + +f_lens_(#pass1{exports = Es} = Acc, L) -> + Fname = fname(lens, Acc), + [ + funspec(L, Fname, [ {[t_attr(L, Rname, Acc), t_atom(L, Rname)], + t_tuple(L, [t_fun(L, [t_record(L, Rname)], t_any(L)), + t_fun(L, [t_any(L), + t_record(L, Rname)], + t_record(L, Rname))])} + || Rname <- Es]), + {function, L, Fname, 2, + [{clause, L, [{var, L, 'Attr'}, {atom, L, Re}], [], + [{call, L, {atom, L, fname(lens, Re, Acc)}, [{var, L, 'Attr'}]}]} + || Re <- Es]} + ]. + +f_lens_1(Rname, Flds, L, Acc) -> + Fname = fname(lens, Rname, Acc), + [funspec(L, Fname, [ {[t_attr(L, Rname, Acc)], + t_tuple(L, [t_fun(L, [t_record(L, Rname)], t_any(L)), + t_fun(L, [t_any(L), + t_record(L, Rname)], + t_record(L, Rname))])} ]), + {function, L, Fname, 1, + [{clause, L, [{atom, L, Attr}], [], + [{tuple, L, [{'fun', L, + {clauses, + [{clause, L, [{var, L, 'R'}], [], + [{call, L, {atom, L, fname(get, Rname, Acc)}, + [{atom, L, Attr}, {var, L, 'R'}]}]} + ]}}, + {'fun', L, + {clauses, + [{clause, L, [{var, L, 'X'}, {var, L, 'R'}], [], + [{call, L, {atom, L, fname(set, Rname, Acc)}, + [{cons,L, {tuple, L, [{atom, L, Attr}, + {var, L, 'X'}]}, {nil,L}}, + {var, L, 'R'}]}] + }]}} + ]}]} || Attr <- Flds] ++ + [{clause, L, [{var, L, 'Attr'}], [], + [bad_record_op(L, Fname, 'Attr')]}] + }]. + +%%% ========== generic parse_transform stuff ============== + +-spec context(atom(), #context{}) -> + term(). +%% @hidden +context(module, #context{module = M} ) -> M; +context(function, #context{function = F}) -> F; +context(arity, #context{arity = A} ) -> A. + + + +rpt_error(Reason, Fun, Info) -> + Fmt = lists:flatten( + ["*** ERROR in parse_transform function:~n" + "*** Reason = ~p~n", + "*** Location: ~p~n", + ["*** ~10w = ~p~n" || _ <- Info]]), + Args = [Reason, Fun | + lists:foldr( + fun({K,V}, Acc) -> + [K, V | Acc] + end, [], Info)], + io:format(Fmt, Args). + +-spec format_error({atom(), term()}) -> + iolist(). +%% @hidden +format_error({_Cat, Error}) -> + Error. diff --git a/ptrans_deporder/_checkouts/parse_trans/src/parse_trans.app.src b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans.app.src new file mode 100644 index 0000000..ad580a5 --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans.app.src @@ -0,0 +1,9 @@ +{application,parse_trans, + [{description,"Parse transform library"}, + {vsn,"3.4.1"}, + {registered,[]}, + {applications,[kernel,stdlib,syntax_tools]}, + {env,[]}, + {maintainers,["Ulf Wiger"]}, + {licenses,["Apache 2.0"]}, + {links,[{"Github","https://github.com/uwiger/parse_trans"}]}]}. diff --git a/ptrans_deporder/_checkouts/parse_trans/src/parse_trans.erl b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans.erl new file mode 100644 index 0000000..c13385c --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans.erl @@ -0,0 +1,968 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -------------------------------------------------- +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% -------------------------------------------------- +%% File : parse_trans.erl +%% @author : Ulf Wiger +%% @end +%% Description : +%% +%% Created : 13 Feb 2006 by Ulf Wiger (then Ericsson) +%%%------------------------------------------------------------------- + +%% @doc Generic parse transform library for Erlang. +%% +%%

...

+%% +%% @end + +-module(parse_trans). + +-compile({no_auto_import,[error/3]}). + +-export([plain_transform/2]). + +-export([ + inspect/4, + transform/4, + depth_first/4, + revert/1, + revert_form/1, + format_exception/2, format_exception/3, + return/2 + ]). + +-export([ + error/3, + format_error/1 + ]). + +-export([ + initial_context/2, + do_inspect/4, + do_transform/4, + do_depth_first/4, + top/3 + ]). + +-export([do_insert_forms/4, + replace_function/4, + replace_function/5, + export_function/3]). + +-export([ + context/2, + get_pos/1, + get_file/1, + get_module/1, + get_attribute/2, + get_attribute/3, + get_orig_syntax_tree/1, + function_exists/3, + optionally_pretty_print/3, + pp_src/2, + pp_beam/1, pp_beam/2 + ]). + +-import(erl_syntax, [atom_value/1, + attribute_name/1, + attribute_arguments/1, + string_value/1, + type/1 + ]). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +-record(context, {module, + function, + arity, + file, + options}). + +%% Useful macros for debugging and error reporting +-define(HERE, {?MODULE, ?LINE}). + +-define(DUMMY_LINE, 9999). + +-define(ERROR(R, F, I, Trace), + begin + rpt_error(R, F, I, Trace), + throw({error,get_pos(I),{R, Trace}}) + end). + +-export_type([forms/0]). + +%% Typedefs +-type form() :: any(). +-type forms() :: [form()]. +-type options() :: [{atom(), any()}]. +-type type() :: atom(). +-type xform_f_rec() :: fun((type(), form(), #context{}, Acc) -> + {form(), boolean(), Acc} + | {forms(), form(), forms(), boolean(), Acc}). +-type xform_f_df() :: fun((type(), form(), #context{}, Acc) -> + {form(), Acc} + | {forms(), form(), forms(), Acc}). +-type insp_f() :: fun((type(), form(), #context{}, A) -> {boolean(), A}). + + +%%% @spec (Reason, Form, Info) -> throw() +%%% Info = [{Key,Value}] +%%% +%%% @doc +%%%

Used to report errors detected during the parse transform.

+%%% @end +%%% +-spec error(string(), any(), [{any(),any()}]) -> + none(). +error(R, _F, I) -> + ST = erlang:process_info(self(), current_stacktrace), + % rpt_error(R, F, I, ST), + throw({error,get_pos(I),{R, ST}}). + +%% @spec plain_transform(Fun, Forms) -> forms() +%% Fun = function() +%% Forms = forms() +%% +%% @doc +%% Performs a transform of `Forms' using the fun `Fun(Form)'. `Form' is always +%% an Erlang abstract form, i.e. it is not converted to syntax_tools +%% representation. The intention of this transform is for the fun to have a +%% catch-all clause returning `continue'. This will ensure that it stays robust +%% against additions to the language. +%% +%% `Fun(Form)' must return either of the following: +%% +%% * `NewForm' - any valid form +%% * `continue' - dig into the sub-expressions of the form +%% * `{done, NewForm}' - Replace `Form' with `NewForm'; return all following +%% forms unchanged +%% * `{error, Reason}' - Abort transformation with an error message. +%% +%% Example - This transform fun would convert all instances of `P ! Msg' to +%% `gproc:send(P, Msg)': +%%
+%% parse_transform(Forms, _Options) ->
+%%     parse_trans:plain_transform(fun do_transform/1, Forms).
+%%
+%% do_transform({'op', L, '!', Lhs, Rhs}) ->
+%%      [NewLhs] = parse_trans:plain_transform(fun do_transform/1, [Lhs]),
+%%      [NewRhs] = parse_trans:plain_transform(fun do_transform/1, [Rhs]),
+%%     {call, L, {remote, L, {atom, L, gproc}, {atom, L, send}},
+%%      [NewLhs, NewRhs]};
+%% do_transform(_) ->
+%%     continue.
+%% 
+%% @end +%% +plain_transform(Fun, Forms) when is_function(Fun, 1), is_list(Forms) -> + plain_transform1(Fun, Forms). + +plain_transform1(_, []) -> + []; +plain_transform1(Fun, [F|Fs]) when is_atom(element(1,F)) -> + case Fun(F) of + skip -> + plain_transform1(Fun, Fs); + continue -> + [list_to_tuple(plain_transform1(Fun, tuple_to_list(F))) | + plain_transform1(Fun, Fs)]; + {done, NewF} -> + [NewF | Fs]; + {error, Reason} -> + error(Reason, F, [{form, F}]); + NewF when is_tuple(NewF) -> + [NewF | plain_transform1(Fun, Fs)] + end; +plain_transform1(Fun, [L|Fs]) when is_list(L) -> + [plain_transform1(Fun, L) | plain_transform1(Fun, Fs)]; +plain_transform1(Fun, [F|Fs]) -> + [F | plain_transform1(Fun, Fs)]; +plain_transform1(_, F) -> + F. + + +%% @spec (list()) -> integer() +%% +%% @doc +%% Tries to retrieve the line number from an erl_syntax form. Returns a +%% (very high) dummy number if not successful. +%% @end +%% +-spec get_pos(list()) -> + erl_anno:location(). +get_pos(I) when is_list(I) -> + case proplists:get_value(form, I) of + undefined -> + ?DUMMY_LINE; + Form -> + Anno = erl_syntax:get_pos(Form), + erl_anno:location(Anno) + end. + + +%%% @spec (Forms) -> string() +%%% @doc +%%% Returns the name of the file being compiled. +%%% @end +%%% +-spec get_file(forms()) -> + string(). +get_file(Forms) -> + string_value(hd(get_attribute(file, Forms, [erl_syntax:string("undefined")]))). + + + +%%% @spec (Forms) -> atom() +%%% @doc +%%% Returns the name of the module being compiled. +%%% @end +%%% +-spec get_module([any()]) -> + atom(). +get_module(Forms) -> + atom_value(hd(get_attribute(module, Forms, [erl_syntax:atom(undefined)]))). + + + +%%% @spec (A, Forms) -> any() +%%% A = atom() +%%% +%%% @doc +%%% Returns the value of the first occurrence of attribute A. +%%% @end +%%% +-spec get_attribute(atom(), [any()]) -> + 'none' | [erl_syntax:syntaxTree()]. +%% +get_attribute(A, Forms) -> get_attribute(A,Forms,[erl_syntax:atom(undefined)]). +get_attribute(A, Forms, Undef) -> + case find_attribute(A, Forms) of + false -> + Undef; + Other -> + Other + end. + +find_attribute(A, [F|Forms]) -> + case type(F) == attribute + andalso atom_value(attribute_name(F)) == A of + true -> + attribute_arguments(F); + false -> + find_attribute(A, Forms) + end; +find_attribute(_, []) -> + false. + +%% @spec (Fname::atom(), Arity::integer(), Forms) -> boolean() +%% +%% @doc +%% Checks whether the given function is defined in Forms. +%% @end +%% +-spec function_exists(atom(), integer(), forms()) -> + boolean(). +function_exists(Fname, Arity, Forms) -> + Fns = proplists:get_value( + functions, erl_syntax_lib:analyze_forms(Forms), []), + lists:member({Fname,Arity}, Fns). + + +%%% @spec (Forms, Options) -> #context{} +%%% +%%% @doc +%%% Initializes a context record. When traversing through the form +%%% list, the context is updated to reflect the current function and +%%% arity. Static elements in the context are the file name, the module +%%% name and the options passed to the transform function. +%%% @end +%%% +-spec initial_context(forms(), options()) -> + #context{}. +initial_context(Forms, Options) -> + File = get_file(Forms), + Module = get_module(Forms), + #context{file = File, + module = Module, + options = Options}. + +%%% @spec (Fun, Acc, Forms, Options) -> {TransformedForms, NewAcc} +%%% Fun = function() +%%% Options = [{Key,Value}] +%%% +%%% @doc +%%% Makes one pass +%%% @end +-spec transform(xform_f_rec(), Acc, forms(), options()) -> + {forms(), Acc} | {error, list()}. +transform(Fun, Acc, Forms, Options) when is_function(Fun, 4) -> + do(fun do_transform/4, Fun, Acc, Forms, Options). + +-spec depth_first(xform_f_df(), Acc, forms(), options()) -> + {forms(), Acc} | {error, list()}. +depth_first(Fun, Acc, Forms, Options) when is_function(Fun, 4) -> + do(fun do_depth_first/4, Fun, Acc, Forms, Options). + +do(Transform, Fun, Acc, Forms, Options) -> + Context = initial_context(Forms, Options), + File = Context#context.file, + try Transform(Fun, Acc, Forms, Context) of + {NewForms, Acc1} when is_list(NewForms) -> + NewForms1 = optionally_renumber(NewForms, Options), + optionally_pretty_print(NewForms1, Options, Context), + {NewForms1, Acc1} + catch + error:Reason:ST -> + {error, + [{File, [{?DUMMY_LINE, ?MODULE, + {Reason, ST}}]}]}; + throw:{error, Ln, What} -> + {error, [{error, {Ln, ?MODULE, What}}]} + end. + +-spec top(function(), forms(), list()) -> + forms() | {error, term()}. +top(F, Forms, Options) -> + Context = initial_context(Forms, Options), + File = Context#context.file, + try F(Forms, Context) of + {error, Reason} -> {error, Reason}; + NewForms when is_list(NewForms) -> + NewForms1 = optionally_renumber(NewForms, Options), + optionally_pretty_print(NewForms1, Options, Context), + NewForms1 + catch + error:Reason:ST -> + {error, + [{File, [{?DUMMY_LINE, ?MODULE, + {Reason, ST}}]}]}; + throw:{error, Ln, What} -> + {error, [{File, [{Ln, ?MODULE, What}]}], []} + end. + +replace_function(F, Arity, NewForm, Forms) -> + replace_function(F, Arity, NewForm, Forms, []). + +replace_function(F, Arity, NewForm, Forms, Opts) -> + {NewForms, _} = + do_transform( + fun(function, Form, _Ctxt, Acc) -> + case erl_syntax:revert(Form) of + {function, _, F, Arity, _} = RevForm -> + {[], NewForm, with_original_f(RevForm, Opts), + false, Acc}; + _ -> + {Form, false, Acc} + end; + (_, Form, _Ctxt, Acc) -> + {Form, false, Acc} + end, false, Forms, initial_context(Forms, [])), + revert(maybe_export_renamed(NewForms, Arity, Opts)). + +with_original_f({function,_,_,_,_} = Form, Opts) -> + case lists:keyfind(rename_original, 1, Opts) of + {_, NewName} when is_atom(NewName) -> + [setelement(3, Form, NewName)]; + _ -> + [] + end. + +maybe_export_renamed(Forms, Arity, Opts) -> + case lists:keyfind(rename_original, 1, Opts) of + {_, NewName} when is_atom(NewName) -> + export_function(NewName, Arity, Forms); + _ -> + Forms + end. + +export_function(F, Arity, Forms) -> + do_insert_forms(above, [{attribute, 1, export, [{F, Arity}]}], Forms, + initial_context(Forms, [])). + +-spec do_insert_forms(above | below, forms(), forms(), #context{}) -> + forms(). +do_insert_forms(above, Insert, Forms, Context) when is_list(Insert) -> + {NewForms, _} = + do_transform( + fun(function, F, _Ctxt, false) -> + {Insert, F, [], _Recurse = false, true}; + (_, F, _Ctxt, Acc) -> + {F, _Recurse = false, Acc} + end, false, Forms, Context), + NewForms; +do_insert_forms(below, Insert, Forms, _Context) when is_list(Insert) -> + insert_below(Forms, Insert). + + +insert_below([F|Rest], Insert) -> + case type(F) of + eof_marker -> + %% In the unlikely case someone misused eof_marker + Insert ++ [F | Rest]; + _ -> + [F|insert_below(Rest, Insert)] + end. + +-spec optionally_pretty_print(forms(), options(), #context{}) -> + ok. +optionally_pretty_print(Result, Options, Context) -> + DoPP = option_value(pt_pp_src, Options, Result), + DoLFs = option_value(pt_log_forms, Options, Result), + File = Context#context.file, + if DoLFs -> + Out1 = outfile(File, forms), + {ok,Fd} = file:open(Out1, [write]), + try lists:foreach(fun(F) -> io:fwrite(Fd, "~p.~n", [F]) end, Result) + after + ok = file:close(Fd) + end; + true -> ok + end, + if DoPP -> + Out2 = outfile(File, pp), + pp_src(Result, Out2), + io:fwrite("Pretty-printed in ~p~n", [Out2]); + true -> ok + end. + +optionally_renumber(Result, Options) -> + case option_value(pt_renumber, Options, Result) of + true -> + io:fwrite("renumbering...~n", []), + Rev = revert(Result), + renumber_(Rev); + false -> + Result + end. + +renumber_(L) when is_list(L) -> + {Result, _} = renumber_(L, 1), + Result. + +renumber_(L, Acc) when is_list(L) -> + lists:mapfoldl(fun renumber_/2, Acc, L); +renumber_(T, Prev) when is_tuple(T) -> + case is_form(T) of + true -> + New = Prev+1, + NewE2 = update_line(element(2, T), New), + T1 = setelement(2, T, NewE2), + {Res, NewAcc} = renumber_(tuple_to_list(T1), New), + {list_to_tuple(Res), NewAcc}; + false -> + L = tuple_to_list(T), + {Res, NewAcc} = renumber_(L, Prev), + {list_to_tuple(Res), NewAcc} + end; +renumber_(X, Prev) -> + {X, Prev}. + +is_form(T) when element(1,T)==type -> true; +is_form(T) -> + try erl_syntax:type(T), + true + catch + error:_ -> + false + end. + +update_line(Element2, Line) -> + case erl_anno:is_anno(Element2) of + true -> + erl_anno:set_line(Line, Element2); + false -> % location + A = erl_anno:new(Element2), + NewA = erl_anno:set_line(Line, A), + erl_anno:location(NewA) + end. + +option_value(Key, Options, Result) -> + case proplists:get_value(Key, Options) of + undefined -> + case find_attribute(Key,Result) of + [Expr] -> + type(Expr) == atom andalso + atom_value(Expr) == true; + _ -> + false + end; + V when is_boolean(V) -> + V + end. + + +%%% @spec (Fun, Forms, Acc, Options) -> NewAcc +%%% Fun = function() +%%% @doc +%%% Equvalent to do_inspect(Fun,Acc,Forms,initial_context(Forms,Options)). +%%% @end +%%% +-spec inspect(insp_f(), A, forms(), options()) -> + A. +inspect(F, Acc, Forms, Options) -> + Context = initial_context(Forms, Options), + do_inspect(F, Acc, Forms, Context). + + + +outfile(File, Type) -> + "lre." ++ RevF = lists:reverse(File), + lists:reverse(RevF) ++ ext(Type). + +ext(pp) -> ".xfm"; +ext(forms) -> ".xforms". + +%% @spec (Forms, Out::filename()) -> ok +%% +%% @doc Pretty-prints the erlang source code corresponding to Forms into Out +%% +-spec pp_src(forms(), string()) -> + ok. +pp_src(Res, F) -> + parse_trans_pp:pp_src(Res, F). +%% Str = [io_lib:fwrite("~s~n", +%% [lists:flatten([erl_pp:form(Fm) || +%% Fm <- revert(Res)])])], +%% file:write_file(F, list_to_binary(Str)). + +%% @spec (Beam::file:filename()) -> string() | {error, Reason} +%% +%% @doc +%% Reads debug_info from the beam file Beam and returns a string containing +%% the pretty-printed corresponding erlang source code. +%% @end +-spec pp_beam(file:filename()) -> ok. +pp_beam(Beam) -> + parse_trans_pp:pp_beam(Beam). + +%% @spec (Beam::filename(), Out::filename()) -> ok | {error, Reason} +%% +%% @doc +%% Reads debug_info from the beam file Beam and pretty-prints it as +%% Erlang source code, storing it in the file Out. +%% @end +%% +-spec pp_beam(file:filename(), file:filename()) -> ok. +pp_beam(F, Out) -> + parse_trans_pp:pp_beam(F, Out). + + +%%% @spec (File) -> Forms +%%% +%%% @doc +%%%

Fetches a Syntax Tree representing the code before pre-processing, +%%% that is, including record and macro definitions. Note that macro +%%% definitions must be syntactically complete forms (this function +%%% uses epp_dodger).

+%%% @end +%%% +-spec get_orig_syntax_tree(string()) -> + forms(). +get_orig_syntax_tree(File) -> + case epp_dodger:parse_file(File) of + {ok, Forms} -> + Forms; + Err -> + error(error_reading_file, ?HERE, [{File,Err}]) + end. + +%%% @spec (Tree) -> Forms +%%% +%%% @doc Reverts back from Syntax Tools format to Erlang forms. +%%%

Note that the Erlang forms are a subset of the Syntax Tools +%%% syntax tree, so this function is safe to call even on a list of +%%% regular Erlang forms.

+%%%

Note2: R16B03 introduced a bug, where forms produced by +%%% `erl_syntax:revert/1' (specifically, implicit funs) could crash the linter. +%%% This function works around that limitation, after first verifying that it's +%%% necessary to do so. Use of the workaround can be forced with the help of +%%% the `parse_trans' environment variable {revert_workaround, true}. This +%%% variable will be removed when R16B03 is no longer 'supported'.

+%%% @end +%%% +-spec revert(forms()) -> + forms(). +revert(Tree) when is_list(Tree) -> + WorkAround = needs_revert_workaround(), + [revert_form(T, WorkAround) || T <- lists:flatten(Tree)]. + +%%% @spec (Tree) -> Form +%%% +%%% @doc Reverts a single form back from Syntax Tools format to Erlang forms. +%%%

`erl_syntax:revert/1' has had a long-standing bug where it doesn't +%%% completely revert attribute forms. This function deals properly with those +%%% cases.

+%%%

Note that the Erlang forms are a subset of the Syntax Tools +%%% syntax tree, so this function is safe to call even on a regular Erlang +%%% form.

+%%%

Note2: R16B03 introduced a bug, where forms produced by +%%% `erl_syntax:revert/1' (specifically, implicit funs) could crash the linter. +%%% This function works around that limitation, after first verifying that it's +%%% necessary to do so. Use of the workaround can be forced with the help of +%%% the `parse_trans' environment variable {revert_workaround, true}. This +%%% variable will be removed when R16B03 is no longer 'supported'.

+%%% @end +revert_form(F) -> + revert_form(F, needs_revert_workaround()). + +revert_form(F, W) -> + case erl_syntax:revert(F) of + {attribute,L,A,Tree} when element(1,Tree) == tree -> + {attribute,L,A,erl_syntax:revert(Tree)}; + Result -> + if W -> fix_impl_fun(Result); + true -> Result + end + end. + +fix_impl_fun({'fun',L,{function,{atom,_,Fn},{integer,_,Ay}}}) -> + {'fun',L,{function,Fn,Ay}}; +fix_impl_fun({'fun',L,{function,{atom,_,M},{atom,_,Fn},{integer,_,Ay}}}) -> + {'fun',L,{function,M,Fn,Ay}}; +fix_impl_fun(T) when is_tuple(T) -> + list_to_tuple([fix_impl_fun(F) || F <- tuple_to_list(T)]); +fix_impl_fun([H|T]) -> + [fix_impl_fun(H) | fix_impl_fun(T)]; +fix_impl_fun(X) -> + X. + +needs_revert_workaround() -> + case application:get_env(parse_trans,revert_workaround) of + {ok, Bool} when is_boolean(Bool) -> Bool; + _ -> + Res = try lint_reverted() + catch + error:_ -> + true + end, + application:set_env(parse_trans,revert_workaround,Res), + Res + end. + +lint_reverted() -> + Ts = [{attribute,1,module,m}, + {attribute,2,export,[{f,0}]}, + erl_syntax:function(erl_syntax:atom(f), + [erl_syntax:clause( + [], + [erl_syntax:implicit_fun( + erl_syntax:atom(f), + erl_syntax:integer(0))])])], + Rev = erl_syntax:revert_forms(Ts), + erl_lint:module(Rev), + false. + + +%%% @spec (Forms, Context) -> Forms | {error,Es,Ws} | {warnings,Forms,Ws} +%%% +%%% @doc Checks the transformed result for errors and warnings +%%%

Errors and warnings can be produced from inside a parse transform, with +%%% a bit of care. The easiest way is to simply produce an `{error, Err}' or +%%% `{warning, Warn}' form in place. This function finds such forms, and +%%% removes them from the form list (otherwise, the linter will crash), and +%%% produces a return value that the compiler can work with.

+%%% +%%% The format of the `error' and `warning' "forms" must be +%%% `{Tag, {Pos, Module, Info}}', where: +%%%
    +%%%
  • `Tag :: error | warning'
  • +%%%
  • `Pos :: LineNumber | {LineNumber, ColumnNumber}'
  • +%%%
  • `Module' is a module that exports a corresponding +%%% `Module:format_error(Info)'
  • +%%%
  • `Info :: term()'
  • +%%%
+%%%

If the error is in the form of a caught exception, `Info' may be produced +%%% using the function {@link format_exception/2}.

+%%% @end +return(Forms, Context) -> + JustForms = plain_transform( + fun({error,_}) -> skip; + ({warning,_}) -> skip; + (_) -> continue + end, Forms), + File = case Context of + #context{file = F} -> F; + _ -> "parse_transform" + end, + case {find_forms(Forms, error), find_forms(Forms, warning)} of + {[], []} -> + JustForms; + {[], Ws} -> + {warnings, JustForms, [{File, [W || {warning,W} <- Ws]}]}; + {Es, Ws} -> + {error, + [{File, [E || {error,E} <- Es]}], + [{File, [W || {warning,W} <- Ws]}]} + end. + +find_forms([H|T], Tag) when element(1, H) == Tag -> + [H|find_forms(T, Tag)]; +find_forms([H|T], Tag) when is_tuple(H) -> + find_forms(tuple_to_list(H), Tag) ++ find_forms(T, Tag); +find_forms([H|T], Tag) when is_list(H) -> + find_forms(H, Tag) ++ find_forms(T, Tag); +find_forms([_|T], Tag) -> + find_forms(T, Tag); +find_forms([], _) -> + []. + + +-define(LINEMAX, 5). +-define(CHAR_MAX, 60). + +%%% @spec (Class, Reason) -> String +%%% @equiv format_exception(Class, Reason, 4) +format_exception(Class, Reason) -> + format_exception(Class, Reason, 4). + +%%% @spec (Class, Reason, Lines) -> String +%%% Class = error | throw | exit +%%% Reason = term() +%%% Lines = integer() | infinity +%%% +%%% @doc Produces a few lines of user-friendly formatting of exception info +%%% +%%% This function is very similar to the exception pretty-printing in the shell, +%%% but returns a string that can be used as error info e.g. by error forms +%%% handled by {@link return/2}. By default, the first 4 lines of the +%%% pretty-printed exception info are returned, but this can be controlled +%%% with the `Lines' parameter. +%%% +%%% Note that a stacktrace is generated inside this function. +%%% @end +format_exception(Class, Reason, Lines) -> + {current_stacktrace, ST} = erlang:process_info(self(), current_stacktrace), + PrintF = fun(Term, I) -> + io_lib_pretty:print( + Term, I, columns(), ?LINEMAX, ?CHAR_MAX, + record_print_fun()) + end, + StackF = fun(_, _, _) -> false end, + lines(Lines, erl_error:format_exception( + 1, Class, Reason, ST, StackF, PrintF)). + +columns() -> + case io:columns() of + {ok, N} -> N; + _-> 80 + end. + +lines(infinity, S) -> S; +lines(N, S) -> + [L1|Ls] = re:split(iolist_to_binary([S]), <<"\n">>, [{return,list}]), + [L1|["\n" ++ L || L <- lists:sublist(Ls, 1, N-1)]]. + +record_print_fun() -> + fun(_,_) -> no end. + +%%% @spec (Attr, Context) -> any() +%%% Attr = module | function | arity | options +%%% +%%% @doc +%%% Accessor function for the Context record. +%%% @end +-spec context(atom(), #context{}) -> + term(). +context(module, #context{module = M} ) -> M; +context(function, #context{function = F}) -> F; +context(arity, #context{arity = A} ) -> A; +context(file, #context{file = F} ) -> F; +context(options, #context{options = O} ) -> O. + + +-spec do_inspect(insp_f(), term(), forms(), #context{}) -> + term(). +do_inspect(F, Acc, Forms, Context) -> + F1 = + fun(Form, Acc0) -> + Type = type(Form), + {Recurse, Acc1} = apply_F(F, Type, Form, Context, Acc0), + if_recurse( + Recurse, Form, _Else = Acc1, + fun(ListOfLists) -> + lists:foldl( + fun(L, AccX) -> + do_inspect( + F, AccX, L, + update_context(Form, Context)) + end, Acc1, ListOfLists) + end) + end, + lists:foldl(F1, Acc, Forms). + +if_recurse(true, Form, Else, F) -> recurse(Form, Else, F); +if_recurse(false, _, Else, _) -> Else. + +recurse(Form, Else, F) -> + case erl_syntax:subtrees(Form) of + [] -> + Else; + [_|_] = ListOfLists -> + F(ListOfLists) + end. + +-spec do_transform(xform_f_rec(), term(), forms(), #context{}) -> + {forms(), term()}. +do_transform(F, Acc, Forms, Context) -> + Rec = fun do_transform/4, % this function + F1 = + fun(Form, Acc0) -> + {Before1, Form1, After1, Recurse, Acc1} = + this_form_rec(F, Form, Context, Acc0), + if Recurse -> + {NewForm, NewAcc} = + enter_subtrees(Form1, F, + update_context(Form1, Context), Acc1, Rec), + {Before1, NewForm, After1, NewAcc}; + true -> + {Before1, Form1, After1, Acc1} + end + end, + mapfoldl(F1, Acc, Forms). + +-spec do_depth_first(xform_f_df(), term(), forms(), #context{}) -> + {forms(), term()}. +do_depth_first(F, Acc, Forms, Context) -> + Rec = fun do_depth_first/4, % this function + F1 = + fun(Form, Acc0) -> + {NewForm, NewAcc} = + enter_subtrees(Form, F, Context, Acc0, Rec), + this_form_df(F, NewForm, Context, NewAcc) + end, + mapfoldl(F1, Acc, Forms). + +enter_subtrees(Form, F, Context, Acc, Recurse) -> + case erl_syntax:subtrees(Form) of + [] -> + {Form, Acc}; + [_|_] = ListOfLists -> + {NewListOfLists, NewAcc} = + mapfoldl( + fun(L, AccX) -> + Recurse(F, AccX, L, Context) + end, Acc, ListOfLists), + NewForm = + erl_syntax:update_tree( + Form, NewListOfLists), + {NewForm, NewAcc} + end. + + +this_form_rec(F, Form, Context, Acc) -> + Type = type(Form), + case apply_F(F, Type, Form, Context, Acc) of + {Form1x, Rec1x, A1x} -> + {[], Form1x, [], Rec1x, A1x}; + {_Be1, _F1, _Af1, _Rec1, _Ac1} = Res1 -> + Res1 + end. +this_form_df(F, Form, Context, Acc) -> + Type = type(Form), + case apply_F(F, Type, Form, Context, Acc) of + {Form1x, A1x} -> + {[], Form1x, [], A1x}; + {_Be1, _F1, _Af1, _Ac1} = Res1 -> + Res1 + end. + +apply_F(F, Type, Form, Context, Acc) -> + try F(Type, Form, Context, Acc) + catch + error:Reason:ST -> + ?ERROR(Reason, + ?HERE, + [{type, Type}, + {context, Context}, + {acc, Acc}, + {apply_f, F}, + {form, Form}, + {stack, ST}], + ST) + end. + + +update_context(Form, Context0) -> + case type(Form) of + function -> + {Fun, Arity} = + erl_syntax_lib:analyze_function(Form), + Context0#context{function = Fun, + arity = Arity}; + _ -> + Context0 + end. + + + + +%%% Slightly modified version of lists:mapfoldl/3 +%%% Here, F/2 is able to insert forms before and after the form +%%% in question. The inserted forms are not transformed afterwards. +mapfoldl(F, Accu0, [Hd|Tail]) -> + {Before, Res, After, Accu1} = + case F(Hd, Accu0) of + {Be, _, Af, _} = Result when is_list(Be), is_list(Af) -> + Result; + {R1, A1} -> + {[], R1, [], A1} + end, + {Rs, Accu2} = mapfoldl(F, Accu1, Tail), + {Before ++ [Res| After ++ Rs], Accu2}; +mapfoldl(F, Accu, []) when is_function(F, 2) -> {[], Accu}. + + +rpt_error(_Reason, _Fun, _Info, _Trace) -> + %% Fmt = lists:flatten( + %% ["*** ERROR in parse_transform function:~n" + %% "*** Reason = ~p~n", + %% "*** Location: ~p~n", + %% "*** Trace: ~p~n", + %% ["*** ~10w = ~p~n" || _ <- Info]]), + %% Args = [Reason, Fun, Trace | + %% lists:foldr( + %% fun({K,V}, Acc) -> + %% [K, V | Acc] + %% end, [], Info)], + %%io:format(Fmt, Args), + ok. + +-spec format_error({atom(), term()}) -> + iolist(). +format_error({E, [{M,F,A}|_]} = Error) -> + try lists:flatten(io_lib:fwrite("~p in ~s:~s/~s", [E, atom_to_list(M), + atom_to_list(F), integer_to_list(A)])) + catch + error:_ -> + format_error_(Error) + end; +format_error(Error) -> + format_error_(Error). + +format_error_(Error) -> + lists:flatten(io_lib:fwrite("~p", [Error])). + + +%% EUnit +-ifdef(TEST). + +format_exeption_test() -> + [_,_,_] = format_exception(error, {error, foo}, 3), + ok. + +-endif. diff --git a/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_codegen.erl b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_codegen.erl new file mode 100644 index 0000000..deb0efa --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_codegen.erl @@ -0,0 +1,431 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -------------------------------------------------- +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% -------------------------------------------------- +%% File : parse_trans_codegen.erl +%% @author : Ulf Wiger +%% @end +%%------------------------------------------------------------------- + +%% @doc Parse transform for code generation pseduo functions +%% +%%

...

+%% +%% @end + +-module(parse_trans_codegen). + +-export([parse_transform/2]). +-export([format_error/1]). + +%% @spec (Forms, Options) -> NewForms +%% +%% @doc +%% Searches for calls to pseudo functions in the module `codegen', +%% and converts the corresponding erlang code to a data structure +%% representing the abstract form of that code. +%% +%% The purpose of these functions is to let the programmer write +%% the actual code that is to be generated, rather than manually +%% writing abstract forms, which is more error prone and cannot be +%% checked by the compiler until the generated module is compiled. +%% +%% Supported functions: +%% +%%

gen_function/2

+%% +%% Usage: `codegen:gen_function(Name, Fun)' +%% +%% Substitutes the abstract code for a function with name `Name' +%% and the same behaviour as `Fun'. +%% +%% `Fun' can either be a anonymous `fun', which is then converted to +%% a named function, or it can be an `implicit fun', e.g. +%% `fun is_member/2'. In the latter case, the referenced function is fetched +%% and converted to an abstract form representation. It is also renamed +%% so that the generated function has the name `Name'. +%%

+%% Another alternative is to wrap a fun inside a list comprehension, e.g. +%%

+%% f(Name, L) ->
+%%     codegen:gen_function(
+%%         Name,
+%%         [ fun({'$var',X}) ->
+%%              {'$var', Y}
+%%           end || {X, Y} &lt;- L ]).
+%% 
+%%

+%% Calling the above with `f(foo, [{1,a},{2,b},{3,c}])' will result in +%% generated code corresponding to: +%%

+%% foo(1) -> a;
+%% foo(2) -> b;
+%% foo(3) -> c.
+%% 
+%% +%%

gen_functions/1

+%% +%% Takes a list of `{Name, Fun}' tuples and produces a list of abstract +%% data objects, just as if one had written +%% `[codegen:gen_function(N1,F1),codegen:gen_function(N2,F2),...]'. +%% +%%

exprs/1

+%% +%% Usage: `codegen:exprs(Fun)' +%% +%% `Fun' is either an anonymous function, or an implicit fun with only one +%% function clause. This "function" takes the body of the fun and produces +%% a data type representing the abstract form of the list of expressions in +%% the body. The arguments of the function clause are ignored, but can be +%% used to ensure that all necessary variables are known to the compiler. +%% +%%

gen_module/3

+%% +%% Generates abstract forms for a complete module definition. +%% +%% Usage: `codegen:gen_module(ModuleName, Exports, Functions)' +%% +%% `ModuleName' is either an atom or a {'$var', V} reference. +%% +%% `Exports' is a list of `{Function, Arity}' tuples. +%% +%% `Functions' is a list of `{Name, Fun}' tuples analogous to that for +%% `gen_functions/1'. +%% +%%

Variable substitution

+%% +%% It is possible to do some limited expansion (importing a value +%% bound at compile-time), using the construct {'$var', V}, where +%% `V' is a bound variable in the scope of the call to `gen_function/2'. +%% +%% Example: +%%
+%% gen(Name, X) ->
+%%    codegen:gen_function(Name, fun(L) -> lists:member({'$var',X}, L) end).
+%% 
+%% +%% After transformation, calling `gen(contains_17, 17)' will yield the +%% abstract form corresponding to: +%%
+%% contains_17(L) ->
+%%    lists:member(17, L).
+%% 
+%% +%%

Form substitution

+%% +%% It is possible to inject abstract forms, using the construct +%% {'$form', F}, where `F' is bound to a parsed form in +%% the scope of the call to `gen_function/2'. +%% +%% Example: +%%
+%% gen(Name, F) ->
+%%    codegen:gen_function(Name, fun(X) -> X =:= {'$form',F} end).
+%% 
+%% +%% After transformation, calling `gen(is_foo, {atom,0,foo})' will yield the +%% abstract form corresponding to: +%%
+%% is_foo(X) ->
+%%    X =:= foo.
+%% 
+%% @end +%% +parse_transform(Forms, Options) -> + Context = parse_trans:initial_context(Forms, Options), + {NewForms, _} = + parse_trans:do_depth_first( + fun xform_fun/4, _Acc = Forms, Forms, Context), + parse_trans:return(parse_trans:revert(NewForms), Context). + +xform_fun(application, Form, _Ctxt, Acc) -> + MFA = erl_syntax_lib:analyze_application(Form), + Anno = erl_syntax:get_pos(Form), + L = erl_anno:line(Anno), + case MFA of + {codegen, {gen_module, 3}} -> + [NameF, ExportsF, FunsF] = + erl_syntax:application_arguments(Form), + NewForms = gen_module(NameF, ExportsF, FunsF, L, Acc), + {NewForms, Acc}; + {codegen, {gen_function, 2}} -> + [NameF, FunF] = + erl_syntax:application_arguments(Form), + NewForm = gen_function(NameF, FunF, L, L, Acc), + {NewForm, Acc}; + {codegen, {gen_function, 3}} -> + [NameF, FunF, LineF] = + erl_syntax:application_arguments(Form), + NewForm = gen_function( + NameF, FunF, L, erl_syntax:integer_value(LineF), Acc), + {NewForm, Acc}; + {codegen, {gen_function_alt, 3}} -> + [NameF, FunF, AltF] = + erl_syntax:application_arguments(Form), + NewForm = gen_function_alt(NameF, FunF, AltF, L, L, Acc), + {NewForm, Acc}; + {codegen, {gen_functions, 1}} -> + [List] = erl_syntax:application_arguments(Form), + Elems = erl_syntax:list_elements(List), + NewForms = lists:map( + fun(E) -> + [NameF, FunF] = erl_syntax:tuple_elements(E), + gen_function(NameF, FunF, L, L, Acc) + end, Elems), + {erl_syntax:list(NewForms), Acc}; + {codegen, {exprs, 1}} -> + [FunF] = erl_syntax:application_arguments(Form), + [Clause] = erl_syntax:fun_expr_clauses(FunF), + [{clause,_,_,_,Body}] = parse_trans:revert([Clause]), + NewForm = substitute(erl_parse:abstract(Body)), + {NewForm, Acc}; + _ -> + {Form, Acc} + end; +xform_fun(_, Form, _Ctxt, Acc) -> + {Form, Acc}. + +gen_module(NameF, ExportsF, FunsF, L, Acc) -> + case erl_syntax:type(FunsF) of + list -> + try gen_module_(NameF, ExportsF, FunsF, L, Acc) + catch + error:E -> + ErrStr = parse_trans:format_exception(error, E), + {error, {L, ?MODULE, ErrStr}} + end; + _ -> + ErrStr = parse_trans:format_exception( + error, "Argument must be a list"), + {error, {L, ?MODULE, ErrStr}} + end. + +gen_module_(NameF, ExportsF, FunsF, L0, Acc) -> + P = erl_syntax:get_pos(NameF), + ModF = case parse_trans:revert_form(NameF) of + {atom,_,_} = Am -> Am; + {tuple,_,[{atom,_,'$var'}, + {var,_,V}]} -> + {var,P,V} + end, + cons( + {cons,P, + {tuple,P, + [{atom,P,attribute}, + {integer,P,1}, + {atom,P,module}, + ModF]}, + substitute( + abstract( + [{attribute,P,export, + lists:map( + fun(TupleF) -> + [F,A] = erl_syntax:tuple_elements(TupleF), + {erl_syntax:atom_value(F), erl_syntax:integer_value(A)} + end, erl_syntax:list_elements(ExportsF))}]))}, + lists:map( + fun(FTupleF) -> + Pos = erl_syntax:get_pos(FTupleF), + [FName, FFunF] = erl_syntax:tuple_elements(FTupleF), + gen_function(FName, FFunF, L0, Pos, Acc) + end, erl_syntax:list_elements(FunsF))). + +cons({cons,L,H,T}, L2) -> + {cons,L,H,cons(T, L2)}; +cons({nil,L}, [H|T]) -> + Pos = erl_syntax:get_pos(H), + {cons,L,H,cons({nil,Pos}, T)}; +cons({nil,L}, []) -> + {nil,L}. + + + +gen_function(NameF, FunF, L0, L, Acc) -> + try gen_function_(NameF, FunF, [], L, Acc) + catch + error:E -> + ErrStr = parse_trans:format_exception(error, E), + {error, {L0, ?MODULE, ErrStr}} + end. + +gen_function_alt(NameF, FunF, AltF, L0, L, Acc) -> + try gen_function_(NameF, FunF, AltF, L, Acc) + catch + error:E -> + ErrStr = parse_trans:format_exception(error, E), + {error, {L0, ?MODULE, ErrStr}} + end. + +gen_function_(NameF, FunF, AltF, L, Acc) -> + case erl_syntax:type(FunF) of + T when T==implicit_fun; T==fun_expr -> + {Arity, Clauses} = gen_function_clauses(T, NameF, FunF, L, Acc), + A1 = erl_anno:new(1), + {tuple, A1, [{atom, A1, function}, + {integer, A1, L}, + NameF, + {integer, A1, Arity}, + substitute(abstract(Clauses))]}; + list_comp -> + %% Extract the fun from the LC + [Template] = parse_trans:revert( + [erl_syntax:list_comp_template(FunF)]), + %% Process fun in the normal fashion (as above) + {Arity, Clauses} = gen_function_clauses(erl_syntax:type(Template), + NameF, Template, L, Acc), + Body = erl_syntax:list_comp_body(FunF), + %% Collect all variables from the LC generator(s) + %% We want to produce an abstract representation of something like: + %% {function,1,Name,Arity, + %% lists:flatten( + %% [(fun(V1,V2,...) -> + %% ... + %% end)(__V1,__V2,...) || {__V1,__V2,...} <- L])} + %% where the __Vn vars are our renamed versions of the LC generator + %% vars. This allows us to instantiate the clauses at run-time. + Vars = lists:flatten( + [sets:to_list(erl_syntax_lib:variables( + erl_syntax:generator_pattern(G))) + || G <- Body]), + Vars1 = [list_to_atom("__" ++ atom_to_list(V)) || V <- Vars], + VarMap = lists:zip(Vars, Vars1), + Body1 = + [erl_syntax:generator( + rename_vars(VarMap, gen_pattern(G)), + gen_body(G)) || G <- Body], + A1 = erl_anno:new(1), + [RevLC] = parse_trans:revert( + [erl_syntax:list_comp( + {call, A1, + {'fun',A1, + {clauses, + [{clause,A1,[{var,A1,V} || V <- Vars],[], + [substitute( + abstract(Clauses))] + }]} + }, [{var,A1,V} || V <- Vars1]}, Body1)]), + AltC = case AltF of + [] -> {nil,A1}; + _ -> + {Arity, AltC1} = gen_function_clauses( + erl_syntax:type(AltF), + NameF, AltF, L, Acc), + substitute(abstract(AltC1)) + end, + {tuple,A1,[{atom,A1,function}, + {integer, A1, L}, + NameF, + {integer, A1, Arity}, + {call, A1, {remote, A1, {atom, A1, lists}, + {atom,A1,flatten}}, + [{op, A1, '++', RevLC, AltC}]}]} + end. + +gen_pattern(G) -> + erl_syntax:generator_pattern(G). + +gen_body(G) -> + erl_syntax:generator_body(G). + +rename_vars(Vars, Tree) -> + erl_syntax_lib:map( + fun(T) -> + case erl_syntax:type(T) of + variable -> + V = erl_syntax:variable_name(T), + {_,V1} = lists:keyfind(V,1,Vars), + erl_syntax:variable(V1); + _ -> + T + end + end, Tree). + +gen_function_clauses(implicit_fun, _NameF, FunF, _L, Acc) -> + AQ = erl_syntax:implicit_fun_name(FunF), + Name = erl_syntax:atom_value(erl_syntax:arity_qualifier_body(AQ)), + Arity = erl_syntax:integer_value( + erl_syntax:arity_qualifier_argument(AQ)), + NewForm = find_function(Name, Arity, Acc), + ClauseForms = erl_syntax:function_clauses(NewForm), + {Arity, ClauseForms}; +gen_function_clauses(fun_expr, _NameF, FunF, _L, _Acc) -> + ClauseForms = erl_syntax:fun_expr_clauses(FunF), + Arity = get_arity(ClauseForms), + {Arity, ClauseForms}. + +find_function(Name, Arity, Forms) -> + [Form] = [F || {function,_,N,A,_} = F <- Forms, + N == Name, + A == Arity], + Form. + +abstract(ClauseForms) -> + erl_parse:abstract(parse_trans:revert(ClauseForms)). + +substitute({tuple,L0, + [{atom,_,tuple}, + Loc, + {cons,_, + {tuple,_,[{atom,_,atom},_LocA,{atom,_,'$var'}]}, + {cons,_, + {tuple,_,[{atom,_,var},_LocB,{atom,_,V}]}, + {nil,_}}}]}) when element(1, Loc) == tuple; % Erlang 24+ + element(1, Loc) == integer -> + AbstConcreteLoc = + case Loc of + {integer, _, L} -> + {integer, L0, L}; + {tuple, _, [{integer, _, L}, {integer, _, C}]} -> + {tuple, L0, [{integer, L0, L}, {integer, L0, C}]} + end, + {call, L0, {remote,L0,{atom,L0,erl_parse}, + {atom,L0,abstract}}, + [{var, L0, V}, AbstConcreteLoc]}; +substitute({tuple,L0, % Erlang 24: {Line,Col} + [{atom,_,tuple}, + Loc, + {cons,_, + {tuple,_,[{atom,_,atom},_LocA,{atom,_,'$form'}]}, + {cons,_, + {tuple,_,[{atom,_,var},_LocB,{atom,_,F}]}, + {nil,_}}}]}) when element(1, Loc) == tuple; % Erlang 24+ + element(1, Loc) == integer -> + {var, L0, F}; +substitute([]) -> + []; +substitute([H|T]) -> + [substitute(H) | substitute(T)]; +substitute(T) when is_tuple(T) -> + list_to_tuple(substitute(tuple_to_list(T))); +substitute(X) -> + X. + +get_arity(Clauses) -> + Ays = [length(erl_syntax:clause_patterns(C)) || C <- Clauses], + case lists:usort(Ays) of + [Ay] -> + Ay; + Other -> + erlang:error(ambiguous, Other) + end. + + +format_error(E) -> + case io_lib:deep_char_list(E) of + true -> + E; + _ -> + io_lib:write(E) + end. diff --git a/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_mod.erl b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_mod.erl new file mode 100644 index 0000000..1eb8c64 --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_mod.erl @@ -0,0 +1,128 @@ +%%============================================================================ +%% Copyright 2014 Ulf Wiger +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%============================================================================ +%% +%% Based on meck_mod.erl from http://github.com/eproxus/meck.git +%% Original author: Adam Lindberg +%% +-module(parse_trans_mod). +%% Interface exports +-export([transform_module/3]). + +-export([abstract_code/1]). +-export([beam_file/1]). +-export([compile_and_load_forms/1]). +-export([compile_and_load_forms/2]). +-export([compile_options/1]). +-export([rename_module/2]). + +%% Types +-type erlang_form() :: term(). +-type compile_options() :: [term()]. + +%%============================================================================ +%% Interface exports +%%============================================================================ + +transform_module(Mod, PT, Options) -> + File = beam_file(Mod), + Forms = abstract_code(File), + Context = parse_trans:initial_context(Forms, Options), + PTMods = if is_atom(PT) -> [PT]; + is_function(PT, 2) -> [PT]; + is_list(PT) -> PT + end, + Transformed = lists:foldl(fun(PTx, Fs) when is_function(PTx, 2) -> + PTx(Fs, Options); + (PTMod, Fs) -> + PTMod:parse_transform(Fs, Options) + end, Forms, PTMods), + parse_trans:optionally_pretty_print(Transformed, Options, Context), + compile_and_load_forms(Transformed, get_compile_options(Options)). + + +-spec abstract_code(binary()) -> erlang_form(). +abstract_code(BeamFile) -> + case beam_lib:chunks(BeamFile, [abstract_code]) of + {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> + Forms; + {ok, {_, [{abstract_code, no_abstract_code}]}} -> + erlang:error(no_abstract_code) + end. + +-spec beam_file(module()) -> binary(). +beam_file(Module) -> + % code:which/1 cannot be used for cover_compiled modules + case code:get_object_code(Module) of + {_, Binary, _Filename} -> Binary; + error -> throw({object_code_not_found, Module}) + end. + +-spec compile_and_load_forms(erlang_form()) -> ok. +compile_and_load_forms(AbsCode) -> compile_and_load_forms(AbsCode, []). + +-spec compile_and_load_forms(erlang_form(), compile_options()) -> ok. +compile_and_load_forms(AbsCode, Opts) -> + case compile:forms(AbsCode, Opts) of + {ok, ModName, Binary} -> + load_binary(ModName, Binary, Opts); + {ok, ModName, Binary, _Warnings} -> + load_binary(ModName, Binary, Opts) + end. + +-spec compile_options(binary() | module()) -> compile_options(). +compile_options(BeamFile) when is_binary(BeamFile) -> + case beam_lib:chunks(BeamFile, [compile_info]) of + {ok, {_, [{compile_info, Info}]}} -> + proplists:get_value(options, Info); + _ -> + [] + end; +compile_options(Module) -> + proplists:get_value(options, Module:module_info(compile)). + +-spec rename_module(erlang_form(), module()) -> erlang_form(). +rename_module([{attribute, Line, module, _OldName}|T], NewName) -> + [{attribute, Line, module, NewName}|T]; +rename_module([H|T], NewName) -> + [H|rename_module(T, NewName)]. + +%%============================================================================== +%% Internal functions +%%============================================================================== + +load_binary(Name, Binary, Opts) -> + code:purge(Name), + File = beam_filename(Name, Opts), + case code:load_binary(Name, File, Binary) of + {module, Name} -> ok; + {error, Reason} -> exit({error_loading_module, Name, Reason}) + end. + +get_compile_options(Options) -> + case lists:keyfind(compile_options, 1, Options) of + {_, COpts} -> + COpts; + false -> + [] + end. + +beam_filename(Mod, Opts) -> + case lists:keyfind(outdir, 1, Opts) of + {_, D} -> + filename:join(D, atom_to_list(Mod) ++ code:objfile_extension()); + false -> + "" + end. diff --git a/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_pp.erl b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_pp.erl new file mode 100644 index 0000000..7478ad2 --- /dev/null +++ b/ptrans_deporder/_checkouts/parse_trans/src/parse_trans_pp.erl @@ -0,0 +1,118 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -------------------------------------------------- +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% -------------------------------------------------- +%% File : parse_trans_pp.erl +%% @author : Ulf Wiger +%% @end +%% Description : +%% +%% Created : 3 Aug 2010 by Ulf Wiger +%% -------------------------------------------------- + +%% @doc Generic parse transform library for Erlang. +%% +%% This module contains some useful utility functions for inspecting +%% the results of parse transforms or code generation. +%% The function `main/1' is called from escript, and can be used to +%% pretty-print debug info in a .beam file from a Linux shell. +%% +%% Using e.g. the following bash alias: +%%
+%% alias pp='escript $PARSE_TRANS_ROOT/ebin/parse_trans_pp.beam'
+%%% 
+%% a file could be pretty-printed using the following command: +%% +%% `$ pp ex_codegen.beam | less' +%% @end + +-module(parse_trans_pp). + +-export([ + pp_src/2, + pp_beam/1, pp_beam/2 + ]). + +-export([main/1]). + + +-spec main([string()]) -> any(). +main([F]) -> + pp_beam(F). + + +%% @spec (Forms, Out::filename()) -> ok +%% +%% @doc Pretty-prints the erlang source code corresponding to Forms into Out +%% +-spec pp_src(parse_trans:forms(), file:filename()) -> + ok. +pp_src(Forms0, F) -> + Forms = epp:restore_typed_record_fields(revert(Forms0)), + Str = [io_lib:fwrite("~s~n", + [lists:flatten([erl_pp:form(Fm) || + Fm <- Forms])])], + file:write_file(F, list_to_binary(Str)). + +%% @spec (Beam::filename()) -> string() | {error, Reason} +%% +%% @doc +%% Reads debug_info from the beam file Beam and returns a string containing +%% the pretty-printed corresponding erlang source code. +%% @end +-spec pp_beam(file:filename()) -> ok | {error, any()}. +pp_beam(Beam) -> + case pp_beam_to_str(Beam) of + {ok, Str} -> + io:put_chars(Str); + Other -> + Other + end. + +%% @spec (Beam::filename(), Out::filename()) -> ok | {error, Reason} +%% +%% @doc +%% Reads debug_info from the beam file Beam and pretty-prints it as +%% Erlang source code, storing it in the file Out. +%% @end +%% +-spec pp_beam(file:filename(), file:filename()) -> ok | {error,any()}. +pp_beam(F, Out) -> + case pp_beam_to_str(F) of + {ok, Str} -> + file:write_file(Out, list_to_binary(Str)); + Other -> + Other + end. + +pp_beam_to_str(F) -> + case beam_lib:chunks(F, [abstract_code]) of + {ok, {_, [{abstract_code,{_,AC0}}]}} -> + AC = epp:restore_typed_record_fields(AC0), + {ok, lists:flatten( + %% io_lib:fwrite("~s~n", [erl_prettypr:format( + %% erl_syntax:form_list(AC))]) + io_lib:fwrite("~s~n", [lists:flatten( + [erl_pp:form(Form) || + Form <- AC])]) + )}; + Other -> + {error, Other} + end. + +-spec revert(parse_trans:forms()) -> + parse_trans:forms(). +revert(Tree) -> + [erl_syntax:revert(T) || T <- lists:flatten(Tree)]. diff --git a/ptrans_deporder/_checkouts/z_dep/.gitignore b/ptrans_deporder/_checkouts/z_dep/.gitignore new file mode 100644 index 0000000..f1c4554 --- /dev/null +++ b/ptrans_deporder/_checkouts/z_dep/.gitignore @@ -0,0 +1,19 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ diff --git a/ptrans_deporder/_checkouts/z_dep/LICENSE b/ptrans_deporder/_checkouts/z_dep/LICENSE new file mode 100644 index 0000000..f0412c7 --- /dev/null +++ b/ptrans_deporder/_checkouts/z_dep/LICENSE @@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021, Fred Hebert . + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/ptrans_deporder/_checkouts/z_dep/README.md b/ptrans_deporder/_checkouts/z_dep/README.md new file mode 100644 index 0000000..ac2c27d --- /dev/null +++ b/ptrans_deporder/_checkouts/z_dep/README.md @@ -0,0 +1,9 @@ +top_dep +===== + +An OTP library + +Build +----- + + $ rebar3 compile diff --git a/ptrans_deporder/_checkouts/z_dep/rebar.config b/ptrans_deporder/_checkouts/z_dep/rebar.config new file mode 100644 index 0000000..0a2fc62 --- /dev/null +++ b/ptrans_deporder/_checkouts/z_dep/rebar.config @@ -0,0 +1,4 @@ +{erl_opts, [debug_info]}. +{deps, [ + b_dep +]}. diff --git a/ptrans_deporder/_checkouts/z_dep/src/z_dep.app.src b/ptrans_deporder/_checkouts/z_dep/src/z_dep.app.src new file mode 100644 index 0000000..ba84472 --- /dev/null +++ b/ptrans_deporder/_checkouts/z_dep/src/z_dep.app.src @@ -0,0 +1,14 @@ +{application, z_dep, + [{description, "An OTP library"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/ptrans_deporder/_checkouts/z_dep/src/z_dep.erl b/ptrans_deporder/_checkouts/z_dep/src/z_dep.erl new file mode 100644 index 0000000..f06a06e --- /dev/null +++ b/ptrans_deporder/_checkouts/z_dep/src/z_dep.erl @@ -0,0 +1,5 @@ +-module(z_dep). + +-compile([{parse_transform, b_dep}]). + + diff --git a/ptrans_deporder/ptrans_deporder.test b/ptrans_deporder/ptrans_deporder.test new file mode 100644 index 0000000..ac401fe --- /dev/null +++ b/ptrans_deporder/ptrans_deporder.test @@ -0,0 +1,11 @@ +TERM=dumb rebar3 compile +>>>= 0 + +ls _build/default/checkouts/a_dep/ebin/a_dep.beam +>>>= 0 + +ls _build/default/checkouts/b_dep/ebin/b_dep.beam +>>>= 0 + +ls _build/default/checkouts/z_dep/ebin/z_dep.beam +>>>= 0 diff --git a/ptrans_deporder/rebar.config b/ptrans_deporder/rebar.config new file mode 100644 index 0000000..6a88d60 --- /dev/null +++ b/ptrans_deporder/rebar.config @@ -0,0 +1,6 @@ +{erl_opts, [debug_info]}. +{deps, [ +  exometer_fetch, % chosen because exometer_core is depended on, which in turns requires parse_trans + a_dep, % in checkouts, transitively depends on parse_trans via b_dep + z_dep % try later sorting order. +]}. diff --git a/ptrans_deporder/rebar.lock b/ptrans_deporder/rebar.lock new file mode 100644 index 0000000..a048766 --- /dev/null +++ b/ptrans_deporder/rebar.lock @@ -0,0 +1,23 @@ +{"1.2.0", +[{<<"bear">>,{pkg,<<"bear">>,<<"0.8.5">>},3}, + {<<"folsom">>,{pkg,<<"folsom">>,<<"0.8.5">>},2}, + {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},3}, + {<<"hut">>,{pkg,<<"hut">>,<<"1.2.0">>},2}, + {<<"lager">>,{pkg,<<"lager">>,<<"3.5.1">>},2}, + {<<"setup">>,{pkg,<<"setup">>,<<"1.8.2">>},2}]}. +[ +{pkg_hash,[ + {<<"bear">>, <<"E95FCA1627CD9E15BAF93CE0A52AFF16917BAF325F0EE65B88CD715376CD2344">>}, + {<<"folsom">>, <<"94A027B56FE84FEED264F9B33CB4C6AC9A801FAD84B87DBDA0836CE83C3B8D69">>}, + {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, + {<<"hut">>, <<"0089DF0FAA2827C605BBADA88153F24FFF5EA7A4BE32ECF0250A7FDC2719CAFB">>}, + {<<"lager">>, <<"63897A61AF646C59BB928FEE9756CE8BDD02D5A1A2F3551D4A5E38386C2CC071">>}, + {<<"setup">>, <<"14E66C480EA51D938527247C1D92C3EA5298826CF7FA7769C936BAD29E2C040E">>}]}, +{pkg_hash_ext,[ + {<<"bear">>, <<"A2FFA70B82C3002DF30D01CB78B9B03D7524CD23008A0264962DE62C97F7AED4">>}, + {<<"folsom">>, <<"2381E94DF3D1FBC48281ABFD433EB14C8E4A5F62CA07DB5D87BB7A7204C9B1C9">>}, + {<<"goldrush">>, <<"99CB4128CFFCB3227581E5D4D803D5413FA643F4EB96523F77D9E6937D994CEB">>}, + {<<"hut">>, <<"097BA947258873DFB4C7697B44677F3AC87DAC76AD4A30C0440AFEFBC18EB233">>}, + {<<"lager">>, <<"D4B4324B9D07F4CF71A59B6E0F43E2D2651778D92AA5BEF68D5A8CDF6D213BA1">>}, + {<<"setup">>, <<"F003468CB80057F9D05098A0CB2E2448EA97FA229EF5E72D477DEFA134059CD7">>}]} +]. diff --git a/ptrans_deporder/src/ptrans_deporder.app.src b/ptrans_deporder/src/ptrans_deporder.app.src new file mode 100644 index 0000000..4849799 --- /dev/null +++ b/ptrans_deporder/src/ptrans_deporder.app.src @@ -0,0 +1,14 @@ +{application, ptrans_deporder, + [{description, "An OTP library"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/ptrans_deporder/src/ptrans_deporder.erl b/ptrans_deporder/src/ptrans_deporder.erl new file mode 100644 index 0000000..255ceba --- /dev/null +++ b/ptrans_deporder/src/ptrans_deporder.erl @@ -0,0 +1,5 @@ +-module(ptrans_deporder). +-export([main/0]). + +main() -> + ok.