diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..07fc221d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*/vendor diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 515976a5..00000000 --- a/LICENSE.md +++ /dev/null @@ -1,664 +0,0 @@ -Tendermint NetMon -Copyright (C) 2016 Tendermint - - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/README.md b/README.md deleted file mode 100644 index af763617..00000000 --- a/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# netmon -blockchain network monitor - - -#Quick Start - -To get started, [install golang](https://golang.org/doc/install) and [set your $GOPATH](https://github.com/tendermint/tendermint/wiki/Setting-GOPATH). - -Install `tendermint`, `tmsp`, and the `netmon`: - -``` -go get github.com/tendermint/tendermint/cmd/tendermint -go get github.com/tendermint/tmsp/cmd/... -go get github.com/tendermint/netmon -``` - -Initialize and start a local tendermint node with - -``` -tendermint init -dummy & -tendermint node --fast_sync=false --log_level=debug -``` - -In another window, start the netmon with - -``` -netmon monitor $GOPATH/src/github.com/tendermint/netmon/local-chain.json -``` - -Then visit your browser at http://localhost:46670. - -The chain's rpc can be found at http://localhost:46657. - -# Notes - -The netmon expects a config file with a list of chains/validators to get started. A default one for a local chain is provided as local-chain.json. `netmon config` can be used to create a config file for a chain deployed with `mintnet`. Configs are also generated by mintnet. - -The API is available as GET requests with URI encoded parameters, or as JSONRPC POST requests. The JSONRPC methods are also exposed over websocket. - -# TODO - -- log metrics for charts -- mintnet rpc commands -- chain size -- val set changes -- more efficient locking / refactor for a big select loop diff --git a/handlers/callbacks.go b/handlers/callbacks.go deleted file mode 100644 index 027769db..00000000 --- a/handlers/callbacks.go +++ /dev/null @@ -1,78 +0,0 @@ -package handlers - -import ( - "time" - - "github.com/tendermint/go-event-meter" - "github.com/tendermint/go-events" - - "github.com/tendermint/netmon/types" - - tmtypes "github.com/tendermint/tendermint/types" -) - -/* - Each chain-validator gets an eventmeter which maintains the websocket - Certain pre-defined events may update the netmon state: latency pongs, new blocks - All callbacks are called in a go-routine by the event-meter - TODO: config changes for new validators and changing ip/port -*/ - -func (tn *TendermintNetwork) registerCallbacks(chainState *types.ChainState, v *types.ValidatorState) error { - v.EventMeter().RegisterLatencyCallback(tn.latencyCallback(chainState, v)) - v.EventMeter().RegisterDisconnectCallback(tn.disconnectCallback(chainState, v)) - return v.EventMeter().Subscribe(tmtypes.EventStringNewBlockHeader(), tn.newBlockCallback(chainState, v)) -} - -// implements eventmeter.EventCallbackFunc -// updates validator and possibly chain with new block -func (tn *TendermintNetwork) newBlockCallback(chainState *types.ChainState, val *types.ValidatorState) eventmeter.EventCallbackFunc { - return func(metric *eventmeter.EventMetric, data events.EventData) { - block := data.(tmtypes.EventDataNewBlockHeader).Header - - // these functions are thread safe - // we should run them concurrently - - // update height for validator - val.NewBlock(block) - - // possibly update height and mean block time for chain - chainState.NewBlock(block) - } -} - -// implements eventmeter.EventLatencyFunc -func (tn *TendermintNetwork) latencyCallback(chain *types.ChainState, val *types.ValidatorState) eventmeter.LatencyCallbackFunc { - return func(latency float64) { - latency = latency / 1000000.0 // ns to ms - oldLatency := val.UpdateLatency(latency) - chain.UpdateLatency(oldLatency, latency) - } -} - -// implements eventmeter.DisconnectCallbackFunc -func (tn *TendermintNetwork) disconnectCallback(chain *types.ChainState, val *types.ValidatorState) eventmeter.DisconnectCallbackFunc { - return func() { - // Validator is down! - chain.SetOnline(val, false) - - // reconnect - // TODO: stop trying eventually ... - for { - time.Sleep(time.Second) - - if err := val.Start(); err != nil { - log.Debug("Can't connect to validator", "valID", val.Config.Validator.ID) - } else { - // register callbacks for the validator - tn.registerCallbacks(chain, val) - - chain.SetOnline(val, true) - - // TODO: authenticate pubkey - - return - } - } - } -} diff --git a/handlers/handlers.go b/handlers/handlers.go deleted file mode 100644 index 2f0785ad..00000000 --- a/handlers/handlers.go +++ /dev/null @@ -1,254 +0,0 @@ -package handlers - -import ( - "fmt" - "sort" - "sync" - "time" - - "github.com/tendermint/go-event-meter" - "github.com/tendermint/go-wire" - - "github.com/tendermint/netmon/types" -) - -type NetMonResult interface { -} - -// for wire.readReflect -var _ = wire.RegisterInterface( - struct{ NetMonResult }{}, - wire.ConcreteType{&types.ChainAndValidatorSetIDs{}, 0x01}, - wire.ConcreteType{&types.ChainState{}, 0x02}, - wire.ConcreteType{&types.ValidatorSet{}, 0x10}, - wire.ConcreteType{&types.Validator{}, 0x11}, - wire.ConcreteType{&types.ValidatorConfig{}, 0x12}, - wire.ConcreteType{&eventmeter.EventMetric{}, 0x20}, -) - -//--------------------------------------------- -// global state and backend functions - -// TODO: relax the locking (use RWMutex, reduce scope) -type TendermintNetwork struct { - mtx sync.Mutex - Chains map[string]*types.ChainState `json:"blockchains"` - ValSets map[string]*types.ValidatorSet `json:"validator_sets"` -} - -func NewTendermintNetwork() *TendermintNetwork { - network := &TendermintNetwork{ - Chains: make(map[string]*types.ChainState), - ValSets: make(map[string]*types.ValidatorSet), - } - return network -} - -//------------ -// Public Methods - -func (tn *TendermintNetwork) Stop() { - tn.mtx.Lock() - defer tn.mtx.Unlock() - wg := new(sync.WaitGroup) - for _, c := range tn.Chains { - for _, v := range c.Config.Validators { - wg.Add(1) - go func(val *types.ValidatorState) { - val.Stop() - wg.Done() - }(v) - } - } - wg.Wait() -} - -//----------------------------------------------------------- -// RPC funcs -//----------------------------------------------------------- - -//------------------ -// Status - -// Returns sorted lists of all chains and validator sets -func (tn *TendermintNetwork) Status() (*types.ChainAndValidatorSetIDs, error) { - tn.mtx.Lock() - defer tn.mtx.Unlock() - chains := make([]string, len(tn.Chains)) - valSets := make([]string, len(tn.ValSets)) - i := 0 - for chain, _ := range tn.Chains { - chains[i] = chain - i += 1 - } - i = 0 - for valset, _ := range tn.ValSets { - valSets[i] = valset - i += 1 - } - sort.StringSlice(chains).Sort() - sort.StringSlice(valSets).Sort() - return &types.ChainAndValidatorSetIDs{ - ChainIDs: chains, - ValidatorSetIDs: valSets, - }, nil - -} - -// NOTE: returned values should not be manipulated by callers as they are pointers to the state! -//------------------ -// Blockchains - -// Get the current state of a chain -func (tn *TendermintNetwork) GetChain(chainID string) (*types.ChainState, error) { - tn.mtx.Lock() - defer tn.mtx.Unlock() - chain, ok := tn.Chains[chainID] - if !ok { - return nil, fmt.Errorf("Unknown chain %s", chainID) - } - chain.Status.RealTimeUpdates() - return chain, nil -} - -// Register a new chain on the network. -// For each validator, start a websocket connection to listen for new block events and record latency -func (tn *TendermintNetwork) RegisterChain(chainConfig *types.BlockchainConfig) (*types.ChainState, error) { - // Don't bother locking until we touch the TendermintNetwork object - - chainState := &types.ChainState{ - Config: chainConfig, - Status: types.NewBlockchainStatus(), - } - chainState.Status.NumValidators = len(chainConfig.Validators) - - // so we can easily lookup validators by id rather than index - chainState.Config.PopulateValIDMap() - - // start the event meter and listen for new blocks on each validator - for _, v := range chainConfig.Validators { - v.Status = &types.ValidatorStatus{} - - var err error - RETRYLOOP: - for i := 0; i < 10; i++ { - if err = v.Start(); err == nil { - break RETRYLOOP - } - time.Sleep(time.Second) - } - if err != nil { - return nil, fmt.Errorf("Error starting validator %s: %v", v.Config.Validator.ID, err) - } - - // register callbacks for the validator - tn.registerCallbacks(chainState, v) - - // the DisconnectCallback will set us offline and start a reconnect routine - chainState.Status.SetOnline(v, true) - - // get/set the validator's pub key - // TODO: make this authenticate... - v.PubKey() - } - - tn.mtx.Lock() - defer tn.mtx.Unlock() - tn.Chains[chainState.Config.ID] = chainState - return chainState, nil -} - -//------------------ -// Validators - -func (tn *TendermintNetwork) GetValidatorSet(valSetID string) (*types.ValidatorSet, error) { - tn.mtx.Lock() - defer tn.mtx.Unlock() - valSet, ok := tn.ValSets[valSetID] - if !ok { - return nil, fmt.Errorf("Unknown validator set %s", valSetID) - } - return valSet, nil -} - -func (tn *TendermintNetwork) RegisterValidatorSet(valSet *types.ValidatorSet) (*types.ValidatorSet, error) { - tn.mtx.Lock() - defer tn.mtx.Unlock() - tn.ValSets[valSet.ID] = valSet - return valSet, nil -} - -func (tn *TendermintNetwork) GetValidator(valSetID, valID string) (*types.Validator, error) { - tn.mtx.Lock() - defer tn.mtx.Unlock() - valSet, ok := tn.ValSets[valSetID] - if !ok { - return nil, fmt.Errorf("Unknown validator set %s", valSetID) - } - val, err := valSet.Validator(valID) - if err != nil { - return nil, err - } - return val, nil -} - -// Update the validator's rpc address (for now its the only thing that can be updated!) -func (tn *TendermintNetwork) UpdateValidator(chainID, valID, rpcAddr string) (*types.ValidatorConfig, error) { - tn.mtx.Lock() - defer tn.mtx.Unlock() - val, err := tn.getChainVal(chainID, valID) - if err != nil { - return nil, err - } - - val.Config.UpdateRPCAddress(rpcAddr) - log.Debug("Update validator rpc address", "chain", chainID, "val", valID, "rpcAddr", rpcAddr) - return val.Config, nil -} - -//------------------ -// Event metering - -func (tn *TendermintNetwork) StartMeter(chainID, valID, eventID string) error { - tn.mtx.Lock() - defer tn.mtx.Unlock() - val, err := tn.getChainVal(chainID, valID) - if err != nil { - return err - } - return val.EventMeter().Subscribe(eventID, nil) -} - -func (tn *TendermintNetwork) StopMeter(chainID, valID, eventID string) error { - tn.mtx.Lock() - defer tn.mtx.Unlock() - val, err := tn.getChainVal(chainID, valID) - if err != nil { - return err - } - return val.EventMeter().Unsubscribe(eventID) -} - -func (tn *TendermintNetwork) GetMeter(chainID, valID, eventID string) (*eventmeter.EventMetric, error) { - tn.mtx.Lock() - defer tn.mtx.Unlock() - val, err := tn.getChainVal(chainID, valID) - if err != nil { - return nil, err - } - - return val.EventMeter().GetMetric(eventID) -} - -// assumes lock is held -func (tn *TendermintNetwork) getChainVal(chainID, valID string) (*types.ValidatorState, error) { - chain, ok := tn.Chains[chainID] - if !ok { - return nil, fmt.Errorf("Unknown chain %s", chainID) - } - val, err := chain.Config.GetValidatorByID(valID) - if err != nil { - return nil, err - } - return val, nil -} diff --git a/handlers/log.go b/handlers/log.go deleted file mode 100644 index a2ee08df..00000000 --- a/handlers/log.go +++ /dev/null @@ -1,18 +0,0 @@ -package handlers - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "handlers") - -/* -func init() { - log.SetHandler( - logger.LvlFilterHandler( - logger.LvlDebug, - logger.BypassHandler(), - ), - ) -} -*/ diff --git a/handlers/routes.go b/handlers/routes.go deleted file mode 100644 index 5de29a43..00000000 --- a/handlers/routes.go +++ /dev/null @@ -1,74 +0,0 @@ -package handlers - -import ( - rpc "github.com/tendermint/go-rpc/server" - "github.com/tendermint/netmon/types" -) - -func Routes(network *TendermintNetwork) map[string]*rpc.RPCFunc { - return map[string]*rpc.RPCFunc{ - // subscribe/unsubscribe are reserved for websocket events. - // "subscribe": rpc.NewWSRPCFunc(Subscribe, []string{"event"}), - // "unsubscribe": rpc.NewWSRPCFunc(Unsubscribe, []string{"event"}), - - "status": rpc.NewRPCFunc(StatusResult(network), ""), - "get_chain": rpc.NewRPCFunc(GetChainResult(network), "chainID"), - "register_chain": rpc.NewRPCFunc(RegisterChainResult(network), "chainConfig"), - "validator_set": rpc.NewRPCFunc(GetValidatorSetResult(network), "valsetID"), - "register_validator_set": rpc.NewRPCFunc(RegisterValidatorSetResult(network), "valSetID"), - "validator": rpc.NewRPCFunc(GetValidatorResult(network), "valSetID,valID"), - "update_validator": rpc.NewRPCFunc(UpdateValidatorResult(network), "chainID,valID,rpcAddr"), - - "start_meter": rpc.NewRPCFunc(network.StartMeter, "chainID,valID,event"), - "stop_meter": rpc.NewRPCFunc(network.StopMeter, "chainID,valID,event"), - "meter": rpc.NewRPCFunc(GetMeterResult(network), "chainID,valID,event"), - } -} - -func StatusResult(network *TendermintNetwork) interface{} { - return func() (NetMonResult, error) { - return network.Status() - } -} - -func GetChainResult(network *TendermintNetwork) interface{} { - return func(chain string) (NetMonResult, error) { - return network.GetChain(chain) - } -} - -func RegisterChainResult(network *TendermintNetwork) interface{} { - return func(chainConfig *types.BlockchainConfig) (NetMonResult, error) { - return network.RegisterChain(chainConfig) - } -} - -func GetValidatorSetResult(network *TendermintNetwork) interface{} { - return func(valSetID string) (NetMonResult, error) { - return network.GetValidatorSet(valSetID) - } -} - -func RegisterValidatorSetResult(network *TendermintNetwork) interface{} { - return func(valSet *types.ValidatorSet) (NetMonResult, error) { - return network.RegisterValidatorSet(valSet) - } -} - -func GetValidatorResult(network *TendermintNetwork) interface{} { - return func(valSetID, valID string) (NetMonResult, error) { - return network.GetValidator(valSetID, valID) - } -} - -func UpdateValidatorResult(network *TendermintNetwork) interface{} { - return func(chainID, valID, rpcAddr string) (NetMonResult, error) { - return network.UpdateValidator(chainID, valID, rpcAddr) - } -} - -func GetMeterResult(network *TendermintNetwork) interface{} { - return func(chainID, valID, eventID string) (NetMonResult, error) { - return network.GetMeter(chainID, valID, eventID) - } -} diff --git a/local-chain.json b/local-chain.json deleted file mode 100644 index b96bc91b..00000000 --- a/local-chain.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "validator_sets": [{ - "id":"local-vals", - "validators": [{ - "id": "local", - "chains": ["mychain"] - }] - }], - "blockchains": [{ - "id": "mychain", - "val_set_id": "local-vals", - "validators": [{ - "config":{ - "validator": { - "id": "local" - }, - "rpc_addr": "localhost:46657", - "index": 0 - } - }] - }] -} diff --git a/log.go b/log.go deleted file mode 100644 index b0872178..00000000 --- a/log.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "netmon") - -/* -func init() { - log.SetHandler( - logger.LvlFilterHandler( - logger.LvlDebug, - logger.BypassHandler(), - ), - ) -} -*/ diff --git a/main.go b/main.go deleted file mode 100644 index c85a830f..00000000 --- a/main.go +++ /dev/null @@ -1,397 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "path" - "strconv" - "strings" - - "github.com/tendermint/netmon/handlers" - "github.com/tendermint/netmon/types" - - "github.com/codegangsta/cli" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-logger" - pcm "github.com/tendermint/go-process" - "github.com/tendermint/go-rpc/server" - "github.com/tendermint/go-wire" -) - -func init() { - logger.SetLogLevel("debug") -} - -func main() { - app := cli.NewApp() - app.Name = "netmon" - app.Usage = "netmon [command] [args...]" - app.Commands = []cli.Command{ - { - Name: "config", - Usage: "Create a config from a mintnet testnet", - ArgsUsage: "[chainID] [prefix] [N]", - Action: func(c *cli.Context) { - cmdConfig(c) - }, - }, - { - Name: "chains-and-vals", - Usage: "Add a chain or validator set to the main config file", - ArgsUsage: "", - Action: func(c *cli.Context) { - cmdChainsAndVals(c) - }, - Subcommands: []cli.Command{ - { - Name: "chain", - Usage: "Add a chain to the main config file", - ArgsUsage: "[configFile] [chainBaseDir]", - Action: func(c *cli.Context) { - cmdAddChain(c) - }, - }, - { - Name: "val", - Usage: "Add a validator set to the main config file", - ArgsUsage: "[configFile] [valsetBaseDir]", - Action: func(c *cli.Context) { - cmdAddValSet(c) - }, - }, - }, - }, - { - Name: "monitor", - Usage: "Monitor a chain", - ArgsUsage: "[config file]", - Action: func(c *cli.Context) { - cmdMonitor(c) - }, - }, - { - Name: "bench", - Usage: "Benchmark a chain's tx throughput and latency", - ArgsUsage: "[config file] [results dir] [n txs] -- [command to fire n txs]", - Action: func(c *cli.Context) { - cmdBench(c) - }, - Flags: []cli.Flag{ - cli.IntFlag{ - Name: "n_txs", - Value: 0, - Usage: "run benchmark until this many txs have been committed", - }, - cli.IntFlag{ - Name: "n_blocks", - Value: 0, - Usage: "run benchmark until this many blocks have been committed", - }, - }, - }, - } - app.Run(os.Args) -} - -func cmdChainsAndVals(c *cli.Context) { - cli.ShowAppHelp(c) -} - -func cmdAddChain(c *cli.Context) { - args := c.Args() - if len(args) != 2 { - Exit("add chain expectes 2 arg") - } - cfgFile, chainDir := args[0], args[1] - - // load major config - chainsAndVals := new(ChainsAndValidators) - if err := ReadJSONFile(chainsAndVals, cfgFile); err != nil { - Exit(err.Error()) - } - - // load new chain - chainCfg_ := new(BlockchainConfig) - if err := ReadJSONFile(chainCfg_, path.Join(chainDir, "chain_config.json")); err != nil { - Exit(err.Error()) - } - chainCfg := convertMintnetBlockchain(chainCfg_) - - // append new chain - chainsAndVals.Blockchains = append(chainsAndVals.Blockchains, chainCfg) - - // write major config - b := wire.JSONBytes(chainsAndVals) - if err := ioutil.WriteFile(cfgFile, b, 0600); err != nil { - Exit(err.Error()) - } -} - -func ReadJSONFile(o interface{}, filename string) error { - b, err := ioutil.ReadFile(filename) - if err != nil { - return err - } - wire.ReadJSON(o, b, &err) - if err != nil { - return err - } - return nil -} - -func cmdAddValSet(c *cli.Context) { - args := c.Args() - if len(args) != 2 { - Exit("add chain expectes 2 arg") - } - cfgFile, valSetDir := args[0], args[1] - - // load major config - chainsAndVals := new(ChainsAndValidators) - if err := ReadJSONFile(chainsAndVals, cfgFile); err != nil { - Exit(err.Error()) - } - - // load new validator set - valSet := new(types.ValidatorSet) - if err := ReadJSONFile(valSet, path.Join(valSetDir, "validator_set.json")); err != nil { - Exit(err.Error()) - } - - // append new validator set - chainsAndVals.ValidatorSets = append(chainsAndVals.ValidatorSets, valSet) - - // write major config to file - b := wire.JSONBytes(chainsAndVals) - if err := ioutil.WriteFile(cfgFile, b, 0600); err != nil { - Exit(err.Error()) - } - -} - -func cmdMonitor(c *cli.Context) { - args := c.Args() - if len(args) != 1 { - Exit("monitor expectes 1 arg") - } - chainsAndValsFile := args[0] - chainsAndVals, err := LoadChainsAndValsFromFile(chainsAndValsFile) - if err != nil { - Exit(err.Error()) - } - - network := registerNetwork(chainsAndVals) - startRPC(network) - - TrapSignal(func() { - network.Stop() - }) -} - -func cmdBench(c *cli.Context) { - args := c.Args() - if len(args) < 2 { - Exit("bench expects at least 2 args") - } - chainsAndValsFile := args[0] - resultsDir := args[1] - // extra args are a program to run locally - if len(args) > 2 { - args = args[2:] - } else { - args = args[:0] - } - - chainsAndVals, err := LoadChainsAndValsFromFile(chainsAndValsFile) - if err != nil { - Exit(err.Error()) - } - - network := registerNetwork(chainsAndVals) - startRPC(network) - - // benchmark txs - done := make(chan *types.BenchmarkResults) - - // we should only have one chain for a benchmark run - chAndValIDs, _ := network.Status() - chain, _ := network.GetChain(chAndValIDs.ChainIDs[0]) - - // setup benchresults struct and fire txs - if nTxs := c.Int("n_txs"); nTxs != 0 { - chain.Status.BenchmarkTxs(done, nTxs, args) - } else if nBlocks := c.Int("n_blocks"); nBlocks != 0 { - chain.Status.BenchmarkBlocks(done, nBlocks, args) - } else { - Exit("Must specify one of n_txs or n_blocks") - } - results := <-done - - b, err := json.Marshal(results) - if err != nil { - Exit(err.Error()) - } - fmt.Println(string(b)) - if err := ioutil.WriteFile(path.Join(resultsDir, "netmon.log"), b, 0600); err != nil { - Exit(err.Error()) - } - finalResults := fmt.Sprintf("%f,%f\n", results.MeanLatency, results.MeanThroughput) - if err := ioutil.WriteFile(path.Join(resultsDir, "final_results"), []byte(finalResults), 0600); err != nil { - Exit(err.Error()) - } -} - -func registerNetwork(chainsAndVals *ChainsAndValidators) *handlers.TendermintNetwork { - // the main object that watches for changes and serves the rpc requests - network := handlers.NewTendermintNetwork() - - for _, valSetCfg := range chainsAndVals.ValidatorSets { - // Register validator set - _, err := network.RegisterValidatorSet(valSetCfg) - if err != nil { - Exit("Register validator set error: " + err.Error()) - } - } - - for _, chainCfg := range chainsAndVals.Blockchains { - // Register blockchain - _, err := network.RegisterChain(chainCfg) - if err != nil { - Exit(Fmt("Register chain error for chain %s: %v", chainCfg.ID, err)) - } - } - - return network -} - -func startRPC(network *handlers.TendermintNetwork) { - // the routes are functions on the network object - routes := handlers.Routes(network) - - // serve http and ws - mux := http.NewServeMux() - wm := rpcserver.NewWebsocketManager(routes, nil) // TODO: evsw - mux.HandleFunc("/websocket", wm.WebsocketHandler) - rpcserver.RegisterRPCFuncs(mux, routes) - if _, err := rpcserver.StartHTTPServer("0.0.0.0:46670", mux); err != nil { - Exit(err.Error()) - } - -} - -func cmdConfig(c *cli.Context) { - args := c.Args() - if len(args) != 3 { - Exit("config expects 3 args") - } - id, prefix := args[0], args[1] - n, err := strconv.Atoi(args[2]) - if err != nil { - Exit(err.Error()) - } - chain, err := ConfigFromMachines(id, prefix, n) - if err != nil { - Exit(err.Error()) - } - - b, err := json.Marshal(chain) - if err != nil { - Exit(err.Error()) - } - fmt.Println(string(b)) -} - -func ConfigFromMachines(chainID, prefix string, N int) (*types.BlockchainConfig, error) { - - chain := &types.BlockchainConfig{ - ID: chainID, - Validators: make([]*types.ValidatorState, N), - } - for i := 0; i < N; i++ { - id := fmt.Sprintf("%s%d", prefix, i+1) - ip, success := runProcessGetResult(id+"-ip", "docker-machine", []string{"ip", id}) - if !success { - return nil, fmt.Errorf(ip) - } - - val := &types.Validator{ - ID: id, - // TODO: pubkey - } - chainVal := &types.ValidatorState{ - Config: &types.ValidatorConfig{ - Validator: val, - RPCAddr: fmt.Sprintf("%s:%d", strings.Trim(ip, "\n"), 46657), - Index: i, - }, - } - chain.Validators[i] = chainVal - } - return chain, nil -} - -func runProcessGetResult(label string, command string, args []string) (string, bool) { - outFile := NewBufferCloser(nil) - fmt.Println(Green(command), Green(args)) - proc, err := pcm.StartProcess(label, command, args, nil, outFile) - if err != nil { - return "", false - } - - <-proc.WaitCh - if proc.ExitState.Success() { - fmt.Println(Blue(string(outFile.Bytes()))) - return string(outFile.Bytes()), true - } else { - // Error! - fmt.Println(Red(string(outFile.Bytes()))) - return string(outFile.Bytes()), false - } -} - -//---------------------------------------------------------------------- - -type ChainsAndValidators struct { - ValidatorSets []*types.ValidatorSet `json:"validator_sets"` - Blockchains []*types.BlockchainConfig `json:"blockchains"` -} - -func LoadChainsAndValsFromFile(configFile string) (*ChainsAndValidators, error) { - - b, err := ioutil.ReadFile(configFile) - if err != nil { - return nil, err - } - - chainsAndVals_ := new(ChainsAndValidators) - wire.ReadJSON(chainsAndVals_, b, &err) - if err != nil { - return nil, err - } - - return chainsAndVals_, nil -} - -// because types are duplicated in mintnet -type BlockchainConfig struct { - ID string `json:"id"` - ValSetID string `json:"val_set_id"` - Validators []*types.ValidatorConfig `json:"validators"` -} - -func convertMintnetBlockchain(b *BlockchainConfig) *types.BlockchainConfig { - vals := make([]*types.ValidatorState, len(b.Validators)) - for j, v := range b.Validators { - vals[j] = new(types.ValidatorState) - vals[j].Config = v - } - return &types.BlockchainConfig{ - ID: b.ID, - ValSetID: b.ValSetID, - Validators: vals, - } - -} diff --git a/setup.sh b/setup.sh deleted file mode 100644 index e4b163b9..00000000 --- a/setup.sh +++ /dev/null @@ -1,51 +0,0 @@ -#! /bin/bash -set -e - -# assumes machines already created -N_MACHINES=4 - -MACH_PREFIX=netmon - -APP_INIT_SCRIPT=$GOPATH/src/github.com/tendermint/mintnet/examples/counter/app/init.sh - -TESTNET_DIR=~/testnets_netmon -CHAINS_AND_VALS=$TESTNET_DIR/chains_and_vals.json -CHAINS_DIR=$TESTNET_DIR/chains -VALS_DIR=$TESTNET_DIR/validators - -VALSETS=(validator-set-numero-uno BOA BunkBankBandaloo victory_validators) -#VALSETS=(my-val-set) - -CHAINS=(blockchain1 chainiac Chainelle chain-a-daisy blockchain100 bandit-chain gambit-chain gambit-chain-duo gambit-chain-1002) -#CHAINS=(my-chain) - -mkdir -p $TESTNET_DIR -echo "{}" > $CHAINS_AND_VALS - -echo "Make some validator sets" -# make some validator sets -for valset in ${VALSETS[@]}; do - mintnet init validator-set $VALS_DIR/$valset - netmon chains-and-vals val $CHAINS_AND_VALS $VALS_DIR/$valset -done - - -echo "Make some blockchains" -# make some blockchains with each validator set -for i in ${!CHAINS[@]}; do - valset=$(($i % ${#VALSETS[@]})) - mintnet init --machines "${MACH_PREFIX}[1-4]" chain --app $APP_INIT_SCRIPT --validator-set $VALS_DIR/${VALSETS[$valset]} $CHAINS_DIR/${CHAINS[$i]} -done - - -echo "Start the chains" -for chain in ${CHAINS[@]}; do - # randomize the machine order for each chain - machs=`python -c "import random; x=range(1, $(($N_MACHINES+1))); random.shuffle(x); print \",\".join(map(str,x))"` - echo $machs - echo $chain - mintnet start --publish-all --machines ${MACH_PREFIX}[$machs] app-$chain $CHAINS_DIR/$chain - - # add the new chain config - netmon chains-and-vals chain $CHAINS_AND_VALS $CHAINS_DIR/$chain -done diff --git a/tm-monitor/Dockerfile.dev b/tm-monitor/Dockerfile.dev new file mode 100644 index 00000000..bbfe6b29 --- /dev/null +++ b/tm-monitor/Dockerfile.dev @@ -0,0 +1,12 @@ +FROM golang:latest + +RUN mkdir -p /go/src/github.com/tendermint/netmon/tm-monitor +WORKDIR /go/src/github.com/tendermint/netmon/tm-monitor + +COPY Makefile /go/src/github.com/tendermint/netmon/tm-monitor/ +COPY glide.yaml /go/src/github.com/tendermint/netmon/tm-monitor/ +COPY glide.lock /go/src/github.com/tendermint/netmon/tm-monitor/ + +RUN make get_deps + +COPY . /go/src/github.com/tendermint/netmon/tm-monitor diff --git a/tm-monitor/LICENSE b/tm-monitor/LICENSE new file mode 100644 index 00000000..8fd56597 --- /dev/null +++ b/tm-monitor/LICENSE @@ -0,0 +1,203 @@ +Copyright 2017 Tendermint + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tm-monitor/Makefile b/tm-monitor/Makefile new file mode 100644 index 00000000..9b7ba2ab --- /dev/null +++ b/tm-monitor/Makefile @@ -0,0 +1,11 @@ +GOTOOLS = \ + github.com/Masterminds/glide + +tools: + go get -v $(GOTOOLS) + +get_deps: tools + @echo "--> Running glide install" + @glide install + +.PHONY: get_deps diff --git a/tm-monitor/README.md b/tm-monitor/README.md new file mode 100644 index 00000000..7e06c223 --- /dev/null +++ b/tm-monitor/README.md @@ -0,0 +1,84 @@ +# Tendermint monitor (tm-monitor) + +Tendermint monitor watches over one or more [Tendermint +core](https://github.com/tendermint/tendermint) applications (nodes), +collecting and providing various statistics to the user. + +* [QuickStart using Docker](#quickstart-using-docker) +* [QuickStart using binaries](#quickstart-using-binaries) +* [Usage](#usage) +* [RPC UI](#rpc-ui) + +## QuickStart using Docker + +``` +docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init +docker run -it --rm -v "/tmp:/tendermint" -p "46657:46657" tendermint/tendermint + +docker run -it --rm tendermint/tm-monitor +``` + +## QuickStart using binaries + +Linux: + +``` +curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.8.0/tendermint_linux_amd64.zip && sudo unzip -d /usr/local/bin tendermint_linux_amd64.zip && sudo chmod +x tendermint +tendermint init +tendermint node --app_proxy=dummy + +tm-monitor localhost:46657 +``` + +Max OS: + +``` +curl -L https://s3-us-west-2.amazonaws.com/tendermint/0.8.0/tendermint_darwin_amd64.zip && sudo unzip -d /usr/local/bin tendermint_darwin_amd64.zip && sudo chmod +x tendermint +tendermint init +tendermint node --app_proxy=dummy + +tm-monitor localhost:46657 +``` + +## Usage + +``` +# monitor single instance +tm-monitor localhost:46657 + +# monitor a few instances by providing comma-separated list of RPC endpoints +tm-monitor host1:46657,host2:46657 +``` + +### RPC UI + +Run `tm-monitor` and visit [http://localhost:46670](http://localhost:46670). +You should see the list of the available RPC endpoints: + +``` +http://localhost:46670/status +http://localhost:46670/status/network +http://localhost:46670/monitor?endpoint=_ +http://localhost:46670/status/node?name=_ +http://localhost:46670/unmonitor?endpoint=_ +``` + +The API is available as GET requests with URI encoded parameters, or as JSONRPC +POST requests. The JSONRPC methods are also exposed over websocket. + +### Ideas + +1. Currently we get IPs and dial, but should reverse so the nodes dial the + netmon, both for node privacy and easier reconfig (validators changing + ip/port). +2. Uptime over last day, month, year +3. `statsd` metrics +4. log metrics for charts +5. show network size (Q: how do I get the number?) +6. metrics RPC + +### TODO + +- [ ] `NumValidators` +- [ ] docker container +- [ ] binary diff --git a/glide.lock b/tm-monitor/glide.lock similarity index 54% rename from glide.lock rename to tm-monitor/glide.lock index 77740047..cd880823 100644 --- a/glide.lock +++ b/tm-monitor/glide.lock @@ -1,34 +1,37 @@ -hash: c2b79c89372479c83c23a80170bdb578b32238f5b387c79d7508de98e512657b -updated: 2016-10-12T11:13:42.070811409-04:00 +hash: 8d69350ae306418c61ce3e1ffdc40ddecef9591eafd645373f94a83aea9aadb1 +updated: 2017-02-28T14:36:53.495322474Z imports: - name: github.com/btcsuite/btcd - version: 42a4366ba8b170de11c471fdfb1f3eede9642a0f + version: 583684b21bfbde9b5fc4403916fd7c807feb0289 subpackages: - btcec -- name: github.com/btcsuite/fastsha256 - version: 637e656429416087660c84436a2a035d69d54e2e - name: github.com/BurntSushi/toml version: 99064174e013895bbd9b025c31100bd1d9b590ca -- name: github.com/codegangsta/cli - version: 55f715e28c46073d0e217e2ce8eb46b0b45e3db6 - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/golang/protobuf - version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e + version: 69b215d01a5606c843240eab4937eab3acee6530 subpackages: - proto - name: github.com/golang/snappy - version: d9eb7a3d35ec988b8585d4a0068e462c27d28380 + version: 553a641470496b2327abcac10b36396bd98e45c9 - name: github.com/gorilla/websocket - version: 2d1e4548da234d9cb742cc3628556fef86aafbac + version: 3f3e394da2b801fbe732a935ef40724762a67a07 +- name: github.com/jmhodges/levigo + version: c42d9e0ca023e2198120196f842701bb4c55d7b9 - name: github.com/mattn/go-colorable - version: 6c903ff4aa50920ca86087a280590b36b3152b9c + version: d898aa9fb31c91f35dd28ca75db377eff023c076 - name: github.com/mattn/go-isatty - version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 + version: dda3de49cbfcec471bd7a70e6cc01fcc3ff90109 - name: github.com/rcrowley/go-metrics - version: ab2277b1c5d15c3cba104e9cbddbdfc622df5ad8 + version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c +- name: github.com/stretchr/testify + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 + subpackages: + - assert + - require - name: github.com/syndtr/goleveldb - version: 6b4daa5362b502898ddf367c5c11deb9e7a5c727 + version: 23851d93a2292dcc56e71a18ec9e0624d84a0f65 subpackages: - leveldb - leveldb/cache @@ -42,59 +45,58 @@ imports: - leveldb/storage - leveldb/table - leveldb/util +- name: github.com/tendermint/abci + version: 1e8791bc9ac2d65eaf3f315393b1312daa46a7f5 + subpackages: + - types - name: github.com/tendermint/ed25519 version: 1f52c6f8b8a5c7908aff4497c186af344b428925 subpackages: - edwards25519 - extra25519 -- name: github.com/tendermint/flowcontrol - version: 84d9671090430e8ec80e35b339907e0579b999eb - name: github.com/tendermint/go-common - version: 47e06734f6ee488cc2e61550a38642025e1d4227 + version: e289af53b6bf6af28da129d9ef64389a4cf7987f - name: github.com/tendermint/go-config version: e64b424499acd0eb9856b88e10c0dff41628c0d6 - name: github.com/tendermint/go-crypto version: 4b11d62bdb324027ea01554e5767b71174680ba0 - name: github.com/tendermint/go-db - version: 31fdd21c7eaeed53e0ea7ca597fb1e960e2988a5 + version: 72f6dacd22a686cdf7fcd60286503e3aceda77ba - name: github.com/tendermint/go-event-meter version: c9240a51209b7afbfc9270faac841e3cb033a4d9 - name: github.com/tendermint/go-events - version: 1652dc8b3f7780079aa98c3ce20a83ee90b9758b + version: fddee66d90305fccb6f6d84d16c34fa65ea5b7f6 +- name: github.com/tendermint/go-flowrate + version: a20c98e61957faa93b4014fbd902f20ab9317a6a + subpackages: + - flowrate - name: github.com/tendermint/go-logger version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2 - name: github.com/tendermint/go-merkle - version: 05042c6ab9cad51d12e4cecf717ae68e3b1409a8 + version: 7a86b4486f2cd84ac885c5bbc609fdee2905f5d1 - name: github.com/tendermint/go-p2p - version: 1eb390680d33299ba0e3334490eca587efd18414 + version: 3d98f675f30dc4796546b8b890f895926152fa8d subpackages: - upnp -- name: github.com/tendermint/go-process - version: ba01cfbb58d446673beff17e72883cb49c835fb9 - name: github.com/tendermint/go-rpc - version: 855255d73eecd25097288be70f3fb208a5817d80 + version: fcea0cda21f64889be00a0f4b6d13266b1a76ee7 subpackages: - client - server - types - name: github.com/tendermint/go-wire - version: 3b0adbc86ed8425eaed98516165b6788d9f4de7a + version: 2f3b7aafe21c80b19b6ee3210ecb3e3d07c7a471 - name: github.com/tendermint/log15 - version: 9545b249b3aacafa97f79e0838b02b274adc6f5f + version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6 subpackages: - term - name: github.com/tendermint/tendermint - version: 302bbc5dbd8277b849060d07e046c33e745e9a1a + version: 764091dfbb035f1b28da4b067526e04c6a849966 subpackages: - - config/tendermint - rpc/core/types - types -- name: github.com/tendermint/tmsp - version: 5d3eb0328a615ba55b580ce871033e605aa8b97d - subpackages: - - types - name: golang.org/x/crypto - version: 4cd25d65a015cc83d41bf3454e6e8d6c116d16da + version: 453249f01cfeb54c3d549ddb75ff152ca243f9d8 subpackages: - curve25519 - nacl/box @@ -105,7 +107,7 @@ imports: - ripemd160 - salsa20/salsa - name: golang.org/x/net - version: 6dba816f1056709e29a1c442883cab1336d3c083 + version: 906cda9512f77671ab44f8c8563b13a8e707b230 subpackages: - context - http2 @@ -115,11 +117,11 @@ imports: - lex/httplex - trace - name: golang.org/x/sys - version: 9bb9f0998d48b31547d975974935ae9b48c7a03c + version: e4594059fe4cde2daf423055a596c2cd1e6c9adf subpackages: - unix - name: google.golang.org/grpc - version: 9eaed1a74af580b44448989c8ed830bc210bddf4 + version: d122f1dfe6ecece11d0c914ce4b2d9cab6d4b4ff subpackages: - codes - credentials @@ -128,5 +130,15 @@ imports: - metadata - naming - peer + - stats + - tap - transport -testImports: [] +testImports: +- name: github.com/davecgh/go-spew + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib diff --git a/glide.yaml b/tm-monitor/glide.yaml similarity index 53% rename from glide.yaml rename to tm-monitor/glide.yaml index f0b9312c..9a4c964d 100644 --- a/glide.yaml +++ b/tm-monitor/glide.yaml @@ -1,21 +1,13 @@ -package: github.com/tendermint/netmon +package: github.com/tendermint/netmon/tm-monitor import: -- package: github.com/codegangsta/cli -- package: github.com/rcrowley/go-metrics - package: github.com/tendermint/go-common -- package: github.com/tendermint/go-config -- package: github.com/tendermint/go-crypto - package: github.com/tendermint/go-event-meter - package: github.com/tendermint/go-events - package: github.com/tendermint/go-logger -- package: github.com/tendermint/go-process -- package: github.com/tendermint/go-rpc - subpackages: - - client - - server -- package: github.com/tendermint/go-wire - package: github.com/tendermint/tendermint subpackages: - - config/tendermint - - rpc/core/types - types + - rpc/core/types +- package: github.com/tendermint/go-wire +- package: github.com/rcrowley/go-metrics +- package: github.com/stretchr/testify diff --git a/tm-monitor/main.go b/tm-monitor/main.go new file mode 100644 index 00000000..63b3f60a --- /dev/null +++ b/tm-monitor/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + cmn "github.com/tendermint/go-common" + logger "github.com/tendermint/go-logger" +) + +var log = logger.New() + +func main() { + var listenAddr string + var verbose bool + + flag.StringVar(&listenAddr, "-listen-addr", "tcp://0.0.0.0:46670", "HTTP and Websocket server listen address") + flag.BoolVar(&verbose, "v", false, "verbose logging") + + flag.Usage = func() { + fmt.Println(`Tendermint monitor watches over one or more Tendermint core +applications, collecting and providing various statistics to the user. + +Usage: + tm-monitor [-v] [--listen-addr="tcp://0.0.0.0:46670"] [endpoints] + +Examples: + # monitor single instance + tm-monitor localhost:46657 + + # monitor a few instances by providing comma-separated list of RPC endpoints + tm-monitor host1:46657,host2:46657`) + fmt.Println("Flags:") + flag.PrintDefaults() + } + + flag.Parse() + + if flag.NArg() == 0 { + flag.Usage() + os.Exit(1) + } + + if verbose { + log.SetHandler(logger.LvlFilterHandler( + logger.LvlDebug, + logger.BypassHandler(), + )) + } else { + log.SetHandler(logger.LvlFilterHandler( + logger.LvlInfo, + logger.BypassHandler(), + )) + } + + m := startMonitor(flag.Arg(0)) + + startRPC(listenAddr, m) + + ton := NewTon(m) + ton.Start() + + cmn.TrapSignal(func() { + ton.Stop() + m.Stop() + }) +} + +func startMonitor(endpoints string) *Monitor { + m := NewMonitor() + + for _, e := range strings.Split(endpoints, ",") { + if err := m.Monitor(NewNode(e)); err != nil { + log.Crit(err.Error()) + os.Exit(1) + } + } + + if err := m.Start(); err != nil { + log.Crit(err.Error()) + os.Exit(1) + } + + return m +} diff --git a/tm-monitor/mock/mock.go b/tm-monitor/mock/mock.go new file mode 100644 index 00000000..80fca88b --- /dev/null +++ b/tm-monitor/mock/mock.go @@ -0,0 +1,37 @@ +package mock + +import ( + em "github.com/tendermint/go-event-meter" +) + +type EventMeter struct { + latencyCallback em.LatencyCallbackFunc + disconnectCallback em.DisconnectCallbackFunc + eventCallback em.EventCallbackFunc +} + +func (e *EventMeter) Start() (bool, error) { return true, nil } +func (e *EventMeter) Stop() bool { return true } +func (e *EventMeter) RegisterLatencyCallback(cb em.LatencyCallbackFunc) { e.latencyCallback = cb } +func (e *EventMeter) RegisterDisconnectCallback(cb em.DisconnectCallbackFunc) { + e.disconnectCallback = cb +} +func (e *EventMeter) Subscribe(eventID string, cb em.EventCallbackFunc) error { + e.eventCallback = cb + return nil +} +func (e *EventMeter) Unsubscribe(eventID string) error { + e.eventCallback = nil + return nil +} + +func (e *EventMeter) Call(callback string, args ...interface{}) { + switch callback { + case "latencyCallback": + e.latencyCallback(args[0].(float64)) + case "disconnectCallback": + e.disconnectCallback() + case "eventCallback": + e.eventCallback(args[0].(*em.EventMetric), args[1]) + } +} diff --git a/tm-monitor/monitor.go b/tm-monitor/monitor.go new file mode 100644 index 00000000..e61d3a8e --- /dev/null +++ b/tm-monitor/monitor.go @@ -0,0 +1,101 @@ +package main + +import ( + "time" + + tmtypes "github.com/tendermint/tendermint/types" +) + +// waiting more than this many seconds for a block means we're unhealthy +const nodeLivenessTimeout = 5 * time.Second + +type Monitor struct { + Nodes map[string]*Node + Network *Network + + monitorQuit chan struct{} // monitor exitting + nodeQuit map[string]chan struct{} // node is being stopped and removed from under the monitor +} + +func NewMonitor() *Monitor { + return &Monitor{ + Nodes: make(map[string]*Node), + Network: NewNetwork(), + monitorQuit: make(chan struct{}), + nodeQuit: make(map[string]chan struct{}), + } +} + +func (m *Monitor) Monitor(n *Node) error { + m.Nodes[n.Name] = n + + blockCh := make(chan tmtypes.Header, 10) + n.SendBlocksTo(blockCh) + blockLatencyCh := make(chan float64, 10) + n.SendBlockLatenciesTo(blockLatencyCh) + disconnectCh := make(chan bool, 10) + n.NotifyAboutDisconnects(disconnectCh) + + if err := n.Start(); err != nil { + return err + } + + m.nodeQuit[n.Name] = make(chan struct{}) + go m.listen(n.Name, blockCh, blockLatencyCh, disconnectCh, m.nodeQuit[n.Name]) + return nil +} + +func (m *Monitor) Unmonitor(n *Node) { + n.Stop() + close(m.nodeQuit[n.Name]) + delete(m.nodeQuit, n.Name) + delete(m.Nodes, n.Name) +} + +func (m *Monitor) Start() error { + go m.recalculateNetworkUptime() + + return nil +} + +func (m *Monitor) Stop() { + close(m.monitorQuit) + + for _, n := range m.Nodes { + m.Unmonitor(n) + } +} + +// main loop where we listen for events from the node +func (m *Monitor) listen(nodeName string, blockCh <-chan tmtypes.Header, blockLatencyCh <-chan float64, disconnectCh <-chan bool, quit <-chan struct{}) { + for { + select { + case <-quit: + return + case b := <-blockCh: + m.Network.NewBlock(b) + case l := <-blockLatencyCh: + m.Network.NewBlockLatency(l) + case disconnected := <-disconnectCh: + if disconnected { + m.Network.NodeIsDown(nodeName) + } else { + m.Network.NodeIsOnline(nodeName) + } + case <-time.After(nodeLivenessTimeout): + m.Network.NodeIsDown(nodeName) + } + } +} + +// recalculateNetworkUptime every N seconds. +func (m *Monitor) recalculateNetworkUptime() { + for { + select { + case <-m.monitorQuit: + return + case <-time.After(10 * time.Second): + m.Network.RecalculateUptime() + } + } +} diff --git a/tm-monitor/monitor_test.go b/tm-monitor/monitor_test.go new file mode 100644 index 00000000..de656de5 --- /dev/null +++ b/tm-monitor/monitor_test.go @@ -0,0 +1,11 @@ +package main_test + +import "testing" + +func TestMonitorStartStop(t *testing.T) { + +} + +func TestMonitorReceivesNewBlocksFromNodes(t *testing.T) { + +} diff --git a/tm-monitor/network.go b/tm-monitor/network.go new file mode 100644 index 00000000..88e4f7d8 --- /dev/null +++ b/tm-monitor/network.go @@ -0,0 +1,172 @@ +package main + +import ( + "fmt" + "sync" + "time" + + metrics "github.com/rcrowley/go-metrics" + tmtypes "github.com/tendermint/tendermint/types" +) + +// UptimeData stores data for how long network has been running +type UptimeData struct { + StartTime time.Time `json:"start_time"` + Uptime float64 `json:"uptime" wire:"unsafe"` // percentage of time we've been `ModerateHealth`y, ever + + totalDownTime time.Duration // total downtime (only updated when we come back online) + wentDown time.Time +} + +type Health int + +const ( + // FullHealth means all validators online, synced, making blocks + FullHealth = iota + // ModerateHealth means we're making blocks + ModerateHealth + // Dead means we're not making blocks due to all validators freezing or crashing + Dead +) + +// Common statistics for network of nodes +type Network struct { + Height uint64 `json:"height"` + + AvgBlockTime float64 `json:"avg_block_time" wire:"unsafe"` // ms (avg over last minute) + blockTimeMeter metrics.Meter + AvgTxThroughput float64 `json:"avg_tx_throughput" wire:"unsafe"` // tx/s (avg over last minute) + txThroughputMeter metrics.Meter + AvgBlockLatency float64 `json:"avg_block_latency" wire:"unsafe"` // ms (avg over last minute) + blockLatencyMeter metrics.Meter + + // Network Info + NumValidators int `json:"num_validators"` + NumValidatorsOnline int `json:"num_validators_online"` + + Health Health `json:"health"` + + UptimeData *UptimeData `json:"uptime_data"` + + nodeStatusMap map[string]bool + + mu sync.Mutex +} + +func NewNetwork() *Network { + return &Network{ + blockTimeMeter: metrics.NewMeter(), + txThroughputMeter: metrics.NewMeter(), + blockLatencyMeter: metrics.NewMeter(), + Health: FullHealth, + UptimeData: &UptimeData{ + StartTime: time.Now(), + Uptime: 100.0, + }, + nodeStatusMap: make(map[string]bool), + } +} + +func (n *Network) NewBlock(b tmtypes.Header) { + n.mu.Lock() + defer n.mu.Unlock() + + if n.Height >= uint64(b.Height) { + log.Debug("Received new block with height %v less or equal to recorded %v", b.Height, n.Height) + return + } + + log.Debug("Received new block", "height", b.Height, "ntxs", b.NumTxs) + n.Height = uint64(b.Height) + + n.blockTimeMeter.Mark(1) + n.AvgBlockTime = (1.0 / n.blockTimeMeter.Rate1()) * 1000 // 1/s to ms + n.txThroughputMeter.Mark(int64(b.NumTxs)) + n.AvgTxThroughput = n.txThroughputMeter.Rate1() + + // if we're making blocks, we're healthy + if n.Health == Dead { + n.Health = ModerateHealth + n.UptimeData.totalDownTime += time.Since(n.UptimeData.wentDown) + } + + // if we are connected to all validators, we're at full health + // TODO: make sure they're all at the same height (within a block) + // and all proposing (and possibly validating ) Alternatively, just + // check there hasn't been a new round in numValidators rounds + if n.NumValidatorsOnline == n.NumValidators { + n.Health = FullHealth + } +} + +func (n *Network) NewBlockLatency(l float64) { + n.mu.Lock() + defer n.mu.Unlock() + + n.blockLatencyMeter.Mark(int64(l)) + n.AvgBlockLatency = n.blockLatencyMeter.Rate1() / 1000000.0 // ns to ms +} + +// RecalculateUptime calculates uptime on demand. +func (n *Network) RecalculateUptime() { + n.mu.Lock() + defer n.mu.Unlock() + + since := time.Since(n.UptimeData.StartTime) + uptime := since - n.UptimeData.totalDownTime + if n.Health != FullHealth { + uptime -= time.Since(n.UptimeData.wentDown) + } + n.UptimeData.Uptime = (float64(uptime) / float64(since)) * 100.0 +} + +func (n *Network) NodeIsDown(name string) { + n.mu.Lock() + defer n.mu.Unlock() + + if online := n.nodeStatusMap[name]; online { + n.nodeStatusMap[name] = false + n.NumValidatorsOnline-- + n.UptimeData.wentDown = time.Now() + n.updateHealth() + } +} + +func (n *Network) NodeIsOnline(name string) { + n.mu.Lock() + defer n.mu.Unlock() + + if online, ok := n.nodeStatusMap[name]; !ok || !online { + n.nodeStatusMap[name] = true + n.NumValidatorsOnline++ + n.UptimeData.totalDownTime += time.Since(n.UptimeData.wentDown) + n.updateHealth() + } +} + +func (n *Network) updateHealth() { + if n.NumValidatorsOnline > n.NumValidators { + panic(fmt.Sprintf("got %d validators. max %ds", n.NumValidatorsOnline, n.NumValidators)) + } + + if n.NumValidatorsOnline != n.NumValidators { + n.Health = ModerateHealth + } + + if n.NumValidatorsOnline == 0 { + n.Health = Dead + } +} + +func (n *Network) GetHealthString() string { + switch n.Health { + case FullHealth: + return "full" + case ModerateHealth: + return "moderate" + case Dead: + return "dead" + default: + return "undefined" + } +} diff --git a/tm-monitor/node.go b/tm-monitor/node.go new file mode 100644 index 00000000..b3037d72 --- /dev/null +++ b/tm-monitor/node.go @@ -0,0 +1,177 @@ +package main + +import ( + "encoding/json" + "fmt" + "math" + "time" + + em "github.com/tendermint/go-event-meter" + events "github.com/tendermint/go-events" + tmtypes "github.com/tendermint/tendermint/types" + + wire "github.com/tendermint/go-wire" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +const maxRestarts = 25 + +type Node struct { + rpcAddr string + + IsValidator bool `json:"is_validator"` // validator or non-validator? + + // "github.com/tendermint/go-crypto" + // PubKey crypto.PubKey `json:"pub_key"` + + Name string `json:"name"` + Online bool `json:"online"` + Height uint64 `json:"height"` + BlockLatency float64 `json:"block_latency" wire:"unsafe"` // ms, interval between block commits + + // em holds the ws connection. Each eventMeter callback is called in a separate go-routine. + em eventMeter + + blockCh chan<- tmtypes.Header + blockLatencyCh chan<- float64 + disconnectCh chan<- bool +} + +func NewNode(rpcAddr string) *Node { + em := em.NewEventMeter(rpcAddr, UnmarshalEvent) + return NewNodeWithEventMeter(rpcAddr, em) +} + +func NewNodeWithEventMeter(rpcAddr string, em eventMeter) *Node { + return &Node{ + rpcAddr: rpcAddr, + em: em, + Name: rpcAddr, + } +} + +func (n *Node) SendBlocksTo(ch chan<- tmtypes.Header) { + n.blockCh = ch +} + +func (n *Node) SendBlockLatenciesTo(ch chan<- float64) { + n.blockLatencyCh = ch +} + +func (n *Node) NotifyAboutDisconnects(ch chan<- bool) { + n.disconnectCh = ch +} + +func (n *Node) Start() error { + if _, err := n.em.Start(); err != nil { + return err + } + + n.em.RegisterLatencyCallback(latencyCallback(n)) + n.em.Subscribe(tmtypes.EventStringNewBlockHeader(), newBlockCallback(n)) + n.em.RegisterDisconnectCallback(disconnectCallback(n)) + + n.Online = true + + return nil +} + +func (n *Node) Stop() { + n.Online = false + + n.em.RegisterLatencyCallback(nil) + n.em.Unsubscribe(tmtypes.EventStringNewBlockHeader()) + n.em.RegisterDisconnectCallback(nil) + + // FIXME stop blocks at event_meter.go:140 + // n.em.Stop() +} + +// implements eventmeter.EventCallbackFunc +func newBlockCallback(n *Node) em.EventCallbackFunc { + return func(metric *em.EventMetric, data events.EventData) { + block := data.(tmtypes.EventDataNewBlockHeader).Header + + n.Height = uint64(block.Height) + + if n.blockCh != nil { + n.blockCh <- *block + } + } +} + +// implements eventmeter.EventLatencyFunc +func latencyCallback(n *Node) em.LatencyCallbackFunc { + return func(latency float64) { + n.BlockLatency = latency / 1000000.0 // ns to ms + if n.blockLatencyCh != nil { + n.blockLatencyCh <- latency + } + } +} + +// implements eventmeter.DisconnectCallbackFunc +func disconnectCallback(n *Node) em.DisconnectCallbackFunc { + return func() { + n.Online = false + if n.disconnectCh != nil { + n.disconnectCh <- true + } + + if err := n.RestartBackOff(); err != nil { + log.Error(err.Error()) + } else { + n.Online = true + if n.disconnectCh != nil { + n.disconnectCh <- false + } + } + } +} + +func (n *Node) RestartBackOff() error { + attempt := 0 + + for { + d := time.Duration(math.Exp2(float64(attempt))) + time.Sleep(d * time.Second) + + if err := n.Start(); err != nil { + log.Debug("Can't connect to node %v due to %v", n, err) + } else { + // TODO: authenticate pubkey + return nil + } + + attempt++ + + if attempt > maxRestarts { + return fmt.Errorf("Reached max restarts for node %v", n) + } + } +} + +type eventMeter interface { + Start() (bool, error) + Stop() bool + RegisterLatencyCallback(em.LatencyCallbackFunc) + RegisterDisconnectCallback(em.DisconnectCallbackFunc) + Subscribe(string, em.EventCallbackFunc) error + Unsubscribe(string) error +} + +// Unmarshal a json event +func UnmarshalEvent(b json.RawMessage) (string, events.EventData, error) { + var err error + result := new(ctypes.TMResult) + wire.ReadJSONPtr(result, b, &err) + if err != nil { + return "", nil, err + } + event, ok := (*result).(*ctypes.ResultEvent) + if !ok { + return "", nil, nil // TODO: handle non-event messages (ie. return from subscribe/unsubscribe) + // fmt.Errorf("Result is not type *ctypes.ResultEvent. Got %v", reflect.TypeOf(*result)) + } + return event.Name, event.Data, nil +} diff --git a/tm-monitor/node_test.go b/tm-monitor/node_test.go new file mode 100644 index 00000000..89d81324 --- /dev/null +++ b/tm-monitor/node_test.go @@ -0,0 +1,74 @@ +package main_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + em "github.com/tendermint/go-event-meter" + monitor "github.com/tendermint/netmon/tm-monitor" + mock "github.com/tendermint/netmon/tm-monitor/mock" + tmtypes "github.com/tendermint/tendermint/types" +) + +func TestNodeStartStop(t *testing.T) { + assert := assert.New(t) + + n, _ := setupNode(t) + assert.Equal(true, n.Online) + + n.Stop() +} + +func TestNodeNewBlockReceived(t *testing.T) { + assert := assert.New(t) + + blockCh := make(chan tmtypes.Header, 100) + n, emMock := setupNode(t) + n.SendBlocksTo(blockCh) + + blockHeader := &tmtypes.Header{Height: 5} + emMock.Call("eventCallback", &em.EventMetric{}, tmtypes.EventDataNewBlockHeader{blockHeader}) + + assert.Equal(uint64(5), n.Height) + assert.Equal(*blockHeader, <-blockCh) +} + +func TestNodeNewBlockLatencyReceived(t *testing.T) { + assert := assert.New(t) + + blockLatencyCh := make(chan float64, 100) + n, emMock := setupNode(t) + n.SendBlockLatenciesTo(blockLatencyCh) + + emMock.Call("latencyCallback", 1000000.0) + + assert.Equal(1.0, n.BlockLatency) + assert.Equal(1000000.0, <-blockLatencyCh) +} + +func TestNodeConnectionLost(t *testing.T) { + assert := assert.New(t) + + disconnectCh := make(chan bool, 100) + n, emMock := setupNode(t) + n.NotifyAboutDisconnects(disconnectCh) + + emMock.Call("disconnectCallback") + + assert.Equal(true, <-disconnectCh) + assert.Equal(false, <-disconnectCh) + + // we're back in a race + assert.Equal(true, n.Online) +} + +func setupNode(t *testing.T) (n *monitor.Node, emMock *mock.EventMeter) { + emMock = &mock.EventMeter{} + n = monitor.NewNodeWithEventMeter("tcp://127.0.0.1:46657", emMock) + + err := n.Start() + require.Nil(t, err) + return +} diff --git a/tm-monitor/rpc.go b/tm-monitor/rpc.go new file mode 100644 index 00000000..67169f6b --- /dev/null +++ b/tm-monitor/rpc.go @@ -0,0 +1,126 @@ +package main + +import ( + "errors" + "net/http" + + rpc "github.com/tendermint/go-rpc/server" +) + +func startRPC(listenAddr string, m *Monitor) { + routes := routes(m) + + // serve http and ws + mux := http.NewServeMux() + wm := rpc.NewWebsocketManager(routes, nil) // TODO: evsw + mux.HandleFunc("/websocket", wm.WebsocketHandler) + rpc.RegisterRPCFuncs(mux, routes) + if _, err := rpc.StartHTTPServer(listenAddr, mux); err != nil { + panic(err) + } +} + +func routes(m *Monitor) map[string]*rpc.RPCFunc { + return map[string]*rpc.RPCFunc{ + "status": rpc.NewRPCFunc(RPCStatus(m), ""), + "status/network": rpc.NewRPCFunc(RPCNetworkStatus(m), ""), + "status/node": rpc.NewRPCFunc(RPCNodeStatus(m), "name"), + "monitor": rpc.NewRPCFunc(RPCMonitor(m), "endpoint"), + "unmonitor": rpc.NewRPCFunc(RPCUnmonitor(m), "endpoint"), + + // "start_meter": rpc.NewRPCFunc(network.StartMeter, "chainID,valID,event"), + // "stop_meter": rpc.NewRPCFunc(network.StopMeter, "chainID,valID,event"), + // "meter": rpc.NewRPCFunc(GetMeterResult(network), "chainID,valID,event"), + } +} + +// RPCStatus returns common statistics for the network and statistics per node. +func RPCStatus(m *Monitor) interface{} { + return func() (networkAndNodes, error) { + values := make([]*Node, len(m.Nodes)) + i := 0 + for _, v := range m.Nodes { + values[i] = v + i++ + } + + return networkAndNodes{m.Network, values}, nil + } +} + +// RPCNetworkStatus returns common statistics for the network. +func RPCNetworkStatus(m *Monitor) interface{} { + return func() (*Network, error) { + return m.Network, nil + } +} + +// RPCNodeStatus returns statistics for the given node. +func RPCNodeStatus(m *Monitor) interface{} { + return func(name string) (*Node, error) { + if n, ok := m.Nodes[name]; ok { + return n, nil + } + return nil, errors.New("Cannot find node with that name") + } +} + +// RPCMonitor allows to dynamically add a endpoint to under the monitor. +func RPCMonitor(m *Monitor) interface{} { + return func(endpoint string) (*Node, error) { + n := NewNode(endpoint) + if err := m.Monitor(n); err != nil { + return nil, err + } + return n, nil + } +} + +// RPCUnmonitor removes the given endpoint from under the monitor. +func RPCUnmonitor(m *Monitor) interface{} { + return func(endpoint string) (bool, error) { + if n, ok := m.Nodes[endpoint]; ok { + m.Unmonitor(n) + return true, nil + } + return false, errors.New("Cannot find node with that name") + } +} + +// func (tn *TendermintNetwork) StartMeter(chainID, valID, eventID string) error { +// tn.mtx.Lock() +// defer tn.mtx.Unlock() +// val, err := tn.getChainVal(chainID, valID) +// if err != nil { +// return err +// } +// return val.EventMeter().Subscribe(eventID, nil) +// } + +// func (tn *TendermintNetwork) StopMeter(chainID, valID, eventID string) error { +// tn.mtx.Lock() +// defer tn.mtx.Unlock() +// val, err := tn.getChainVal(chainID, valID) +// if err != nil { +// return err +// } +// return val.EventMeter().Unsubscribe(eventID) +// } + +// func (tn *TendermintNetwork) GetMeter(chainID, valID, eventID string) (*eventmeter.EventMetric, error) { +// tn.mtx.Lock() +// defer tn.mtx.Unlock() +// val, err := tn.getChainVal(chainID, valID) +// if err != nil { +// return nil, err +// } + +// return val.EventMeter().GetMetric(eventID) +// } + +//--> types + +type networkAndNodes struct { + Network *Network `json:"network"` + Nodes []*Node `json:"nodes"` +} diff --git a/tm-monitor/ton.go b/tm-monitor/ton.go new file mode 100644 index 00000000..57296b9a --- /dev/null +++ b/tm-monitor/ton.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "io" + "os" + "text/tabwriter" + "time" +) + +const ( + // Default refresh rate - 200ms + defaultRefreshRate = time.Millisecond * 200 +) + +// Ton - table of nodes. +// +// It produces the unordered list of nodes and updates it periodically. +// +// Default output is stdout, but it could be changed. Note if you want for +// refresh to work properly, output must support [ANSI escape +// codes](http://en.wikipedia.org/wiki/ANSI_escape_code). +// +// Ton was inspired by [Linux top +// program](https://en.wikipedia.org/wiki/Top_(software)) as the name suggests. +type Ton struct { + monitor *Monitor + + RefreshRate time.Duration + Output io.Writer + quit chan struct{} +} + +func NewTon(m *Monitor) *Ton { + return &Ton{ + RefreshRate: defaultRefreshRate, + Output: os.Stdout, + quit: make(chan struct{}), + monitor: m, + } +} + +func (o *Ton) Start() { + clearScreen(o.Output) + o.Print() + go o.refresher() +} + +func (o *Ton) Print() { + moveCursor(o.Output, 1, 1) + o.printHeader() + fmt.Println() + o.printTable() +} + +func (o *Ton) Stop() { + close(o.quit) +} + +func (o *Ton) printHeader() { + n := o.monitor.Network + fmt.Fprintf(o.Output, "%v up %.2f\n", n.UptimeData.StartTime, n.UptimeData.Uptime) + fmt.Println() + fmt.Fprintf(o.Output, "Height: %d\n", n.Height) + fmt.Fprintf(o.Output, "Avg block time: %.3f ms\n", n.AvgBlockTime) + fmt.Fprintf(o.Output, "Avg Tx throughput: %.0f per sec\n", n.AvgTxThroughput) + fmt.Fprintf(o.Output, "Avg block latency: %.3f ms\n", n.AvgBlockLatency) + fmt.Fprintf(o.Output, "Validators: %d online / %d total ", n.NumValidatorsOnline, n.NumValidators) + fmt.Fprintf(o.Output, "Health: %s\n", n.GetHealthString()) +} + +func (o *Ton) printTable() { + w := tabwriter.NewWriter(o.Output, 0, 0, 4, ' ', 0) + fmt.Fprintln(w, "NAME\tHEIGHT\tBLOCK LATENCY\tONLINE\t") + for _, n := range o.monitor.Nodes { + fmt.Fprintln(w, fmt.Sprintf("%s\t%d\t%.3f ms\t%v\t", n.Name, n.Height, n.BlockLatency, n.Online)) + } + w.Flush() +} + +// Internal loop for refreshing +func (o *Ton) refresher() { + for { + select { + case <-o.quit: + return + case <-time.After(o.RefreshRate): + o.Print() + } + } +} + +func clearScreen(w io.Writer) { + fmt.Fprint(w, "\033[2J") +} + +func moveCursor(w io.Writer, x int, y int) { + fmt.Fprintf(w, "\033[%d;%dH", x, y) +} diff --git a/types/chain.go b/types/chain.go deleted file mode 100644 index f5c5344a..00000000 --- a/types/chain.go +++ /dev/null @@ -1,339 +0,0 @@ -package types - -import ( - "fmt" - "os" - "os/exec" - "sync" - "time" - - "github.com/rcrowley/go-metrics" - . "github.com/tendermint/go-common" - tmtypes "github.com/tendermint/tendermint/types" -) - -// waitign more than this many seconds for a block means we're unhealthy -const newBlockTimeoutSeconds = 5 - -//------------------------------------------------ -// blockchain types -// NOTE: mintnet duplicates some types from here and val.go -//------------------------------------------------ - -// Known chain and validator set IDs (from which anything else can be found) -// Returned by the Status RPC -type ChainAndValidatorSetIDs struct { - ChainIDs []string `json:"chain_ids"` - ValidatorSetIDs []string `json:"validator_set_ids"` -} - -//------------------------------------------------ -// chain state - -// Main chain state -// Returned over RPC; also used to manage state -type ChainState struct { - Config *BlockchainConfig `json:"config"` - Status *BlockchainStatus `json:"status"` -} - -func (cs *ChainState) NewBlock(block *tmtypes.Header) { - cs.Status.NewBlock(block) -} - -func (cs *ChainState) UpdateLatency(oldLatency, newLatency float64) { - cs.Status.UpdateLatency(oldLatency, newLatency) -} - -func (cs *ChainState) SetOnline(val *ValidatorState, isOnline bool) { - cs.Status.SetOnline(val, isOnline) -} - -//------------------------------------------------ -// Blockchain Config: id, validator config - -// Chain Config -type BlockchainConfig struct { - // should be fixed for life of chain - ID string `json:"id"` - ValSetID string `json:"val_set_id"` // NOTE: do we really commit to one val set per chain? - - // handles live validator states (latency, last block, etc) - // and validator set changes - mtx sync.Mutex - Validators []*ValidatorState `json:"validators"` // TODO: this should be ValidatorConfig and the state in BlockchainStatus - valIDMap map[string]int // map IDs to indices -} - -// So we can fetch validator by id rather than index -func (bc *BlockchainConfig) PopulateValIDMap() { - bc.mtx.Lock() - defer bc.mtx.Unlock() - bc.valIDMap = make(map[string]int) - for i, v := range bc.Validators { - bc.valIDMap[v.Config.Validator.ID] = i - } -} - -func (bc *BlockchainConfig) GetValidatorByID(valID string) (*ValidatorState, error) { - bc.mtx.Lock() - defer bc.mtx.Unlock() - valIndex, ok := bc.valIDMap[valID] - if !ok { - return nil, fmt.Errorf("Unknown validator %s", valID) - } - return bc.Validators[valIndex], nil -} - -//------------------------------------------------ -// BlockchainStatus - -// Basic blockchain metrics -type BlockchainStatus struct { - mtx sync.Mutex - - // Blockchain Info - Height int `json:"height"` // latest height we've got - BlockchainSize int64 `json:"blockchain_size"` - MeanBlockTime float64 `json:"mean_block_time" wire:"unsafe"` // ms (avg over last minute) - TxThroughput float64 `json:"tx_throughput" wire:"unsafe"` // tx/s (avg over last minute) - - blockTimeMeter metrics.Meter - txThroughputMeter metrics.Meter - - // Network Info - NumValidators int `json:"num_validators"` - ActiveValidators int `json:"active_validators"` - //ActiveNodes int `json:"active_nodes"` - MeanLatency float64 `json:"mean_latency" wire:"unsafe"` // ms - - // Health - FullHealth bool `json:"full_health"` // all validators online, synced, making blocks - Healthy bool `json:"healthy"` // we're making blocks - - // Uptime - UptimeData *UptimeData `json:"uptime_data"` - - // What else can we get / do we want? - // TODO: charts for block time, latency (websockets/event-meter ?) - - // for benchmark runs - benchResults *BenchmarkResults -} - -func (bc *BlockchainStatus) BenchmarkTxs(results chan *BenchmarkResults, nTxs int, args []string) { - log.Notice("Running benchmark", "ntxs", nTxs) - bc.benchResults = &BenchmarkResults{ - StartTime: time.Now(), - nTxs: nTxs, - results: results, - } - - if len(args) > 0 { - // TODO: capture output to file - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - go cmd.Run() - } -} - -func (bc *BlockchainStatus) BenchmarkBlocks(results chan *BenchmarkResults, nBlocks int, args []string) { - log.Notice("Running benchmark", "nblocks", nBlocks) - bc.benchResults = &BenchmarkResults{ - StartTime: time.Now(), - nBlocks: nBlocks, - results: results, - } - - if len(args) > 0 { - // TODO: capture output to file - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - go cmd.Run() - } -} - -type Block struct { - Time time.Time `json:time"` - Height int `json:"height"` - NumTxs int `json:"num_txs"` -} - -type BenchmarkResults struct { - StartTime time.Time `json:"start_time"` - StartBlock int `json:"start_block"` - TotalTime float64 `json:"total_time"` // seconds - Blocks []*Block `json:"blocks"` - NumBlocks int `json:"num_blocks"` - NumTxs int `json:"num_txs` - MeanLatency float64 `json:"latency"` // seconds per block - MeanThroughput float64 `json:"throughput"` // txs per second - - // either we wait for n blocks or n txs - nBlocks int - nTxs int - - done bool - results chan *BenchmarkResults -} - -// Return the total time to commit all txs, in seconds -func (br *BenchmarkResults) ElapsedTime() float64 { - return float64(br.Blocks[br.NumBlocks-1].Time.Sub(br.StartTime)) / float64(1000000000) -} - -// Return the avg seconds/block -func (br *BenchmarkResults) Latency() float64 { - return br.ElapsedTime() / float64(br.NumBlocks) -} - -// Return the avg txs/second -func (br *BenchmarkResults) Throughput() float64 { - return float64(br.NumTxs) / br.ElapsedTime() -} - -func (br *BenchmarkResults) Done() { - log.Info("Done benchmark", "num blocks", br.NumBlocks, "block len", len(br.Blocks)) - br.done = true - br.TotalTime = br.ElapsedTime() - br.MeanThroughput = br.Throughput() - br.MeanLatency = br.Latency() - br.results <- br -} - -type UptimeData struct { - StartTime time.Time `json:"start_time"` - Uptime float64 `json:"uptime" wire:"unsafe"` // Percentage of time we've been Healthy, ever - - totalDownTime time.Duration // total downtime (only updated when we come back online) - wentDown time.Time - - // TODO: uptime over last day, month, year -} - -func NewBlockchainStatus() *BlockchainStatus { - return &BlockchainStatus{ - blockTimeMeter: metrics.NewMeter(), - txThroughputMeter: metrics.NewMeter(), - Healthy: true, - UptimeData: &UptimeData{ - StartTime: time.Now(), - Uptime: 100.0, - }, - } -} - -func (s *BlockchainStatus) NewBlock(block *tmtypes.Header) { - s.mtx.Lock() - defer s.mtx.Unlock() - if block.Height > s.Height { - numTxs := block.NumTxs - s.Height = block.Height - s.blockTimeMeter.Mark(1) - s.txThroughputMeter.Mark(int64(numTxs)) - s.MeanBlockTime = (1.0 / s.blockTimeMeter.Rate1()) * 1000 // 1/s to ms - s.TxThroughput = s.txThroughputMeter.Rate1() - - log.Debug("New Block", "height", s.Height, "ntxs", numTxs) - if s.benchResults != nil && !s.benchResults.done { - if s.benchResults.StartBlock == 0 && numTxs > 0 { - s.benchResults.StartBlock = s.Height - } - s.benchResults.Blocks = append(s.benchResults.Blocks, &Block{ - Time: time.Now(), - Height: s.Height, - NumTxs: numTxs, - }) - s.benchResults.NumTxs += numTxs - s.benchResults.NumBlocks += 1 - if s.benchResults.nTxs > 0 && s.benchResults.NumTxs >= s.benchResults.nTxs { - s.benchResults.Done() - } else if s.benchResults.nBlocks > 0 && s.benchResults.NumBlocks >= s.benchResults.nBlocks { - s.benchResults.Done() - } - } - - // if we're making blocks, we're healthy - if !s.Healthy { - s.Healthy = true - s.UptimeData.totalDownTime += time.Since(s.UptimeData.wentDown) - } - - // if we are connected to all validators, we're at full health - // TODO: make sure they're all at the same height (within a block) and all proposing (and possibly validating ) - // Alternatively, just check there hasn't been a new round in numValidators rounds - if s.ActiveValidators == s.NumValidators { - s.FullHealth = true - } - - // TODO: should we refactor so there's a central loop and ticker? - go s.newBlockTimeout(s.Height) - } -} - -// we have newBlockTimeoutSeconds to make a new block, else we're unhealthy -func (s *BlockchainStatus) newBlockTimeout(height int) { - time.Sleep(time.Second * newBlockTimeoutSeconds) - - s.mtx.Lock() - defer s.mtx.Unlock() - if !(s.Height > height) { - s.Healthy = false - s.UptimeData.wentDown = time.Now() - } -} - -// Used to calculate uptime on demand. TODO: refactor this into the central loop ... -func (s *BlockchainStatus) RealTimeUpdates() { - s.mtx.Lock() - defer s.mtx.Unlock() - since := time.Since(s.UptimeData.StartTime) - uptime := since - s.UptimeData.totalDownTime - if !s.Healthy { - uptime -= time.Since(s.UptimeData.wentDown) - } - s.UptimeData.Uptime = float64(uptime) / float64(since) -} - -func (s *BlockchainStatus) UpdateLatency(oldLatency, newLatency float64) { - s.mtx.Lock() - defer s.mtx.Unlock() - - // update avg validator rpc latency - mean := s.MeanLatency * float64(s.NumValidators) - mean = (mean - oldLatency + newLatency) / float64(s.NumValidators) - s.MeanLatency = mean -} - -// Toggle validators online/offline (updates ActiveValidators and FullHealth) -func (s *BlockchainStatus) SetOnline(val *ValidatorState, isOnline bool) { - val.SetOnline(isOnline) - - var change int - if isOnline { - change = 1 - } else { - change = -1 - } - - s.mtx.Lock() - defer s.mtx.Unlock() - - s.ActiveValidators += change - - if s.ActiveValidators > s.NumValidators { - panic(Fmt("got %d validators. max %ds", s.ActiveValidators, s.NumValidators)) - } - - // if we lost a connection we're no longer at full health, even if it's still online. - // so long as we receive blocks, we'll know we're still healthy - if s.ActiveValidators != s.NumValidators { - s.FullHealth = false - } -} - -func TwoThirdsMaj(count, total int) bool { - return float64(count) > (2.0/3.0)*float64(total) -} diff --git a/types/log.go b/types/log.go deleted file mode 100644 index dbe8a678..00000000 --- a/types/log.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "types") diff --git a/types/val.go b/types/val.go deleted file mode 100644 index 3770df17..00000000 --- a/types/val.go +++ /dev/null @@ -1,167 +0,0 @@ -package types - -import ( - "encoding/json" - "fmt" - "sync" - - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-event-meter" - "github.com/tendermint/go-events" - client "github.com/tendermint/go-rpc/client" - "github.com/tendermint/go-wire" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - tmtypes "github.com/tendermint/tendermint/types" -) - -//------------------------------------------------ -// validator types -//------------------------------------------------ - -//------------------------------------------------ -// simple validator set and validator (just crypto, no network) - -// validator set (independent of chains) -type ValidatorSet struct { - ID string `json:"id"` - Validators []*Validator `json:"validators"` -} - -func (vs *ValidatorSet) Validator(valID string) (*Validator, error) { - for _, v := range vs.Validators { - if v.ID == valID { - return v, nil - } - } - return nil, fmt.Errorf("Unknwon validator %s", valID) -} - -// validator (independent of chain) -type Validator struct { - ID string `json:"id"` - PubKey crypto.PubKey `json:"pub_key"` - Chains []string `json:"chains,omitempty"` // TODO: put this elsewhere (?) -} - -//------------------------------------------------ -// Live validator on a chain - -// Validator on a chain -// Returned over RPC but also used to manage state -// Responsible for communication with the validator -type ValidatorState struct { - Config *ValidatorConfig `json:"config"` - Status *ValidatorStatus `json:"status"` - - // Currently we get IPs and dial, - // but should reverse so the nodes dial the netmon, - // both for node privacy and easier reconfig (validators changing ip/port) - em *eventmeter.EventMeter // holds a ws connection to the val - client *client.ClientURI // rpc client -} - -// Start a new event meter, including the websocket connection -// Also create the http rpc client for convenienve -func (vs *ValidatorState) Start() error { - // we need the lock because RPCAddr can be updated concurrently - vs.Config.mtx.Lock() - rpcAddr := vs.Config.RPCAddr - vs.Config.mtx.Unlock() - - em := eventmeter.NewEventMeter(rpcAddr, UnmarshalEvent) - if _, err := em.Start(); err != nil { - return err - } - vs.em = em - vs.client = client.NewClientURI(fmt.Sprintf("http://%s", rpcAddr)) - return nil -} - -func (vs *ValidatorState) Stop() { - vs.em.Stop() -} - -func (vs *ValidatorState) EventMeter() *eventmeter.EventMeter { - return vs.em -} - -func (vs *ValidatorState) NewBlock(block *tmtypes.Header) { - vs.Status.mtx.Lock() - defer vs.Status.mtx.Unlock() - vs.Status.BlockHeight = block.Height -} - -func (vs *ValidatorState) UpdateLatency(latency float64) float64 { - vs.Status.mtx.Lock() - defer vs.Status.mtx.Unlock() - old := vs.Status.Latency - vs.Status.Latency = latency - return old -} - -func (vs *ValidatorState) SetOnline(isOnline bool) { - vs.Status.mtx.Lock() - defer vs.Status.mtx.Unlock() - vs.Status.Online = isOnline -} - -// Return the validators pubkey. If it's not yet set, get it from the node -// TODO: proof that it's the node's key -// XXX: Is this necessary? Why would it not be set -func (vs *ValidatorState) PubKey() crypto.PubKey { - if vs.Config.Validator.PubKey != nil { - return vs.Config.Validator.PubKey - } - - var result ctypes.TMResult - _, err := vs.client.Call("status", nil, &result) - if err != nil { - log.Error("Error getting validator pubkey", "addr", vs.Config.RPCAddr, "val", vs.Config.Validator.ID, "error", err) - return nil - } - status := result.(*ctypes.ResultStatus) - vs.Config.Validator.PubKey = status.PubKey - return vs.Config.Validator.PubKey -} - -type ValidatorConfig struct { - mtx sync.Mutex - Validator *Validator `json:"validator"` - P2PAddr string `json:"p2p_addr"` - RPCAddr string `json:"rpc_addr"` - Index int `json:"index,omitempty"` -} - -// TODO: update p2p address - -func (vc *ValidatorConfig) UpdateRPCAddress(rpcAddr string) { - vc.mtx.Lock() - defer vc.mtx.Unlock() - vc.RPCAddr = rpcAddr -} - -type ValidatorStatus struct { - mtx sync.Mutex - Online bool `json:"online"` - Latency float64 `json:"latency" wire:"unsafe"` - BlockHeight int `json:"block_height"` -} - -//------------------------------------------------------------ -// utility - -// Unmarshal a json event -func UnmarshalEvent(b json.RawMessage) (string, events.EventData, error) { - var err error - result := new(ctypes.TMResult) - wire.ReadJSONPtr(result, b, &err) - if err != nil { - return "", nil, err - } - event, ok := (*result).(*ctypes.ResultEvent) - if !ok { - return "", nil, nil // TODO: handle non-event messages (ie. return from subscribe/unsubscribe) - // fmt.Errorf("Result is not type *ctypes.ResultEvent. Got %v", reflect.TypeOf(*result)) - } - return event.Name, event.Data, nil -}