├── .gitignore ├── .readthedocs.yml ├── .stylish-haskell.yaml ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── NOTICE ├── README.rst ├── Setup.hs ├── app └── cardano-rt-view.hs ├── cabal.project ├── cardano-rt-view.cabal ├── default.nix ├── doc ├── .sphinx │ ├── _templates │ │ └── layout.html │ └── requirements.txt ├── conf.py ├── getting-started │ ├── building-rt-view-from-sources.md │ ├── building-the-documentation.md │ ├── faq.md │ ├── install.md │ ├── node-configuration.md │ └── rt-view-configuration.md ├── gui-overview │ └── overview.md ├── images │ └── screenshot.png ├── index.rst ├── technical-details │ ├── email-notifications.md │ └── understanding-metrics.md └── use-cases │ ├── different-machines.md │ └── liveview-to-rtview.md ├── nix ├── darwin-release.nix ├── default.nix ├── haskell.nix ├── linux-release.nix ├── pkgs.nix ├── regenerate.sh ├── sources.json ├── sources.nix ├── update-iohk-nix.sh ├── util.nix └── windows-release.nix ├── release.nix ├── scripts └── cabal-inside-nix-shell.sh ├── shell.nix ├── src └── Cardano │ ├── RTView.hs │ └── RTView │ ├── Acceptor.hs │ ├── CLI.hs │ ├── Config.hs │ ├── EKG.hs │ ├── ErrorBuffer.hs │ ├── GUI │ ├── CSS │ │ └── Style.hs │ ├── Elements.hs │ ├── JS │ │ ├── Charts.hs │ │ └── Utils.hs │ ├── Markup │ │ ├── Notifications.hs │ │ ├── OwnInfo.hs │ │ ├── PageBody.hs │ │ └── Pane.hs │ └── Updater.hs │ ├── Git │ ├── Rev.hs │ └── RevTH.hs │ ├── NodeState │ ├── CSV.hs │ ├── Parsers.hs │ ├── Types.hs │ └── Updater.hs │ ├── Notifications │ ├── CheckEvents.hs │ ├── Send.hs │ ├── Send │ │ └── Email.hs │ └── Types.hs │ ├── SupportedNodes.hs │ └── WebServer.hs ├── static ├── css │ └── w3.css ├── images │ ├── bars.svg │ ├── bell-slash.svg │ ├── bell.svg │ ├── blockchain.svg │ ├── bugs.svg │ ├── cardano-logo.svg │ ├── clipboard.svg │ ├── cpu.svg │ ├── disk.svg │ ├── dropdown-blue.svg │ ├── dropdown-dark.svg │ ├── dropdown-light.svg │ ├── file-download.svg │ ├── filter.svg │ ├── hide.svg │ ├── info-light.svg │ ├── info.svg │ ├── key.svg │ ├── memory.svg │ ├── mempool.svg │ ├── network.svg │ ├── peers.svg │ ├── question.svg │ ├── resources.svg │ ├── rts.svg │ ├── search.svg │ ├── show.svg │ ├── sort.svg │ ├── times.svg │ └── trash.svg └── js │ └── chart.js └── test ├── rt-view-analyzer ├── LICENSE ├── NOTICE ├── README.md ├── Setup.hs ├── configuration │ └── rt-view-config.yaml ├── logObjects.json ├── rt-view-analyzer.cabal ├── scripts │ ├── runTest.sh │ └── sender.sh └── src │ ├── Analyzers.hs │ ├── CLI.hs │ ├── Config.hs │ └── Main.hs └── suite ├── Test └── Cardano │ └── RTView │ └── HtmlCheck.hs └── cardano-rt-view-test.hs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Editors 2 | ## 3 | *~ 4 | \#* 5 | \.#* 6 | *.swp 7 | .dir-locals.el 8 | tags 9 | 10 | ## Build artifacts 11 | ## 12 | /bin 13 | /dist 14 | /dist-newstyle 15 | .stack-work/ 16 | 17 | .ghc.environment.* 18 | stack.yaml.lock 19 | 20 | ## Runtime artifacts 21 | ## 22 | result* 23 | /logs 24 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | 4 | sphinx: 5 | configuration: doc/conf.py 6 | 7 | # Optionally set the version of Python and requirements required to build your docs 8 | python: 9 | version: 3.7 10 | install: 11 | - requirements: doc/.sphinx/requirements.txt 12 | -------------------------------------------------------------------------------- /.stylish-haskell.yaml: -------------------------------------------------------------------------------- 1 | # stylish-haskell configuration file 2 | # ================================== 3 | 4 | # The stylish-haskell tool is mainly configured by specifying steps. These steps 5 | # are a list, so they have an order, and one specific step may appear more than 6 | # once (if needed). Each file is processed by these steps in the given order. 7 | steps: 8 | # Convert some ASCII sequences to their Unicode equivalents. This is disabled 9 | # by default. 10 | # - unicode_syntax: 11 | # # In order to make this work, we also need to insert the UnicodeSyntax 12 | # # language pragma. If this flag is set to true, we insert it when it's 13 | # # not already present. You may want to disable it if you configure 14 | # # language extensions using some other method than pragmas. Default: 15 | # # true. 16 | # add_language_pragma: true 17 | 18 | # Format record definitions. This is disabled by default. 19 | # 20 | # You can control the layout of record fields. The only rules that can't be configured 21 | # are these: 22 | # 23 | # - "|" is always aligned with "=" 24 | # - "," in fields is always aligned with "{" 25 | # - "}" is likewise always aligned with "{" 26 | # 27 | - records: 28 | # # How to format equals sign between type constructor and data constructor. 29 | # # Possible values: 30 | # # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor. 31 | # # - "indent N" -- insert a new line and N spaces from the beginning of the next line. 32 | equals: "indent 2" 33 | # 34 | # # How to format first field of each record constructor. 35 | # # Possible values: 36 | # # - "same_line" -- "{" and first field goes on the same line as the data constructor. 37 | # # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor 38 | first_field: "indent 2" 39 | # 40 | # # How many spaces to insert between the column with "," and the beginning of the comment in the next line. 41 | field_comment: 2 42 | # 43 | # # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines. 44 | deriving: 2 45 | 46 | # Align the right hand side of some elements. This is quite conservative 47 | # and only applies to statements where each element occupies a single 48 | # line. All default to true. 49 | - simple_align: 50 | cases: false 51 | top_level_patterns: true 52 | records: true 53 | 54 | # Import cleanup 55 | - imports: 56 | # There are different ways we can align names and lists. 57 | # 58 | # - global: Align the import names and import list throughout the entire 59 | # file. 60 | # 61 | # - file: Like global, but don't add padding when there are no qualified 62 | # imports in the file. 63 | # 64 | # - group: Only align the imports per group (a group is formed by adjacent 65 | # import lines). 66 | # 67 | # - none: Do not perform any alignment. 68 | # 69 | # Default: global. 70 | align: global 71 | 72 | # The following options affect only import list alignment. 73 | # 74 | # List align has following options: 75 | # 76 | # - after_alias: Import list is aligned with end of import including 77 | # 'as' and 'hiding' keywords. 78 | # 79 | # > import qualified Data.List as List (concat, foldl, foldr, head, 80 | # > init, last, length) 81 | # 82 | # - with_alias: Import list is aligned with start of alias or hiding. 83 | # 84 | # > import qualified Data.List as List (concat, foldl, foldr, head, 85 | # > init, last, length) 86 | # 87 | # - with_module_name: Import list is aligned `list_padding` spaces after 88 | # the module name. 89 | # 90 | # > import qualified Data.List as List (concat, foldl, foldr, head, 91 | # init, last, length) 92 | # 93 | # This is mainly intended for use with `pad_module_names: false`. 94 | # 95 | # > import qualified Data.List as List (concat, foldl, foldr, head, 96 | # init, last, length, scanl, scanr, take, drop, 97 | # sort, nub) 98 | # 99 | # - new_line: Import list starts always on new line. 100 | # 101 | # > import qualified Data.List as List 102 | # > (concat, foldl, foldr, head, init, last, length) 103 | # 104 | # Default: after_alias 105 | #list_align: with_module_name 106 | list_align: after_alias 107 | 108 | # Right-pad the module names to align imports in a group: 109 | # 110 | # - true: a little more readable 111 | # 112 | # > import qualified Data.List as List (concat, foldl, foldr, 113 | # > init, last, length) 114 | # > import qualified Data.List.Extra as List (concat, foldl, foldr, 115 | # > init, last, length) 116 | # 117 | # - false: diff-safe 118 | # 119 | # > import qualified Data.List as List (concat, foldl, foldr, init, 120 | # > last, length) 121 | # > import qualified Data.List.Extra as List (concat, foldl, foldr, 122 | # > init, last, length) 123 | # 124 | # Default: true 125 | pad_module_names: false 126 | 127 | # Long list align style takes effect when import is too long. This is 128 | # determined by 'columns' setting. 129 | # 130 | # - inline: This option will put as much specs on same line as possible. 131 | # 132 | # - new_line: Import list will start on new line. 133 | # 134 | # - new_line_multiline: Import list will start on new line when it's 135 | # short enough to fit to single line. Otherwise it'll be multiline. 136 | # 137 | # - multiline: One line per import list entry. 138 | # Type with constructor list acts like single import. 139 | # 140 | # > import qualified Data.Map as M 141 | # > ( empty 142 | # > , singleton 143 | # > , ... 144 | # > , delete 145 | # > ) 146 | # 147 | # Default: inline 148 | long_list_align: inline 149 | 150 | # Align empty list (importing instances) 151 | # 152 | # Empty list align has following options 153 | # 154 | # - inherit: inherit list_align setting 155 | # 156 | # - right_after: () is right after the module name: 157 | # 158 | # > import Vector.Instances () 159 | # 160 | # Default: inherit 161 | empty_list_align: inherit 162 | 163 | # List padding determines indentation of import list on lines after import. 164 | # This option affects 'long_list_align'. 165 | # 166 | # - : constant value 167 | # 168 | # - module_name: align under start of module name. 169 | # Useful for 'file' and 'group' align settings. 170 | # 171 | # Default: 4 172 | list_padding: 4 173 | 174 | # Separate lists option affects formatting of import list for type 175 | # or class. The only difference is single space between type and list 176 | # of constructors, selectors and class functions. 177 | # 178 | # - true: There is single space between Foldable type and list of it's 179 | # functions. 180 | # 181 | # > import Data.Foldable (Foldable (fold, foldl, foldMap)) 182 | # 183 | # - false: There is no space between Foldable type and list of it's 184 | # functions. 185 | # 186 | # > import Data.Foldable (Foldable(fold, foldl, foldMap)) 187 | # 188 | # Default: true 189 | separate_lists: true 190 | 191 | # Space surround option affects formatting of import lists on a single 192 | # line. The only difference is single space after the initial 193 | # parenthesis and a single space before the terminal parenthesis. 194 | # 195 | # - true: There is single space associated with the enclosing 196 | # parenthesis. 197 | # 198 | # > import Data.Foo ( foo ) 199 | # 200 | # - false: There is no space associated with the enclosing parenthesis 201 | # 202 | # > import Data.Foo (foo) 203 | # 204 | # Default: false 205 | space_surround: false 206 | 207 | # Language pragmas 208 | - language_pragmas: 209 | # We can generate different styles of language pragma lists. 210 | # 211 | # - vertical: Vertical-spaced language pragmas, one per line. 212 | # 213 | # - compact: A more compact style. 214 | # 215 | # - compact_line: Similar to compact, but wrap each line with 216 | # `{-#LANGUAGE #-}'. 217 | # 218 | # Default: vertical. 219 | style: vertical 220 | 221 | # Align affects alignment of closing pragma brackets. 222 | # 223 | # - true: Brackets are aligned in same column. 224 | # 225 | # - false: Brackets are not aligned together. There is only one space 226 | # between actual import and closing bracket. 227 | # 228 | # Default: true 229 | align: false 230 | 231 | # stylish-haskell can detect redundancy of some language pragmas. If this 232 | # is set to true, it will remove those redundant pragmas. Default: true. 233 | remove_redundant: true 234 | 235 | # Language prefix to be used for pragma declaration, this allows you to 236 | # use other options non case-sensitive like "language" or "Language". 237 | # If a non correct String is provided, it will default to: LANGUAGE. 238 | language_prefix: LANGUAGE 239 | 240 | # Replace tabs by spaces. This is disabled by default. 241 | # - tabs: 242 | # # Number of spaces to use for each tab. Default: 8, as specified by the 243 | # # Haskell report. 244 | # spaces: 8 245 | 246 | # Remove trailing whitespace 247 | - trailing_whitespace: {} 248 | 249 | # Squash multiple spaces between the left and right hand sides of some 250 | # elements into single spaces. Basically, this undoes the effect of 251 | # simple_align but is a bit less conservative. 252 | # - squash: {} 253 | 254 | # A common setting is the number of columns (parts of) code will be wrapped 255 | # to. Different steps take this into account. 256 | # 257 | # Set this to null to disable all line wrapping. 258 | # 259 | # Default: 80. 260 | columns: 100 261 | 262 | # By default, line endings are converted according to the OS. You can override 263 | # preferred format here. 264 | # 265 | # - native: Native newline format. CRLF on Windows, LF on other OSes. 266 | # 267 | # - lf: Convert to LF ("\n"). 268 | # 269 | # - crlf: Convert to CRLF ("\r\n"). 270 | # 271 | # Default: native. 272 | newline: native 273 | 274 | # Sometimes, language extensions are specified in a cabal file or from the 275 | # command line instead of using language pragmas in the file. stylish-haskell 276 | # needs to be aware of these, so it can parse the file correctly. 277 | # 278 | # No language extensions are enabled by default. 279 | language_extensions: 280 | - BangPatterns 281 | - ConstraintKinds 282 | - DataKinds 283 | - DefaultSignatures 284 | - DeriveDataTypeable 285 | - DeriveGeneric 286 | - ExistentialQuantification 287 | - FlexibleContexts 288 | - FlexibleInstances 289 | - FunctionalDependencies 290 | - GADTs 291 | - GeneralizedNewtypeDeriving 292 | - LambdaCase 293 | - MultiParamTypeClasses 294 | - MultiWayIf 295 | - NoImplicitPrelude 296 | - OverloadedStrings 297 | - PolyKinds 298 | - RecordWildCards 299 | - ScopedTypeVariables 300 | - StandaloneDeriving 301 | - TemplateHaskell 302 | - TupleSections 303 | - TypeApplications 304 | - TypeFamilies 305 | - ViewPatterns 306 | - ExplicitNamespaces 307 | 308 | # Attempt to find the cabal file in ancestors of the current directory, and 309 | # parse options (currently only language extensions) from that. 310 | # 311 | # Default: true 312 | cabal: true 313 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | ## [0.3.0] 5 | 6 | ### Added 7 | 8 | - Email notifications ([#121](https://github.com/input-output-hk/cardano-rt-view/pull/121), [#141](https://github.com/input-output-hk/cardano-rt-view/pull/141)) 9 | - Display RTView information ([#112](https://github.com/input-output-hk/cardano-rt-view/pull/112)) 10 | - Add RTView local log ([#116](https://github.com/input-output-hk/cardano-rt-view/pull/116)) 11 | - Configure node panes' placement on the web-page ([#119](https://github.com/input-output-hk/cardano-rt-view/pull/119)) 12 | 13 | ### Fixed 14 | 15 | - Space leak fixed ([#113](https://github.com/input-output-hk/cardano-rt-view/pull/113), [#139](https://github.com/input-output-hk/cardano-rt-view/pull/139)) 16 | - Handling of errors in `Errors` tab is fixed ([#143](https://github.com/input-output-hk/cardano-rt-view/pull/143), [#144](https://github.com/input-output-hk/cardano-rt-view/pull/144)) 17 | 18 | ### Changed 19 | 20 | - One single view mode instead of two ones ([#117](https://github.com/input-output-hk/cardano-rt-view/pull/117)) 21 | - Update node's metrics namespaces ([#118](https://github.com/input-output-hk/cardano-rt-view/pull/118)) 22 | 23 | ## [0.2.0] 24 | 25 | ### Added 26 | 27 | - New CLI parameters `--version` and `--supported-nodes` ([#77](https://github.com/input-output-hk/cardano-rt-view/pull/77), [#83](https://github.com/input-output-hk/cardano-rt-view/pull/83)) 28 | - New `Idle` tag for the node ([#82](https://github.com/input-output-hk/cardano-rt-view/pull/82)) 29 | - New field `Blockchain start time` ([#96](https://github.com/input-output-hk/cardano-rt-view/pull/96)) 30 | - New field `Node start time` ([#97](https://github.com/input-output-hk/cardano-rt-view/pull/97)) 31 | - Now RTView knows the nodes it can work with: check/show the list of supported versions ([#83](https://github.com/input-output-hk/cardano-rt-view/pull/83)) 32 | - New features in `Errors` tab: sorting, filtering, exporting, removing ([#87](https://github.com/input-output-hk/cardano-rt-view/pull/87)) 33 | 34 | ### Fixed 35 | 36 | - UTF-8 encoding in Windows terminal is setting automatically ([#66](https://github.com/input-output-hk/cardano-rt-view/pull/66)) 37 | - Dynamic Y-axis for 'Resources' charts ([#86](https://github.com/input-output-hk/cardano-rt-view/pull/86)) 38 | - Fixed `Node commit` link ([#103](https://github.com/input-output-hk/cardano-rt-view/pull/103)) 39 | 40 | ### Changed 41 | 42 | - New way how RTView receives node's basic info ([#75](https://github.com/input-output-hk/cardano-rt-view/pull/75)). **IMPORTANT**: the node's version must be `1.22.1` or higher to work with it properly. 43 | - Networking sockets connection is used by default during interactive dialog ([#80](https://github.com/input-output-hk/cardano-rt-view/pull/80)) 44 | - Suggested examples of the node's configuration: JSON instead of YAML ([#79](https://github.com/input-output-hk/cardano-rt-view/pull/79)) 45 | - Optimized web-page traffic: update DOM-elements only if needed ([#84](https://github.com/input-output-hk/cardano-rt-view/pull/84)) 46 | - Documentation improvements ([#62](https://github.com/input-output-hk/cardano-rt-view/pull/62), [#81](https://github.com/input-output-hk/cardano-rt-view/pull/81)) 47 | - Internal refactoring ([#76](https://github.com/input-output-hk/cardano-rt-view/pull/76), [#85](https://github.com/input-output-hk/cardano-rt-view/pull/85), [#104](https://github.com/input-output-hk/cardano-rt-view/pull/104)) 48 | - Moved to GHC 8.10.2 ([#65](https://github.com/input-output-hk/cardano-rt-view/pull/65)) 49 | 50 | ## [0.1.0] 51 | 52 | This is the first release of RTView. 53 | 54 | ### Added 55 | 56 | - Cardano-like re-design ([#29](https://github.com/input-output-hk/cardano-rt-view/pull/29)) 57 | - Interactive dialog ([#12](https://github.com/input-output-hk/cardano-rt-view/pull/12), [#28](https://github.com/input-output-hk/cardano-rt-view/pull/28), [#49](https://github.com/input-output-hk/cardano-rt-view/pull/49)) 58 | - Documentation ([#37](https://github.com/input-output-hk/cardano-rt-view/pull/37), [#56](https://github.com/input-output-hk/cardano-rt-view/pull/56)). 59 | 60 | ### Fixed 61 | 62 | - Fixed distributed work mode ([#50](https://github.com/input-output-hk/cardano-rt-view/pull/50)) 63 | - Improved GUI UX ([#27](https://github.com/input-output-hk/cardano-rt-view/pull/27), [#30](https://github.com/input-output-hk/cardano-rt-view/pull/30), [#33](https://github.com/input-output-hk/cardano-rt-view/pull/33), [#38](https://github.com/input-output-hk/cardano-rt-view/pull/38), [#39](https://github.com/input-output-hk/cardano-rt-view/pull/39), [#48](https://github.com/input-output-hk/cardano-rt-view/pull/48)) 64 | - Node uptime with days ([#26](https://github.com/input-output-hk/cardano-rt-view/pull/26)) 65 | - Fixed suggested pipe on Windows ([#25](https://github.com/input-output-hk/cardano-rt-view/pull/25)) 66 | - Corrected peers number ([#24](https://github.com/input-output-hk/cardano-rt-view/pull/24)) 67 | 68 | ### Changed 69 | 70 | - No changes yet, it is the first release. 71 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # General reviewers per PR 2 | * @codiepp @deepfire @denisshevchenko 3 | 4 | # Specific reviewers for code pieces 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Input Output (Hong Kong) Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. raw:: html 2 | 3 |

4 | 5 |   6 | 7 |   8 | 9 |

10 | 11 | ******************************************** 12 | RTView: real-time watching for Cardano nodes 13 | ******************************************** 14 | 15 | IMPORTANT 16 | ========= 17 | 18 | This project is archived, its development was stopped 2 years ago. 19 | 20 | Please check out our brand new RTView `here `_. 21 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/cardano-rt-view.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | 3 | #if defined(mingw32_HOST_OS) 4 | import System.IO (hSetEncoding, stdout, stderr, utf8) 5 | import System.Win32.Console (setConsoleCP) 6 | #endif 7 | 8 | import Data.Text (unpack) 9 | import Data.Version (showVersion) 10 | import Options.Applicative (ParserInfo, (<**>), customExecParser, fullDesc, header, 11 | help, helper, info, infoOption, long, prefs, short, 12 | showHelpOnEmpty) 13 | 14 | import Cardano.RTView (runCardanoRTView) 15 | import Cardano.RTView.CLI (RTViewParams, parseRTViewParams) 16 | import Cardano.RTView.SupportedNodes (showSupportedNodesVersions) 17 | import Paths_cardano_rt_view (version) 18 | 19 | main :: IO () 20 | main = do 21 | #if defined(mingw32_HOST_OS) 22 | -- Unfortunately, the terminal in Windows 10 isn't UTF8-ready by default. 23 | -- Set encoding and code page explicitly. 24 | hSetEncoding stdout utf8 25 | hSetEncoding stderr utf8 26 | setConsoleCP 65001 27 | #endif 28 | rtViewParams <- customExecParser (prefs showHelpOnEmpty) rtViewInfo 29 | runCardanoRTView rtViewParams 30 | where 31 | rtViewInfo :: ParserInfo RTViewParams 32 | rtViewInfo = info 33 | (parseRTViewParams <**> helper <**> versionOption <**> supportedNodesOption) 34 | (fullDesc <> header "cardano-rt-view - real-time view for cardano node.") 35 | versionOption = infoOption 36 | (showVersion version) 37 | (long "version" <> 38 | short 'v' <> 39 | help "Show version") 40 | supportedNodesOption = infoOption 41 | ("Supported versions of Cardano node: " <> unpack showSupportedNodesVersions) 42 | (long "supported-nodes" <> 43 | help "Show supported versions of Cardano node") 44 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | index-state: 2020-10-22T00:00:00Z 2 | 3 | packages: 4 | ./*.cabal 5 | test/rt-view-analyzer/*.cabal 6 | 7 | allow-newer: base 8 | 9 | -- related to cardano-ledger-specs: 10 | -- always write GHC env files, because they are needed by the doctests. 11 | write-ghc-environment-files: always 12 | 13 | -- --------------------------------------------------------- 14 | -- Disable all tests by default 15 | 16 | tests: False 17 | 18 | test-show-details: direct 19 | 20 | -- Then enable specific tests in this repo 21 | 22 | package cardano-rt-view 23 | tests: True 24 | 25 | -- required for nix: 26 | 27 | package ouroboros-network 28 | tests: False 29 | 30 | ---------- 8< ----------- 31 | source-repository-package 32 | type: git 33 | location: https://github.com/input-output-hk/iohk-monitoring-framework 34 | tag: 86f2dfc8db25133c95494318fe656155ac6a5754 35 | --sha256: 0zbmpg5ircgy80ss57aa4nqfk0p0k01y3chdjqs41s52swiypss5 36 | subdir: iohk-monitoring 37 | 38 | source-repository-package 39 | type: git 40 | location: https://github.com/input-output-hk/iohk-monitoring-framework 41 | tag: 86f2dfc8db25133c95494318fe656155ac6a5754 42 | --sha256: 0zbmpg5ircgy80ss57aa4nqfk0p0k01y3chdjqs41s52swiypss5 43 | subdir: contra-tracer 44 | 45 | source-repository-package 46 | type: git 47 | location: https://github.com/input-output-hk/iohk-monitoring-framework 48 | tag: 86f2dfc8db25133c95494318fe656155ac6a5754 49 | --sha256: 0zbmpg5ircgy80ss57aa4nqfk0p0k01y3chdjqs41s52swiypss5 50 | subdir: plugins/backend-trace-acceptor 51 | 52 | source-repository-package 53 | type: git 54 | location: https://github.com/input-output-hk/iohk-monitoring-framework 55 | tag: 86f2dfc8db25133c95494318fe656155ac6a5754 56 | --sha256: 0zbmpg5ircgy80ss57aa4nqfk0p0k01y3chdjqs41s52swiypss5 57 | subdir: tracer-transformers 58 | 59 | source-repository-package 60 | type: git 61 | location: https://github.com/input-output-hk/ouroboros-network 62 | tag: 7cda58405b6ee0d335b11e88e5c9989c7a3a6e03 63 | --sha256: 0zxmp001mixrba1fzjgzcjf6vl6i5d3q837267njyvmkajdrxgx7 64 | subdir: Win32-network 65 | -------------------------------------------------------------------------------- /cardano-rt-view.cabal: -------------------------------------------------------------------------------- 1 | name: cardano-rt-view 2 | version: 0.3.0 3 | description: Real-time watching for Cardano nodes 4 | author: IOHK 5 | maintainer: operations@iohk.io 6 | license: Apache-2.0 7 | license-files: 8 | LICENSE 9 | NOTICE 10 | build-type: Simple 11 | cabal-version: >= 1.10 12 | 13 | library 14 | hs-source-dirs: src 15 | 16 | exposed-modules: Cardano.RTView 17 | Cardano.RTView.CLI 18 | Cardano.RTView.Config 19 | 20 | Cardano.RTView.Acceptor 21 | 22 | Cardano.RTView.EKG 23 | 24 | Cardano.RTView.ErrorBuffer 25 | 26 | Cardano.RTView.GUI.CSS.Style 27 | Cardano.RTView.GUI.Elements 28 | Cardano.RTView.GUI.JS.Charts 29 | Cardano.RTView.GUI.JS.Utils 30 | Cardano.RTView.GUI.Markup.Notifications 31 | Cardano.RTView.GUI.Markup.OwnInfo 32 | Cardano.RTView.GUI.Markup.PageBody 33 | Cardano.RTView.GUI.Markup.Pane 34 | Cardano.RTView.GUI.Updater 35 | 36 | Cardano.RTView.Git.Rev 37 | Cardano.RTView.Git.RevTH 38 | 39 | Cardano.RTView.NodeState.CSV 40 | Cardano.RTView.NodeState.Parsers 41 | Cardano.RTView.NodeState.Types 42 | Cardano.RTView.NodeState.Updater 43 | 44 | Cardano.RTView.Notifications.CheckEvents 45 | Cardano.RTView.Notifications.Send 46 | Cardano.RTView.Notifications.Send.Email 47 | Cardano.RTView.Notifications.Types 48 | 49 | Cardano.RTView.SupportedNodes 50 | 51 | Cardano.RTView.WebServer 52 | 53 | other-modules: Paths_cardano_rt_view 54 | 55 | build-depends: aeson 56 | , aeson-pretty 57 | , ansi-terminal 58 | , async 59 | , base >=4.12 && <5 60 | , bytestring 61 | , clay 62 | , cassava 63 | , containers 64 | , deepseq 65 | , directory 66 | , ekg-core 67 | , extra 68 | , file-embed 69 | , filepath 70 | , formatting 71 | , hashable 72 | , iohk-monitoring 73 | , lobemo-backend-trace-acceptor 74 | , mime-mail 75 | , optparse-applicative 76 | , process 77 | , smtp-mail == 0.3.0.0 78 | , stm 79 | , template-haskell 80 | , text 81 | , threepenny-gui 82 | , time 83 | , unordered-containers 84 | 85 | default-language: Haskell2010 86 | default-extensions: OverloadedStrings 87 | ScopedTypeVariables 88 | 89 | ghc-options: -Wall 90 | -fno-warn-all-missed-specialisations 91 | -fno-warn-implicit-prelude 92 | -fno-warn-missing-import-lists 93 | -fno-warn-orphans 94 | -fno-warn-safe 95 | -fno-warn-unsafe 96 | 97 | executable cardano-rt-view 98 | hs-source-dirs: app 99 | main-is: cardano-rt-view.hs 100 | 101 | other-modules: Paths_cardano_rt_view 102 | 103 | build-depends: base >=4.12 && <5 104 | , cardano-rt-view 105 | , optparse-applicative 106 | , text 107 | if os(windows) 108 | build-depends: Win32 109 | 110 | default-language: Haskell2010 111 | 112 | ghc-options: -threaded 113 | -Wall 114 | -rtsopts 115 | "-with-rtsopts=-T" 116 | 117 | test-suite cardano-rt-view-test 118 | hs-source-dirs: test/suite 119 | main-is: cardano-rt-view-test.hs 120 | 121 | other-modules: Test.Cardano.RTView.HtmlCheck 122 | 123 | type: exitcode-stdio-1.0 124 | 125 | build-depends: base >=4.12 && <5 126 | , cardano-rt-view 127 | , tasty 128 | , tasty-hunit 129 | , threepenny-gui 130 | 131 | default-language: Haskell2010 132 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem 2 | , crossSystem ? null 3 | # allows to cutomize haskellNix (ghc and profiling, see ./nix/haskell.nix) 4 | , config ? {} 5 | # allows to override dependencies of the project without modifications, 6 | # eg. to test build against local checkout of nixpkgs and iohk-nix: 7 | # nix build -f default.nix cardano-node --arg sourcesOverride '{ 8 | # iohk-nix = ../iohk-nix; 9 | # }' 10 | , sourcesOverride ? {} 11 | # override scripts with custom configuration 12 | , customConfig ? {} 13 | # pinned version of nixpkgs augmented with overlays (iohk-nix and our packages). 14 | , pkgs ? import ./nix { inherit system crossSystem config sourcesOverride customConfig; } 15 | , gitrev ? pkgs.iohkNix.commitIdFromGitRepoOrZero ./.git 16 | }: 17 | with pkgs; with commonLib; 18 | let 19 | 20 | haskellPackages = recRecurseIntoAttrs 21 | # the Haskell.nix package set, reduced to local packages. 22 | (cardanoRTViewHaskellPackages.projectPackages); 23 | 24 | self = { 25 | inherit haskellPackages pkgs; 26 | 27 | # Grab the executable component of our package. 28 | inherit (haskellPackages.cardano-rt-view.components.exes) 29 | cardano-rt-view; 30 | inherit (haskellPackages.cardano-rt-view.identifier) version; 31 | 32 | # `tests` are the test suites which have been built. 33 | tests = collectComponents' "tests" haskellPackages; 34 | # `benchmarks` (only built, not run). 35 | benchmarks = collectComponents' "benchmarks" haskellPackages; 36 | 37 | exes = collectComponents' "exes" haskellPackages; 38 | 39 | checks = recurseIntoAttrs { 40 | # `checks.tests` collect results of executing the tests: 41 | tests = collectChecks haskellPackages; 42 | }; 43 | 44 | shell = import ./shell.nix { 45 | inherit pkgs; 46 | withHoogle = false; 47 | }; 48 | }; 49 | in self 50 | -------------------------------------------------------------------------------- /doc/.sphinx/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block footer %} 4 | {{ super() }} 5 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /doc/.sphinx/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==3.1.1 2 | sphinx-intl==2.0.1 3 | transifex-client==0.13.10 4 | testresources==2.0.1 5 | -e git+https://github.com/input-output-hk/sphinx_rtd_theme.git#egg=sphinx_rtd_theme 6 | recommonmark==0.6 7 | ## The following requirements were added by pip freeze: 8 | alabaster==0.7.12 9 | Babel==2.8.0 10 | certifi==2020.4.5.2 11 | chardet==3.0.4 12 | click==7.1.2 13 | sphinxcontrib-mermaid==0.4.0 14 | sphinxemoji==0.1.6 15 | sphinx_markdown_tables==0.0.15 16 | CommonMark==0.9.1 17 | docutils==0.16 18 | future==0.18.2 19 | idna==2.9 20 | imagesize==1.2.0 21 | Jinja2==2.11.2 22 | jsonpointer==2.0 23 | jsonref==0.2 24 | MarkupSafe==1.1.1 25 | Pygments==2.6.1 26 | pytz==2020.1 27 | requests==2.24.0 28 | six==1.15.0 29 | snowballstemmer==2.0.0 30 | sphinxcontrib-websupport==1.2.2 31 | urllib3==1.25.9 32 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import sphinx_rtd_theme 4 | import recommonmark 5 | 6 | from recommonmark.transform import AutoStructify 7 | from os.path import abspath, join, dirname 8 | 9 | sys.path.insert(0, abspath(join(dirname(__file__)))) 10 | 11 | # -- RTD configuration ------------------------------------------------ 12 | 13 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 14 | 15 | # This is used for linking and such so we link to the thing we're building 16 | rtd_version = os.environ.get("READTHEDOCS_VERSION", "latest") 17 | if rtd_version not in ["stable", "latest"]: 18 | rtd_version = "stable" 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'cardano-rt-view Documentation' 23 | copyright = '2020, IOHK' 24 | author = 'IOHK' 25 | 26 | # The full version, including alpha/beta/rc tags 27 | release = '0.3.0' 28 | 29 | # -- General configuration --------------------------------------------------- 30 | master_doc = 'index' 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | 35 | extensions = [ 36 | "sphinx_rtd_theme", 37 | 'recommonmark', 38 | 'sphinx_markdown_tables', 39 | 'sphinxemoji.sphinxemoji', 40 | "sphinx.ext.autodoc", 41 | "sphinx.ext.autosummary", 42 | "sphinx.ext.intersphinx", 43 | "sphinx.ext.viewcode", 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['.sphinx/_templates'] 48 | html_static_path = ['.sphinx/_static'] 49 | 50 | source_suffix = { 51 | '.rst': 'restructuredtext', 52 | '.md': 'markdown', 53 | } 54 | 55 | intersphinx_mapping = { 56 | "commandsv1": ( 57 | "https://robotpy.readthedocs.io/projects/commands-v1/en/%s/" 58 | % rtd_version, 59 | None, 60 | ), 61 | } 62 | 63 | # List of patterns, relative to source directory, that match files and 64 | # directories to ignore when looking for source files. 65 | # This pattern also affects html_static_path and html_extra_path. 66 | exclude_patterns = [] 67 | 68 | 69 | # -- Options for HTML output ------------------------------------------------- 70 | 71 | # The theme to use for HTML and HTML Help pages. See the documentation for 72 | # a list of builtin themes. 73 | # 74 | html_theme = "sphinx_rtd_theme" 75 | 76 | html_theme_options = { 77 | 'logo_only': False, 78 | 'display_version': False, 79 | 'prev_next_buttons_location': 'bottom', 80 | 'style_external_links': False, 81 | 'style_nav_header_background': '#0635a7', 82 | # Toc options 83 | 'collapse_navigation': True, 84 | 'sticky_navigation': True, 85 | 'navigation_depth': 4, 86 | 'includehidden': True, 87 | 'titles_only': False 88 | } 89 | 90 | # Add any paths that contain custom static files (such as style sheets) here, 91 | # relative to this directory. They are copied after the builtin static files, 92 | # so a file named "default.css" will overwrite the builtin "default.css". 93 | 94 | # html_logo = ".sphinx/*.png" 95 | 96 | html_context = { 97 | "display_github": True, # Add 'Edit on Github' link instead of 'View page source' 98 | "github_user": "input-output-hk", 99 | "github_repo": "cardano-rt-view", 100 | "github_version": "master", 101 | "conf_py_path": "/", 102 | "source_suffix": source_suffix, 103 | } 104 | 105 | # -- Custom Document processing ---------------------------------------------- 106 | 107 | def setup(app): 108 | app.add_config_value('recommonmark_config', { 109 | 'enable_auto_doc_ref': False, 110 | 'enable_auto_toc_tree': False, 111 | }, True) 112 | app.add_transform(AutoStructify) 113 | -------------------------------------------------------------------------------- /doc/getting-started/building-rt-view-from-sources.md: -------------------------------------------------------------------------------- 1 | # Building RTView 2 | 3 | RTView is a program implemented in the Haskell programming language. There are different ways how to build it from scratch, but this guide describes two recommended methods: 4 | 5 | 1. Using `cabal`; 6 | 2. Using `nix`. 7 | 8 | ## Prerequisites 9 | 10 | You will need: 11 | 12 | 1. An x86 host (AMD or Intel) with at least 2 cores, 8GB of RAM, and at least 10GB of free disk space; 13 | 2. A recent version of Linux, macOS, or Windows (see clarification below); 14 | 3. [git](https://git-scm.com/) program. 15 | 16 | ## Get the source code 17 | 18 | Clone RTView repository: 19 | 20 | ``` 21 | git clone https://github.com/input-output-hk/cardano-rt-view.git 22 | ``` 23 | 24 | ## Building using cabal 25 | 26 | ### Install GHC and cabal 27 | 28 | It is recommended to use [ghcup](https://www.haskell.org/ghcup/) to install GHC and cabal on your computer. Run this command (as a user other than `root`): 29 | 30 | ``` 31 | curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh 32 | ``` 33 | 34 | and follow the onscreen instructions. Please do not forget to restart your terminal to activate changes in your `PATH` variable. 35 | 36 | Now install and activate the required GHC version: 37 | 38 | ``` 39 | ghcup install ghc 8.6.5 40 | ghcup set ghc 8.6.5 41 | ``` 42 | 43 | Check GHC version: 44 | 45 | ``` 46 | ghc --version 47 | The Glorious Glasgow Haskell Compilation System, version 8.6.5 48 | ``` 49 | 50 | ### Building RTView 51 | 52 | Now go to the repository directory and run: 53 | 54 | ``` 55 | cabal build all 56 | ``` 57 | 58 | After the build is finished, you can run RTView using this command: 59 | 60 | ``` 61 | cabal exec -- cardano-rt-view 62 | ``` 63 | 64 | ## Building using nix 65 | 66 | Nix is a powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible. Please note that you **cannot** use `nix` on Windows (use `cabal` instead). 67 | 68 | The quickest way to install Nix is to run the following command (as a user other than `root` with `sudo` permission): 69 | 70 | ``` 71 | curl -L https://nixos.org/nix/install | sh 72 | ``` 73 | 74 | Make sure to follow the instructions output by this script. 75 | 76 | After `nix` is installed, go to the repository directory. 77 | 78 | ### Build RTView program 79 | 80 | Use the following command to build RTView program: 81 | 82 | ``` 83 | nix-build -A cardano-rt-view 84 | ``` 85 | After that, you will see a link `result` in the repository directory. This link points to the program. For example, on Linux, you will see something like this: 86 | 87 | ``` 88 | result -> /nix/store/xnff4qfrpxrsgwnfwc2i6jx5pj8bq3xi-cardano-rt-view-exe-cardano-rt-view-* 89 | ``` 90 | 91 | That directory contains `bin/cardano-rt-view` program. 92 | 93 | ### Build RTView release package 94 | 95 | You can also build a complete release package for your platform. It is similar to the archive available in the [stable releases](https://github.com/input-output-hk/cardano-rt-view/releases). To do it, please use the following commands: 96 | 97 | 1. `nix-build release.nix -A cardano-rt-view-linux-release` - for Linux; 98 | 2. `nix-build release.nix -A cardano-rt-view-darwin-release` - for macOS. 99 | 100 | After that, you will see a link `result` in the repository directory. This link points to the package. For example, on Linux, you will see something like this: 101 | 102 | ``` 103 | result -> /nix/store/fa0c47r743ra9nslpl86zp4pjpz8sygs-cardano-rt-view-*-linux-x86_64 104 | ``` 105 | 106 | That directory contains an archive `cardano-rt-view-*-linux-x86_64.tar.gz`. 107 | -------------------------------------------------------------------------------- /doc/getting-started/building-the-documentation.md: -------------------------------------------------------------------------------- 1 | # Building the documentation 2 | 3 | ## Prerequisites 4 | 5 | Please make sure you have Python 3 installed. If not - follow instructions from the [official Python page](https://www.python.org/downloads/). 6 | 7 | To install the documentation generator `sphinx-doc` on your platform, please follow [these instructions](https://www.sphinx-doc.org/en/master/usage/installation.html). 8 | 9 | ## Preparation 10 | 11 | Execute these commands: 12 | 13 | ``` 14 | sphinx-build -b html . builddir 15 | pip3 install sphinx-rtd-theme 16 | pip3 install recommonmark 17 | pip3 install sphinx_markdown_tables --user 18 | pip3 install sphinxemoji --user 19 | ``` 20 | 21 | ## Building documentation 22 | 23 | Use this command to build the documentation: 24 | 25 | ``` 26 | sphinx-build doc html 27 | ``` 28 | 29 | Now open the documentation at `html/index.html` page. 30 | -------------------------------------------------------------------------------- /doc/getting-started/faq.md: -------------------------------------------------------------------------------- 1 | # RTView: FAQ 2 | 3 | ## «I don't want to wait the next release - where can I find the latest builds of RTView?» 4 | 5 | You can download the latest build from [Hydra CI](https://hydra.iohk.io/jobset/Cardano/cardano-rt-view). 6 | 7 | ## «What should I use, network sockets, or named pipes?» 8 | 9 | Network sockets use the host and the port to connect RTView with your nodes. It looks like `123.45.67.89:3000`, where `123.45.67.89` is a public IP address and `3000` is an opened port. 10 | 11 | Named pipes use special files to connect RTView with your nodes. It looks like `/run/user/1000/rt-view-pipes/node-1` on Linux or `\\.\pipe\Users-Name-AppData-Local-Temp-_rt-view-pipes_node-1` on Windows. 12 | 13 | Use named pipes if your nodes and RTView are running on the **same** machine, and you don't want to open a port on your localhost. If your nodes and RTView are running on **different** machines, or you can open a port on your localhost - use network sockets. 14 | 15 | ## «If I want to run RTView on one machine and the node on other one - can I use the different OS on these machines?» 16 | 17 | Yes, you can. For example, your node can be launched on Linux machine, and RTView can be launched on Windows machine. 18 | 19 | ## «Where can I find an example of the node's configuration file for working with RTView?» 20 | 21 | You can find it [here](https://github.com/input-output-hk/cardano-rt-view/blob/master/doc/getting-started/node-configuration.md#cardano-node-configuration-file-complete-example). 22 | 23 | ## «How many nodes can I connect to my RTView process?» 24 | 25 | You can connect as many nodes as you want. 26 | 27 | ## «I know that particular error occurred, but I don't see it in the Errors tab. Why?» 28 | 29 | Please make sure you added corresponding tracer name in your node's configuration file. You can find the details [here](https://github.com/input-output-hk/cardano-rt-view/blob/master/doc/getting-started/node-configuration.md#errors-routing). 30 | -------------------------------------------------------------------------------- /doc/getting-started/install.md: -------------------------------------------------------------------------------- 1 | # Install RTView 2 | 3 | ## Prerequisites 4 | 5 | You will need: 6 | 7 | 1. An x86 host (AMD or Intel) with at least 2 cores, 4GB of RAM, and at least 100MB of free disk space; 8 | 2. A recent version of Linux, Windows or macOS; 9 | 3. Any modern web browser. 10 | 11 | ## Download and unpack 12 | 13 | Please go to [releases page](https://github.com/input-output-hk/cardano-rt-view/releases) and download an archive for your platform, their names look like this: 14 | 15 | 1. `cardano-rt-view-*-darwin.zip` 16 | 2. `cardano-rt-view-*-linux-x86_64.tar.gz` 17 | 3. `cardano-rt-view-*-win64.zip` 18 | 19 | Then unpack an archive, inside you will find an executable `cardano-rt-view`. 20 | 21 | ## macOS notes 22 | 23 | To prevent a system warning about "an application downloaded from the Internet", run this command: 24 | 25 | ``` 26 | xattr -r -d com.apple.quarantine cardano-rt-view-*-darwin/* 27 | ``` 28 | 29 | ## Run and configuration dialog 30 | 31 | After you run an executable `cardano-rt-view`, an interactive dialog will be started: 32 | 33 | ``` 34 | RTView: real-time watching for Cardano nodes 35 | 36 | Let's configure RTView... 37 | ``` 38 | 39 | The first question is: 40 | 41 | ``` 42 | How many nodes will you connect (1 - 99, default is 3): 43 | ``` 44 | 45 | Please input the number of `cardano-node` processes that should forward their metrics to RTView. Press the "Enter" key if you want to use the default value. 46 | 47 | The next question is: 48 | 49 | ``` 50 | Input the names of the nodes (default are "node-1", "node-2", "node-3"), one at a time: 51 | ``` 52 | 53 | From RTView's point of view, each `cardano-node` process that forwards its metrics to RTView, should be identified by a unique name. You can use any name you want. **IMPORTANT**: please note that name must not include _spaces_ or _dots_! 54 | 55 | The next question is: 56 | 57 | ``` 58 | Indicate the port for the web server (1024 - 65535, default is 8024): 59 | ``` 60 | 61 | Please input the port RTView will use to display the web-page. For example, if you keep the default port `8024`, the web-page will be available on `http://127.0.0.1:8024`. 62 | 63 | The next question is: 64 | 65 | ``` 66 | Indicate how your nodes should be connected with RTView: networking sockets or named pipes

." 67 | Default way is sockets, so if you are not sure - choose : 68 | ``` 69 | 70 | Please choose the way how the nodes should be connected to RTView. 71 | 72 | If you chose `S`, you will be asked about the base port: 73 | 74 | ``` 75 | Ok, sockets will be used. Indicate the base port to listen for connections (1024 - 65535, default is 3000): 76 | ``` 77 | 78 | The base port will be used for the first node that forwards its metrics to RTView. For example, if you will launch three `cardano-node` processes (`node-1`, `node-2`, and `node-3`) that will forward their metrics using network sockets, this is how they will be connected to RTView: 79 | 80 | 1. `node-1` -> `0.0.0.0:3000` 81 | 1. `node-2` -> `0.0.0.0:3001` 82 | 1. `node-3` -> `0.0.0.0:3002` 83 | 84 | Bit if you selected `P`, you will be asked about the directory when pipes will be created: 85 | 86 | ``` 87 | Ok, pipes will be used. Indicate the directory for them, default is "/run/user/1000/rt-view-pipes": 88 | ``` 89 | 90 | The next question is: 91 | 92 | ``` 93 | Now, indicate a host of machine RTView will be launched on (default is 0.0.0.0): 94 | ``` 95 | 96 | If your nodes are launched on the same machine with RTView, you can choose the default address. But if RTView will be launched on another machine, please specify its reachable public IP address. It will allow your nodes to connect with RTView. In this case, it is assumed that a machine with RTView is accessible using that IP address. 97 | 98 | The last question is: 99 | 100 | ``` 101 | Indicate the directory with static content for the web server, default is "static": 102 | ``` 103 | 104 | Since RTView displays nodes' metrics on the web-page, it uses static web content (CSS, JS, images). By default, it's `static` directory that is included in the archive you've downloaded. 105 | 106 | Then you will see this message: 107 | 108 | ``` 109 | Great, RTView is ready to run! Its configuration was saved at PATH_TO/rt-view.yaml. Press to continue... 110 | ``` 111 | 112 | where `PATH_TO` is the full path to the default system configuration directory. 113 | 114 | After you pressed `Enter`, RTView will show all the changes you have to make in your node(s) configuration file(s). For example, if you chose all default values on Linux, you will see this: 115 | 116 | ``` 117 | Now you have to make the following changes in your node's configuration file: 118 | 119 | 1. Find TurnOnLogMetrics flag and make sure it is true: 120 | 121 | "TurnOnLogMetrics": true 122 | 123 | 2. Since you have 3 nodes, add following traceForwardTo sections in the root of their configuration files: 124 | 125 | "traceForwardTo": { 126 | "tag": "RemoteSocket", 127 | "contents": [ 128 | "0.0.0.0", 129 | "3000" 130 | ] 131 | } 132 | 133 | "traceForwardTo": { 134 | "tag": "RemoteSocket", 135 | "contents": [ 136 | "0.0.0.0", 137 | "3001" 138 | ] 139 | } 140 | 141 | "traceForwardTo": { 142 | "tag": "RemoteSocket", 143 | "contents": [ 144 | "0.0.0.0", 145 | "3002" 146 | ] 147 | } 148 | 149 | After you are done, press to run RTView... 150 | ``` 151 | 152 | After you pressed `Enter`, RTView will be launched, and you can open `http://127.0.0.1:8024` (if you chose default web-port) and see the web-page. 153 | -------------------------------------------------------------------------------- /doc/getting-started/node-configuration.md: -------------------------------------------------------------------------------- 1 | # Cardano Node Configuration 2 | 3 | ## Prerequisites 4 | 5 | It is assumed that you already have at least one instance of `cardano-node`, which is already configured properly. If not - please read its [official documentation](https://docs.cardano.org/projects/cardano-node/en/latest/). 6 | 7 | ## RTView-related changes 8 | 9 | After you finished the RTView configuration dialog, it displayed all the changes you have to make in your nodes' configuration files. For example, on Linux with all default values accepted, it looks like this: 10 | 11 | ``` 12 | 1. Find TurnOnLogMetrics flag and make sure it is true: 13 | 14 | "TurnOnLogMetrics": true 15 | 16 | 2. Since you have 3 nodes, add following traceForwardTo sections in the root of their configuration files: 17 | 18 | "traceForwardTo": { 19 | "tag": "RemoteSocket", 20 | "contents": [ 21 | "0.0.0.0", 22 | "3000" 23 | ] 24 | } 25 | 26 | "traceForwardTo": { 27 | "tag": "RemoteSocket", 28 | "contents": [ 29 | "0.0.0.0", 30 | "3001" 31 | ] 32 | } 33 | 34 | "traceForwardTo": { 35 | "tag": "RemoteSocket", 36 | "contents": [ 37 | "0.0.0.0", 38 | "3002" 39 | ] 40 | } 41 | ``` 42 | 43 | ## Cardano node configuration file: complete example 44 | 45 | This is an example of the node's configuration file prepared for working with RTView on Linux: 46 | 47 | ``` 48 | { 49 | "ApplicationName": "cardano-sl", 50 | "ApplicationVersion": 1, 51 | "ByronGenesisFile": "mainnet-byron-genesis.json", 52 | "ByronGenesisHash": "5f20df933584822601f9e3f8c024eb5eb252fe8cefb24d1317dc3d432e940ebb", 53 | "LastKnownBlockVersion-Alt": 0, 54 | "LastKnownBlockVersion-Major": 2, 55 | "LastKnownBlockVersion-Minor": 0, 56 | "MaxKnownMajorProtocolVersion": 2, 57 | "Protocol": "Cardano", 58 | "RequiresNetworkMagic": "RequiresNoMagic", 59 | "ShelleyGenesisFile": "mainnet-shelley-genesis.json", 60 | "ShelleyGenesisHash": "1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81", 61 | "TraceBlockFetchClient": false, 62 | "TraceBlockFetchDecisions": false, 63 | "TraceBlockFetchProtocol": false, 64 | "TraceBlockFetchProtocolSerialised": false, 65 | "TraceBlockFetchServer": false, 66 | "TraceChainDb": true, 67 | "TraceChainSyncBlockServer": false, 68 | "TraceChainSyncClient": false, 69 | "TraceChainSyncHeaderServer": false, 70 | "TraceChainSyncProtocol": false, 71 | "TraceDNSResolver": true, 72 | "TraceDNSSubscription": true, 73 | "TraceErrorPolicy": true, 74 | "TraceForge": true, 75 | "TraceHandshake": false, 76 | "TraceIpSubscription": true, 77 | "TraceLocalChainSyncProtocol": false, 78 | "TraceLocalErrorPolicy": true, 79 | "TraceLocalHandshake": false, 80 | "TraceLocalTxSubmissionProtocol": false, 81 | "TraceLocalTxSubmissionServer": false, 82 | "TraceMempool": true, 83 | "TraceMux": false, 84 | "TraceTxInbound": false, 85 | "TraceTxOutbound": false, 86 | "TraceTxSubmissionProtocol": false, 87 | "TracingVerbosity": "NormalVerbosity", 88 | "TurnOnLogMetrics": true, 89 | "TurnOnLogging": true, 90 | "ViewMode": "SimpleView", 91 | "defaultBackends": [ 92 | "KatipBK" 93 | ], 94 | "defaultScribes": [ 95 | [ 96 | "StdoutSK", 97 | "stdout" 98 | ] 99 | ], 100 | "hasEKG": 12788, 101 | "hasPrometheus": [ 102 | "127.0.0.1", 103 | 12798 104 | ], 105 | "minSeverity": "Info", 106 | "options": { 107 | "mapBackends": { 108 | "cardano.node.metrics": [ 109 | "EKGViewBK" 110 | ], 111 | "cardano.node.BlockFetchDecision.peers": [ 112 | "EKGViewBK" 113 | ] 114 | }, 115 | "mapSubtrace": { 116 | "#ekgview": { 117 | "contents": [ 118 | [ 119 | { 120 | "contents": "cardano.epoch-validation.benchmark", 121 | "tag": "Contains" 122 | }, 123 | [ 124 | { 125 | "contents": ".monoclock.basic.", 126 | "tag": "Contains" 127 | } 128 | ] 129 | ], 130 | [ 131 | { 132 | "contents": "cardano.epoch-validation.benchmark", 133 | "tag": "Contains" 134 | }, 135 | [ 136 | { 137 | "contents": "diff.RTS.cpuNs.timed.", 138 | "tag": "Contains" 139 | } 140 | ] 141 | ], 142 | [ 143 | { 144 | "contents": "#ekgview.#aggregation.cardano.epoch-validation.benchmark", 145 | "tag": "StartsWith" 146 | }, 147 | [ 148 | { 149 | "contents": "diff.RTS.gcNum.timed.", 150 | "tag": "Contains" 151 | } 152 | ] 153 | ] 154 | ], 155 | "subtrace": "FilterTrace" 156 | }, 157 | "benchmark": { 158 | "contents": [ 159 | "GhcRtsStats", 160 | "MonotonicClock" 161 | ], 162 | "subtrace": "ObservableTrace" 163 | }, 164 | "cardano.epoch-validation.utxo-stats": { 165 | "subtrace": "NoTrace" 166 | }, 167 | "cardano.node.metrics": { 168 | "subtrace": "Neutral" 169 | } 170 | } 171 | }, 172 | "rotation": { 173 | "rpKeepFilesNum": 10, 174 | "rpLogLimitBytes": 5000000, 175 | "rpMaxAgeHours": 24 176 | }, 177 | "setupBackends": [ 178 | "KatipBK" 179 | ], 180 | "setupScribes": [ 181 | { 182 | "scFormat": "ScText", 183 | "scKind": "StdoutSK", 184 | "scName": "stdout", 185 | "scRotation": null 186 | } 187 | ], 188 | "traceForwardTo": { 189 | "tag": "RemoteSocket", 190 | "contents": [ 191 | "0.0.0.0", 192 | "3000" 193 | ] 194 | } 195 | } 196 | ``` 197 | 198 | This configuration file is based on [this Mainnet configuration](https://hydra.iohk.io/build/4553119/download/1/mainnet-config.json). 199 | -------------------------------------------------------------------------------- /doc/getting-started/rt-view-configuration.md: -------------------------------------------------------------------------------- 1 | # RTView Configuration 2 | 3 | ## Saved configuration 4 | 5 | During the start, RTView initiates an interactive dialog with the user. Based on the given answers, it creates a configuration file (see an example below). This file will be automatically saved on your hard drive in the system default configuration directory, depending on your platform. 6 | 7 | And when you'll start RTView again, it will ask you: 8 | 9 | ``` 10 | Saved configuration file is found. Do you want to use it? 11 | ``` 12 | 13 | `Y` is a default option, so if you press `Y` or `Enter`, the saved configuration will be used. Otherwise, you will enter into the same interactive dialog again. 14 | 15 | ## Configuration file: example 16 | 17 | This is an example of a saved RTView configuration created on Linux, with all default values accepted during dialog: 18 | 19 | ``` 20 | { 21 | "rotation": null, 22 | "defaultBackends": [ 23 | "KatipBK" 24 | ], 25 | "setupBackends": [ 26 | "KatipBK", 27 | "LogBufferBK", 28 | "TraceAcceptorBK" 29 | ], 30 | "hasPrometheus": null, 31 | "hasGraylog": null, 32 | "hasGUI": null, 33 | "traceForwardTo": null, 34 | "traceAcceptAt": [ 35 | { 36 | "remoteAddr": { 37 | "tag": "RemoteSocket", 38 | "contents": [ 39 | "0.0.0.0", 40 | "3000" 41 | ] 42 | }, 43 | "nodeName": "node-1" 44 | }, 45 | { 46 | "remoteAddr": { 47 | "tag": "RemoteSocket", 48 | "contents": [ 49 | "0.0.0.0", 50 | "3001" 51 | ] 52 | }, 53 | "nodeName": "node-2" 54 | }, 55 | { 56 | "remoteAddr": { 57 | "tag": "RemoteSocket", 58 | "contents": [ 59 | "0.0.0.0", 60 | "3002" 61 | ] 62 | }, 63 | "nodeName": "node-3" 64 | } 65 | ], 66 | "defaultScribes": [ 67 | [ 68 | "StdoutSK", 69 | "stdout" 70 | ] 71 | ], 72 | "options": { 73 | "mapBackends": { 74 | "cardano-rt-view.acceptor": [ 75 | "LogBufferBK", 76 | { 77 | "kind": "UserDefinedBK", 78 | "name": "ErrorBufferBK" 79 | } 80 | ] 81 | } 82 | }, 83 | "setupScribes": [ 84 | { 85 | "scMaxSev": "Emergency", 86 | "scName": "stdout", 87 | "scRotation": null, 88 | "scMinSev": "Notice", 89 | "scKind": "StdoutSK", 90 | "scFormat": "ScText", 91 | "scPrivacy": "ScPublic" 92 | } 93 | ], 94 | "hasEKG": null, 95 | "forwardDelay": null, 96 | "minSeverity": "Info" 97 | } 98 | ``` 99 | 100 | ## Explicit configuration 101 | 102 | It is possible to provide a configuration file explicitly, via `--config` command line parameter: 103 | 104 | ``` 105 | cardano-rt-view --config /path/to/your/cardano-rt-view.json 106 | ``` 107 | 108 | ## Logging 109 | 110 | RTView has its log files. You can find the directory path where log files will be stored in `RTView Info` modal window: click to info icon at the top bar. Another way to find this path is to check RTView configuration file: see the section 111 | 112 | ``` 113 | "FileSK","/full/path/to/cardano-rt-view-logs/cardano-rt-view.log" 114 | ``` 115 | 116 | Please note that the minimum severity level for logging is `Info` by default. If you need to see detailed debugging information in the log, please set the minimum severity level to `Debug`. To do it, open RTView configuration file and change the line 117 | 118 | ``` 119 | "minSeverity":"Info" 120 | ``` 121 | 122 | to 123 | 124 | ``` 125 | "minSeverity":"Debug" 126 | ``` 127 | -------------------------------------------------------------------------------- /doc/gui-overview/overview.md: -------------------------------------------------------------------------------- 1 | # GUI Overview 2 | 3 | ![Screenshot](../images/screenshot.png) 4 | 5 | RTView displays nodes' metrics on the web-page. All the metrics received from a particular node will be shown inside a panel. 6 | 7 | The top bar of each panel shows the `Name`. This is the name of the particular node you chose during RTView interactive dialog. For example, if you picked the default option for nodes' names, the `Name` in the first panel will be `node-1`. 8 | 9 | ## Select node 10 | 11 | At the top bar of the page, you will find the dropdown list `Select node`. Here you can select the nodes you want to see: 12 | 13 | 1. Check the checkbox if you want to see the corresponding panel; 14 | 2. Uncheck it if you're going to hide it. 15 | 16 | ## Tabs 17 | 18 | Each panel has the following tabs: 19 | 20 | 1. `Node info` - shows necessary information about the node; 21 | 2. `Key Evolving Signature` - KES-related information; 22 | 3. `Peers` - displays information about the peers of this node; 23 | 4. `Blockchain` - offers blockchain data (epochs, slots, blocks, etc.); 24 | 5. `Mempool` - shows the mempool and transactions information; 25 | 6. `Resources` - shows resources consumed by the node (memory, CPU, disk, network); 26 | 7. `Errors` - shows the error messages received from the node; 27 | 8. `RTS GC` - shows GHC's runtime system information. 28 | 29 | ## Tooltips 30 | 31 | Each tab contains some metrics. For example, `Node info` tab includes metrics `Node protocol`, `Node version`, `Node endpoint`, etc. If you hover a mouse on the metric's name - you will see a tooltip with short information about this metric. 32 | -------------------------------------------------------------------------------- /doc/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/cardano-rt-view/dddc7f3b06fecf771b0da3d96030174e54e29306/doc/images/screenshot.png -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | ############### 2 | cardano-rt-view 3 | ############### 4 | 5 | .. include:: ../README.rst 6 | 7 | .. toctree:: 8 | :titlesonly: 9 | :hidden: 10 | 11 | Go Back to Cardano Documentation 12 | 13 | .. toctree:: 14 | :maxdepth: 3 15 | :caption: Getting Started 16 | :titlesonly: 17 | :hidden: 18 | 19 | getting-started/install 20 | getting-started/building-rt-view-from-sources 21 | getting-started/building-the-documentation 22 | getting-started/node-configuration 23 | getting-started/rt-view-configuration 24 | 25 | .. toctree:: 26 | :maxdepth: 3 27 | :caption: GUI Overview 28 | :titlesonly: 29 | :hidden: 30 | 31 | gui-overview/overview 32 | 33 | .. toctree:: 34 | :maxdepth: 3 35 | :caption: Use Cases 36 | :titlesonly: 37 | :hidden: 38 | 39 | use-cases/different-machines 40 | use-cases/liveview-to-rtview 41 | 42 | .. toctree:: 43 | :maxdepth: 3 44 | :caption: Technical Details 45 | :titlesonly: 46 | :hidden: 47 | 48 | technical-details/understanding-metrics 49 | technical-details/email-notifications 50 | -------------------------------------------------------------------------------- /doc/technical-details/email-notifications.md: -------------------------------------------------------------------------------- 1 | # Email Notifications 2 | 3 | RTView `0.3.0` and higher can send automatic email notifications about specified events (for example, warnings or errors). Click on the bell icon on the top bar to see the corresponding settings. 4 | 5 | ## SMTP settings 6 | 7 | Technically, RTView contains an email client that sends emails using SMTP. That's why you need the SMTP settings of your email provider. Please fill in all inputs marked by an asterisk in `Notifications` -> `How to notify`. 8 | 9 | You can use `Test email` button to check if your email settings are correct. 10 | 11 | ## Note for Gmail users 12 | 13 | If you want to set up email notifications using your Gmail account, please make sure that `2-Step Verification` is enabled. You can check it in `Google Account` -> `Security`. After you enabled `2-Step Verification`, please generate the new app password (if you don't have one already) in `Security` -> `App passwords`. You'll need this app password for RTView settings. 14 | 15 | Now you can set up RTView notifications: 16 | 17 | 1. `SMTP server host`: `smtp.gmail.com` 18 | 2. `SMTP server port`: `587` 19 | 3. `Username`: most likely, it's your email address 20 | 4. `Password`: app password you've generated 21 | 5. `SSL`: `STARTTLS` 22 | 23 | ## Check period 24 | 25 | By default, the email client in the RTView checks events once per 2 minutes. But this can be configured. To do it, please click on the info icon on the top bar to see `RTView Information`. Here you can find the path to `Notifications file`. This JSON-file contains a field `nsCheckPeriodInSec`, which specifies the check period in seconds. For example, if you set this value to `3600` like this: 26 | 27 | ``` 28 | "nsCheckPeriodInSec": 3600, 29 | ``` 30 | 31 | the email client will check events only once per hour. In this case, you will receive only one email per hour with all events (if any of them occurred). 32 | -------------------------------------------------------------------------------- /doc/technical-details/understanding-metrics.md: -------------------------------------------------------------------------------- 1 | # Understanding Metrics 2 | 3 | Cardano node forwards its metrics and RTView accepts them. This guide describes some technical details behind it. 4 | 5 | ## Processes interaction 6 | 7 | Interaction between processes can be shown schematically like this: 8 | 9 | ``` 10 | +=================================+ +================================+ 11 | | cardano-node | | RTView | 12 | | +------------------+ | | +-----------------+ | 13 | | | iohk-monitoring | | | | iohk-monitoring | | 14 | | | +----------+ | | +----------+ | | 15 | | | TraceForwarderBK | Endpoint |------>>| Endpoint | TraceAcceptorBK | | 16 | | +------------------+----------+ | | +----------+-----------------+ | 17 | +=================================+ | +------------+ | +=============+ 18 | | | Web-server |<<--->>| Web browser | 19 | | +------------+ | +=============+ 20 | +================================+ 21 | ``` 22 | 23 | You can think of the library `iohk-monitoring` as an engine for forwarding/accepting metrics from the node to RTView. Mainly, there are two plugins in this library, `TraceForwarderBK` and `TraceAcceptorBK`, which are doing the job. That's why you should activate `TraceForwarderBK` in the node's configuration file. 24 | 25 | More details about `iohk-monitoring` library can be found in [its repository](https://github.com/input-output-hk/iohk-monitoring-framework/). 26 | -------------------------------------------------------------------------------- /doc/use-cases/different-machines.md: -------------------------------------------------------------------------------- 1 | # Use Case: Different Machines 2 | 3 | One of the main benefits of RTView is the ability of distributed work: you can run it on one machine and run your nodes on other ones. In this case, your nodes will send their metrics to RTView over the network. This guide describes how to set it up. 4 | 5 | ## Scenario 6 | 7 | You have two machines: 8 | 9 | 1. machine for the RTView (let's call it `Server machine`), 10 | 2. machine for the node (let's call it `Client machine`). 11 | 12 | So, one instance of the RTView will be launched on the `Server machine`, and one instance of the node will be launched on the `Client machine`. It is assumed that you have a network connection between `Server machine` and `Client machine`. 13 | 14 | Additionally, it is assumed that you have some web-server (for example, NGINX) on the `Server machine`. It will allow you to access RTView's web-page from any device. But technically, web-server is not a requirement, because RTView anyway runs its web-server. 15 | 16 | ## Server machine setup 17 | 18 | First of all, check the public IP of the `Server machine`: 19 | 20 | ``` 21 | curl ifconfig.me 22 | ``` 23 | 24 | Let's assume that it's `12.13.14.15`. Please remember this IP, you will need it soon. 25 | 26 | Now, download RTView archive for your platform. For example, if `Server machine` works under Linux, download the latest `*.tar.gz` archive from [Releases page](https://github.com/input-output-hk/cardano-rt-view/releases). Unpack it and run: 27 | 28 | ``` 29 | tar -xvf cardano-rt-view-*-linux-x86_64.tar.gz 30 | ./cardano-rt-view 31 | ``` 32 | 33 | Example of answers during configuration dialog: 34 | 35 | ``` 36 | RTView: real-time watching for Cardano nodes 37 | 38 | Let's configure RTView... 39 | 40 | How many nodes will you connect (1 - 99, default is 3): 1 41 | Ok, 1 nodes. 42 | 43 | Input the name of the node (default is "node-1"): 44 | Ok, default name "node-1" will be used. 45 | 46 | Indicate the port for the web server (1024 - 65535, default is 8024): 47 | Ok, the web-page will be available on http://127.0.0.1:8024, on the machine RTView will be launched on. 48 | 49 | Indicate how your nodes should be connected with RTView: networking sockets or named pipes

." 50 | Default way is sockets, so if you are not sure - choose : s 51 | Ok, sockets will be used. Indicate the port base to listen for connections (1024 - 65535, default is 3000): 52 | Ok, these ports will be used to accept nodes' metrics: 3000 53 | Now, indicate a host of machine RTView will be launched on (default is 0.0.0.0): 12.13.14.15 54 | Ok, it is assumed that RTView will be launched on the host "12.13.14.15". 55 | 56 | Indicate the directory with static content for the web server, default is "static": 57 | Ok, default directory will be used. 58 | 59 | Great, RTView is ready to run! Its configuration was saved at /home/denis/.config/cardano-rt-view.json. Press to continue... 60 | ``` 61 | 62 | Please note that the public IP of the `Server machine` was indicated for the question `indicate a host of machine RTView will be launched on`. Now, after you press `Enter`, you will see the changes you have to make in your node's configuration file: 63 | 64 | ``` 65 | 1. Find TurnOnLogMetrics flag and make sure it is true: 66 | 67 | "TurnOnLogMetrics": true 68 | 69 | 2. Since you have 1 node, add following traceForwardTo section in the root of its configuration file: 70 | 71 | "traceForwardTo": { 72 | "tag": "RemoteSocket", 73 | "contents": [ 74 | "12.13.14.15", 75 | "3000" 76 | ] 77 | } 78 | ``` 79 | 80 | As you can see, the section `traceForwardTo` contains the `Server machine`'s public IP. 81 | 82 | **IMPORTANT:** please make sure that corresponding port on the `Server machine` is available and open. In this example, the port `3000` should be open for the `Client machine`. 83 | 84 | ## Client machine setup 85 | 86 | It is assumed that you already have the node on the `Client machine`. If not - please read [how to build it](https://docs.cardano.org/projects/cardano-node/en/latest/getting-started/building-the-node-using-nix.html). 87 | 88 | Now open your node's configuration file and make the changes you saw in the end of the RTView configuration dialog. 89 | 90 | ## Launch 91 | 92 | Now go to the `Server machine` and launch RTView, then go to the `Client machine` and launch your node. After that, your node will connect to the RTView via `12.13.14.15:3000`. To see the RTView's web-page, please open `http://12.13.14.15:8024` (if you chose default port) in any browser. 93 | -------------------------------------------------------------------------------- /doc/use-cases/liveview-to-rtview.md: -------------------------------------------------------------------------------- 1 | # Use Case: From LiveView to RTView 2 | 3 | Many RTView users were LiveView users. But since LiveView was deprecated and removed from Cardano node, this guide describes how to move from LiveView to RTView. 4 | 5 | ## Main difference 6 | 7 | The main difference between LiveView and RTView is this: 8 | 9 | * LiveView was a part of `cardano-node` executable. 10 | * RTView isn't a part of `cardano-node` executable; it is a separate application. 11 | 12 | That's why LiveView was available immediately: you turn `ViewMode` to `LiveView` in your node's configuration file, and it's already here, right in your Unix terminal. 13 | 14 | But RTView is a separate application: you should download, configure, and launch it separately from your node. 15 | 16 | ## Understand concepts 17 | 18 | 1. LiveView is a part of the node, so if you have ten nodes, you need ten terminal windows to see their metrics. RTView receives metrics from your nodes, so if you have ten nodes, you need only one RTView to show their metrics. 19 | 2. LiveView provides TUI (textual UI) in the terminal. RTView provides web UI in any browser. 20 | 3. LiveView's TUI is Unix-based software, so you cannot run the node on Windows in LiveView mode. RTView is a cross-platform application so that you can run it on Windows, Linux, or macOS. 21 | 22 | Because of web UI, you can run RTView on a headless machine with a web-server and use the browser on any connected device (laptop, tablet, or phone) to see the metrics from your nodes. Please see [use case example](https://github.com/input-output-hk/cardano-rt-view/blob/master/doc/use-cases/different-machines.md#use-case-different-machines). 23 | 24 | ## RTView workflow 25 | 26 | 1. Install and configure RTView. Please read [this guide](https://github.com/input-output-hk/cardano-rt-view/blob/master/doc/getting-started/install.md#install-rtview). 27 | 2. Configure your nodes. Please read [this guide](https://github.com/input-output-hk/cardano-rt-view/blob/master/doc/getting-started/node-configuration.md#cardano-node-configuration). 28 | 3. Run RTView. 29 | 4. Run your nodes. 30 | 31 | After that, open the web-page and see the metrics received from your nodes. 32 | -------------------------------------------------------------------------------- /nix/darwin-release.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Darwin release cardano-rt-view-*.zip 3 | # 4 | # This bundles up the macOS executable with its dependencies 5 | # and static directory. Result is *.zip file. 6 | # 7 | ############################################################################ 8 | 9 | { pkgs 10 | , project 11 | , exes 12 | , staticDir 13 | }: 14 | 15 | let 16 | lib = pkgs.lib; 17 | name = "cardano-rt-view-${project.version}-darwin"; 18 | rtViewServiceExe = lib.head (lib.filter (exe: lib.hasInfix "cardano-rt-view" exe.name) exes); 19 | 20 | in pkgs.runCommand name { 21 | buildInputs = with pkgs.buildPackages; [ 22 | binutils 23 | darwin.cctools 24 | nix 25 | haskellBuildUtils.package 26 | zip 27 | ]; 28 | } '' 29 | mkdir -p $out release 30 | 31 | cd release 32 | mkdir ./static 33 | cp -R ${staticDir}/* ./static/ 34 | 35 | cp ${rtViewServiceExe}/bin/* . 36 | 37 | chmod -R +w . 38 | 39 | rewrite-libs ./ ${rtViewServiceExe}/bin/* 40 | 41 | dist_file=$out/${name}.zip 42 | zip -r $dist_file . 43 | 44 | mkdir -p $out/nix-support 45 | echo "file binary-dist $dist_file" > $out/nix-support/hydra-build-products 46 | '' 47 | -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem 2 | , crossSystem ? null 3 | , config ? {} 4 | , sourcesOverride ? {} 5 | , gitrev ? null, customConfig ? {} 6 | }: 7 | let 8 | sources = import ./sources.nix { inherit pkgs; } 9 | // sourcesOverride; 10 | iohkNixMain = import sources.iohk-nix {}; 11 | haskellNix = (import sources."haskell.nix" { inherit system sourcesOverride; }).nixpkgsArgs; 12 | # use our own nixpkgs if it exists in our sources, 13 | # otherwise use iohkNix default nixpkgs. 14 | nixpkgs = if (sources ? nixpkgs) 15 | then (builtins.trace "Not using IOHK default nixpkgs (use 'niv drop nixpkgs' to use default for better sharing)" 16 | sources.nixpkgs) 17 | else (builtins.trace "Using IOHK default nixpkgs" 18 | iohkNixMain.nixpkgs); 19 | 20 | # for inclusion in pkgs: 21 | overlays = 22 | # Haskell.nix (https://github.com/input-output-hk/haskell.nix) 23 | haskellNix.overlays 24 | # haskell-nix.haskellLib.extra: some useful extra utility functions for haskell.nix 25 | ++ iohkNixMain.overlays.haskell-nix-extra 26 | ++ iohkNixMain.overlays.crypto 27 | # iohkNix: nix utilities and niv: 28 | ++ iohkNixMain.overlays.iohkNix 29 | # our own overlays: 30 | ++ [ 31 | (pkgs: _: with pkgs; rec { 32 | inherit gitrev; 33 | 34 | # commonLib: mix pkgs.lib with iohk-nix utils and our own: 35 | commonLib = lib // iohkNix // iohkNix.cardanoLib 36 | // import ./util.nix { inherit haskell-nix; } 37 | # also expose our sources, nixpkgs and overlays 38 | // { inherit overlays sources nixpkgs; }; 39 | }) 40 | # And, of course, our haskell-nix-ified cabal project: 41 | (import ./pkgs.nix) 42 | ]; 43 | 44 | pkgs = import nixpkgs { 45 | inherit system crossSystem overlays; 46 | config = haskellNix.config // config; 47 | }; 48 | 49 | in pkgs 50 | -------------------------------------------------------------------------------- /nix/haskell.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Builds Haskell packages with Haskell.nix 3 | ############################################################################ 4 | { pkgs 5 | , lib 6 | , stdenv 7 | , haskell-nix 8 | , buildPackages 9 | , config ? {} 10 | # GHC attribute name 11 | , compiler ? config.haskellNix.compiler or "ghc8102" 12 | # Enable profiling 13 | , profiling ? config.haskellNix.profiling or false 14 | # Enable asserts for given packages 15 | , assertedPackages ? [] 16 | # Version info, to be passed when not building from a git work tree 17 | , gitrev ? null 18 | , libsodium ? pkgs.libsodium 19 | }: 20 | let 21 | 22 | src = haskell-nix.haskellLib.cleanGit { 23 | name = "cardano-rt-view-src"; 24 | src = ../.; 25 | }; 26 | 27 | projectPackages = lib.attrNames (haskell-nix.haskellLib.selectProjectPackages 28 | (haskell-nix.cabalProject { 29 | inherit src; 30 | compiler-nix-name = "ghc8102"; 31 | })); 32 | 33 | # This creates the Haskell package set. 34 | # https://input-output-hk.github.io/haskell.nix/user-guide/projects/ 35 | pkgSet = haskell-nix.cabalProject (lib.optionalAttrs stdenv.hostPlatform.isWindows { 36 | # FIXME: without this deprecated attribute, db-converter fails to compile directory with: 37 | # Encountered missing dependencies: unix >=2.5.1 && <2.9 38 | ghc = buildPackages.haskell-nix.compiler.${compiler}; 39 | } // { 40 | inherit src; 41 | compiler-nix-name = compiler; 42 | modules = [ 43 | { compiler.nix-name = compiler; } 44 | # Allow reinstallation of Win32 45 | { nonReinstallablePkgs = 46 | [ "rts" "ghc-heap" "ghc-prim" "integer-gmp" "integer-simple" "base" 47 | "deepseq" "array" "ghc-boot-th" "pretty" "template-haskell" 48 | # ghcjs custom packages 49 | "ghcjs-prim" "ghcjs-th" 50 | "ghc-boot" 51 | "ghc" "array" "binary" "bytestring" "containers" 52 | "filepath" "ghc-boot" "ghc-compact" "ghc-prim" 53 | # "ghci" "haskeline" 54 | "hpc" 55 | "mtl" "parsec" "text" "transformers" 56 | "xhtml" 57 | # "stm" "terminfo" 58 | ]; 59 | 60 | 61 | } 62 | { 63 | # Packages we wish to ignore version bounds of. 64 | # This is similar to jailbreakCabal, however it 65 | # does not require any messing with cabal files. 66 | packages.katip.doExactConfig = true; 67 | } 68 | # TODO: Compile all local packages with -Werror: 69 | { packages.cardano-rt-view.configureFlags = [ 70 | "--ghc-option=-Wall" 71 | ]; 72 | } 73 | # Musl libc fully static build 74 | (lib.optionalAttrs stdenv.hostPlatform.isMusl (let 75 | # Module options which adds GHC flags and libraries for a fully static build 76 | fullyStaticOptions = { 77 | enableShared = false; 78 | enableStatic = true; 79 | }; 80 | in 81 | { 82 | packages = lib.genAttrs (projectPackages) (name: fullyStaticOptions); 83 | 84 | # Haddock not working and not needed for cross builds 85 | doHaddock = false; 86 | } 87 | )) 88 | 89 | (lib.optionalAttrs (stdenv.hostPlatform != stdenv.buildPlatform) { 90 | # Make sure we use a buildPackages version of happy 91 | packages.pretty-show.components.library.build-tools = [ buildPackages.haskell-nix.haskellPackages.happy ]; 92 | 93 | # Remove hsc2hs build-tool dependencies (suitable version will be available as part of the ghc derivation) 94 | packages.Win32.components.library.build-tools = lib.mkForce []; 95 | packages.terminal-size.components.library.build-tools = lib.mkForce []; 96 | packages.network.components.library.build-tools = lib.mkForce []; 97 | 98 | # Disable cabal-doctest tests by turning off custom setups 99 | packages.comonad.package.buildType = lib.mkForce "Simple"; 100 | packages.distributive.package.buildType = lib.mkForce "Simple"; 101 | packages.lens.package.buildType = lib.mkForce "Simple"; 102 | packages.nonempty-vector.package.buildType = lib.mkForce "Simple"; 103 | packages.semigroupoids.package.buildType = lib.mkForce "Simple"; 104 | }) 105 | ]; 106 | # TODO add flags to packages (like cs-ledger) so we can turn off tests that will 107 | # not build for windows on a per package bases (rather than using --disable-tests). 108 | # configureArgs = lib.optionalString stdenv.hostPlatform.isWindows "--disable-tests"; 109 | }); 110 | 111 | # setGitRev is a postInstall script to stamp executables with 112 | # version info. It uses the "gitrev" argument, if set. Otherwise, 113 | # the revision is sourced from the local git work tree. 114 | gitrev' = if (gitrev == null) 115 | then buildPackages.commonLib.commitIdFromGitRepoOrZero ../.git 116 | else gitrev; 117 | haskellBuildUtils = buildPackages.haskellBuildUtils.package; 118 | in 119 | pkgSet // { 120 | projectPackages = lib.getAttrs projectPackages pkgSet; 121 | } 122 | -------------------------------------------------------------------------------- /nix/linux-release.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Linux release cardano-rt-view-*.tar.gz 3 | # 4 | # This bundles up the linux executable with its dependencies 5 | # and static directory. Result is *.tar.gz archive. 6 | # 7 | ############################################################################ 8 | 9 | { pkgs 10 | , project 11 | , exes 12 | , staticDir 13 | }: 14 | 15 | let 16 | lib = pkgs.lib; 17 | name = "cardano-rt-view-${project.version}-linux-x86_64"; 18 | rtViewServiceExe = lib.head (lib.filter (exe: lib.hasInfix "cardano-rt-view" exe.name) exes); 19 | 20 | in pkgs.runCommand name { 21 | buildInputs = with pkgs.buildPackages; [ 22 | haskellBuildUtils.package 23 | ]; 24 | } '' 25 | mkdir -p $out release 26 | 27 | cd release 28 | mkdir ./static 29 | cp -R ${staticDir}/* ./static/ 30 | 31 | cp -n --remove-destination -v ${rtViewServiceExe}/bin/* ./ 32 | 33 | chmod -R +w . 34 | 35 | dist_file=$out/${name}.tar.gz 36 | tar -czf $dist_file . 37 | 38 | mkdir -p $out/nix-support 39 | echo "file binary-dist $dist_file" > $out/nix-support/hydra-build-products 40 | '' 41 | -------------------------------------------------------------------------------- /nix/pkgs.nix: -------------------------------------------------------------------------------- 1 | # our packages overlay 2 | pkgs: _: with pkgs; { 3 | cardanoRTViewHaskellPackages = import ./haskell.nix { 4 | inherit config 5 | pkgs 6 | lib 7 | stdenv 8 | haskell-nix 9 | buildPackages 10 | ; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /nix/regenerate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | cd $(git rev-parse --show-toplevel) 4 | 5 | exec $(nix-build `dirname $0`/. -A iohkNix.cabalProjectRegenerate --no-out-link --option substituters "https://hydra.iohk.io https://cache.nixos.org" --option trusted-substituters "" --option trusted-public-keys "hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=")/bin/cabal-project-regenerate 6 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "haskell.nix": { 3 | "branch": "master", 4 | "description": "Alternative Haskell Infrastructure for Nixpkgs", 5 | "homepage": "https://input-output-hk.github.io/haskell.nix", 6 | "owner": "input-output-hk", 7 | "repo": "haskell.nix", 8 | "rev": "5fc93a3fd171bb40b97585e5eb2cab43dc467446", 9 | "sha256": "0a4q51jsxsy6z1ljbfr97haffzhwsrai74mzj17ljk8d78c26kfm", 10 | "type": "tarball", 11 | "url": "https://github.com/input-output-hk/haskell.nix/archive/5fc93a3fd171bb40b97585e5eb2cab43dc467446.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "iohk-nix": { 15 | "branch": "master", 16 | "description": "nix scripts shared across projects", 17 | "homepage": null, 18 | "owner": "input-output-hk", 19 | "repo": "iohk-nix", 20 | "rev": "83109208047b07d5f0c103c87e6685c61c36a9f6", 21 | "sha256": "1wfflfhd3jc8j49kgbvw0absvibkwm77wrjjc9ls8xkrcnprn4l7", 22 | "type": "tarball", 23 | "url": "https://github.com/input-output-hk/iohk-nix/archive/83109208047b07d5f0c103c87e6685c61c36a9f6.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "nixpkgs": { 27 | "branch": "nixpkgs-20.09-darwin", 28 | "description": "Nix Packages collection", 29 | "homepage": null, 30 | "owner": "NixOS", 31 | "repo": "nixpkgs", 32 | "rev": "07e5844fdf6fe99f41229d7392ce81cfe191bcfc", 33 | "sha256": "0p2z6jidm4rlp2yjfl553q234swj1vxl8z0z8ra1hm61lfrlcmb9", 34 | "type": "tarball", 35 | "url": "https://github.com/NixOS/nixpkgs/archive/07e5844fdf6fe99f41229d7392ce81cfe191bcfc.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: spec: 10 | if spec.builtin or true then 11 | builtins_fetchurl { inherit (spec) url sha256; } 12 | else 13 | pkgs.fetchurl { inherit (spec) url sha256; }; 14 | 15 | fetch_tarball = pkgs: spec: 16 | if spec.builtin or true then 17 | builtins_fetchTarball { inherit (spec) url sha256; } 18 | else 19 | pkgs.fetchzip { inherit (spec) url sha256; }; 20 | 21 | fetch_git = spec: 22 | builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; 23 | 24 | fetch_builtin-tarball = spec: 25 | builtins.trace 26 | '' 27 | WARNING: 28 | The niv type "builtin-tarball" will soon be deprecated. You should 29 | instead use `builtin = true`. 30 | 31 | $ niv modify -a type=tarball -a builtin=true 32 | '' 33 | builtins_fetchTarball { inherit (spec) url sha256; }; 34 | 35 | fetch_builtin-url = spec: 36 | builtins.trace 37 | '' 38 | WARNING: 39 | The niv type "builtin-url" will soon be deprecated. You should 40 | instead use `builtin = true`. 41 | 42 | $ niv modify -a type=file -a builtin=true 43 | '' 44 | (builtins_fetchurl { inherit (spec) url sha256; }); 45 | 46 | # 47 | # Various helpers 48 | # 49 | 50 | # The set of packages used when specs are fetched using non-builtins. 51 | mkPkgs = sources: 52 | let 53 | sourcesNixpkgs = 54 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; 55 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 56 | hasThisAsNixpkgsPath = == ./.; 57 | in 58 | if builtins.hasAttr "nixpkgs" sources 59 | then sourcesNixpkgs 60 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 61 | import {} 62 | else 63 | abort 64 | '' 65 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 66 | add a package called "nixpkgs" to your sources.json. 67 | ''; 68 | 69 | # The actual fetching function. 70 | fetch = pkgs: name: spec: 71 | 72 | if ! builtins.hasAttr "type" spec then 73 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 74 | else if spec.type == "file" then fetch_file pkgs spec 75 | else if spec.type == "tarball" then fetch_tarball pkgs spec 76 | else if spec.type == "git" then fetch_git spec 77 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec 78 | else if spec.type == "builtin-url" then fetch_builtin-url spec 79 | else 80 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 81 | 82 | # Ports of functions for older nix versions 83 | 84 | # a Nix version of mapAttrs if the built-in doesn't exist 85 | mapAttrs = builtins.mapAttrs or ( 86 | f: set: with builtins; 87 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 88 | ); 89 | 90 | # fetchTarball version that is compatible between all the versions of Nix 91 | builtins_fetchTarball = { url, sha256 }@attrs: 92 | let 93 | inherit (builtins) lessThan nixVersion fetchTarball; 94 | in 95 | if lessThan nixVersion "1.12" then 96 | fetchTarball { inherit url; } 97 | else 98 | fetchTarball attrs; 99 | 100 | # fetchurl version that is compatible between all the versions of Nix 101 | builtins_fetchurl = { url, sha256 }@attrs: 102 | let 103 | inherit (builtins) lessThan nixVersion fetchurl; 104 | in 105 | if lessThan nixVersion "1.12" then 106 | fetchurl { inherit url; } 107 | else 108 | fetchurl attrs; 109 | 110 | # Create the final "sources" from the config 111 | mkSources = config: 112 | mapAttrs ( 113 | name: spec: 114 | if builtins.hasAttr "outPath" spec 115 | then abort 116 | "The values in sources.json should not have an 'outPath' attribute" 117 | else 118 | spec // { outPath = fetch config.pkgs name spec; } 119 | ) config.sources; 120 | 121 | # The "config" used by the fetchers 122 | mkConfig = 123 | { sourcesFile ? ./sources.json 124 | , sources ? builtins.fromJSON (builtins.readFile sourcesFile) 125 | , pkgs ? mkPkgs sources 126 | }: rec { 127 | # The sources, i.e. the attribute set of spec name to spec 128 | inherit sources; 129 | 130 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 131 | inherit pkgs; 132 | }; 133 | in 134 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 135 | -------------------------------------------------------------------------------- /nix/update-iohk-nix.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -A devops ../shell.nix -i bash 3 | niv update iohk-nix 4 | -------------------------------------------------------------------------------- /nix/util.nix: -------------------------------------------------------------------------------- 1 | { haskell-nix }: 2 | 3 | with haskell-nix.haskellLib; 4 | { 5 | 6 | inherit 7 | selectProjectPackages 8 | collectComponents'; 9 | 10 | inherit (extra) 11 | recRecurseIntoAttrs 12 | collectChecks; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /nix/windows-release.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Windows release cardano-rt-view-*.zip 3 | # 4 | # This bundles up the windows executable with its dependencies 5 | # and static directory. 6 | # 7 | ############################################################################ 8 | 9 | { pkgs 10 | , project 11 | , exes 12 | , staticDir 13 | }: 14 | 15 | let 16 | lib = pkgs.lib; 17 | name = "cardano-rt-view-${project.version}-win64"; 18 | rtViewServiceExe = lib.head (lib.filter (exe: lib.hasInfix "cardano-rt-view" exe.name) exes); 19 | 20 | in pkgs.runCommand name { 21 | buildInputs = with pkgs.buildPackages; [ 22 | zip 23 | haskellBuildUtils.package 24 | ]; 25 | } '' 26 | mkdir -p $out release 27 | cd release 28 | 29 | cp -n --remove-destination -v ${rtViewServiceExe}/bin/* ./ 30 | 31 | mkdir ./static 32 | cp -R ${staticDir}/* ./static/ 33 | 34 | chmod -R +w . 35 | 36 | zip -r $out/${name}.zip . 37 | 38 | cd .. 39 | 40 | #dist_file=$(ls $out) 41 | mkdir -p $out/nix-support 42 | echo "file binary-dist $out/${name}.zip" > $out/nix-support/hydra-build-products 43 | 44 | cd /tmp 45 | mkdir -p output 46 | cp -vf "$out/${name}.zip" output/ 47 | ls -lh output/ 48 | pwd 49 | '' 50 | -------------------------------------------------------------------------------- /release.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # 3 | # Hydra release jobset. 4 | # 5 | # The purpose of this file is to select jobs defined in default.nix and map 6 | # them to all supported build platforms. 7 | # 8 | ############################################################################ 9 | 10 | # The project sources 11 | { cardano-rt-view ? { outPath = ./.; rev = "abcdef"; } 12 | 13 | # Function arguments to pass to the project 14 | , projectArgs ? { 15 | inherit sourcesOverride; 16 | config = { allowUnfree = false; inHydra = true; }; 17 | gitrev = cardano-rt-view.rev; 18 | } 19 | 20 | # The systems that the jobset will be built for. 21 | , supportedSystems ? [ "x86_64-linux" "x86_64-darwin" ] 22 | 23 | # The systems used for cross-compiling (default: linux) 24 | , supportedCrossSystems ? [ (builtins.head supportedSystems) ] 25 | 26 | # Cross compilation to Windows is currently only supported on linux. 27 | , linuxBuild ? builtins.elem "x86_64-linux" supportedCrossSystems 28 | 29 | # Cross compilation to Windows is currently only supported on linux. 30 | , windowsBuild ? linuxBuild 31 | 32 | , darwinBuild ? builtins.elem "x86_64-darwin" supportedCrossSystems 33 | 34 | # A Hydra option 35 | , scrubJobs ? true 36 | 37 | # Dependencies overrides 38 | , sourcesOverride ? {} 39 | 40 | # Import pkgs, including IOHK common nix lib 41 | , pkgs ? import ./nix { inherit sourcesOverride; } 42 | 43 | }: 44 | 45 | with (import pkgs.iohkNix.release-lib) { 46 | inherit pkgs; 47 | inherit supportedSystems supportedCrossSystems scrubJobs projectArgs; 48 | packageSet = import cardano-rt-view; 49 | gitrev = cardano-rt-view.rev; 50 | }; 51 | 52 | with pkgs.lib; 53 | 54 | let 55 | 56 | # restrict supported systems to a subset where tests (if exist) are required to pass: 57 | testsSupportedSystems = intersectLists supportedSystems [ "x86_64-linux" "x86_64-darwin" ]; 58 | # Recurse through an attrset, returning all derivations in a list matching test supported systems. 59 | collectJobs' = ds: filter (d: elem d.system testsSupportedSystems) (collect isDerivation ds); 60 | # Adds the package name to the derivations for windows-testing-bundle.nix 61 | # (passthru.identifier.name does not survive mapTestOn) 62 | collectJobs = ds: concatLists ( 63 | mapAttrsToList (packageName: package: 64 | map (drv: drv // { inherit packageName; }) (collectJobs' package) 65 | ) ds); 66 | 67 | nonDefaultBuildSystems = tail supportedSystems; 68 | 69 | # Paths or prefixes of paths of derivations to build only on the default system (ie. linux on hydra): 70 | onlyBuildOnDefaultSystem = [ ["checks"] ]; 71 | # Paths or prefix of paths for which cross-builds (mingwW64, musl64) are disabled: 72 | noCrossBuild = [ ["shell"] ] 73 | ++ onlyBuildOnDefaultSystem; 74 | noMusl64Build = [ ["checks"] ["tests"] ["benchmarks"] ["haskellPackages"] ] 75 | ++ noCrossBuild; 76 | 77 | # Remove build jobs for which cross compiling does not make sense. 78 | filterProject = noBuildList: mapAttrsRecursiveCond (a: !(isDerivation a)) (path: value: 79 | if (isDerivation value && (any (p: take (length p) path == p) noBuildList)) then null 80 | else value 81 | ) project; 82 | 83 | inherit (systems.examples) mingwW64 musl64; 84 | 85 | jobs = { 86 | native = 87 | let filteredBuilds = mapAttrsRecursiveCond (a: !(isList a)) (path: value: 88 | if (any (p: take (length p) path == p) onlyBuildOnDefaultSystem) then filter (s: !(elem s nonDefaultBuildSystems)) value else value) 89 | (packagePlatforms project); 90 | in (mapTestOn (__trace (__toJSON filteredBuilds) filteredBuilds)); 91 | musl64 = mapTestOnCross musl64 (packagePlatformsCross (filterProject noMusl64Build)); 92 | "${mingwW64.config}" = mapTestOnCross mingwW64 (packagePlatformsCross (filterProject noCrossBuild)); 93 | cardano-rt-view-win64-release = import ./nix/windows-release.nix { 94 | inherit pkgs project; 95 | exes = collectJobs jobs.${mingwW64.config}.exes.cardano-rt-view; 96 | staticDir = ./static; 97 | }; 98 | cardano-rt-view-linux-release = import ./nix/linux-release.nix { 99 | inherit pkgs project; 100 | exes = collectJobs jobs.musl64.exes.cardano-rt-view; 101 | staticDir = ./static; 102 | }; 103 | cardano-rt-view-darwin-release = import ./nix/darwin-release.nix { 104 | inherit (pkgsFor "x86_64-darwin") pkgs; 105 | inherit project; 106 | exes = filter (p: p.system == "x86_64-darwin") (collectJobs jobs.native.exes.cardano-rt-view); 107 | staticDir = ./static; 108 | }; 109 | } // (mkRequiredJob (concatLists [ 110 | (collectJobs jobs.native.checks) 111 | (collectJobs jobs.native.benchmarks) 112 | (collectJobs jobs.native.exes) 113 | (optional windowsBuild jobs.cardano-rt-view-win64-release) 114 | (optional linuxBuild jobs.cardano-rt-view-linux-release) 115 | (optional darwinBuild jobs.cardano-rt-view-darwin-release) 116 | ])); 117 | 118 | in jobs 119 | -------------------------------------------------------------------------------- /scripts/cabal-inside-nix-shell.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cabal_project="$(git rev-parse --show-toplevel)"/cabal.project 4 | 5 | set -x 6 | sed -i 's_^ -- ../_ ../_' "$cabal_project" 7 | sed -ni '1,/--- 8< ---/ p' "$cabal_project" 8 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # This file is used by nix-shell. 2 | # It just takes the shell attribute from default.nix. 3 | { config ? {} 4 | , sourcesOverride ? {} 5 | , minimal ? false 6 | , withHoogle ? (! minimal) 7 | , pkgs ? import ./nix { 8 | inherit config sourcesOverride; 9 | } 10 | }: 11 | with pkgs; 12 | let 13 | # This provides a development environment that can be used with nix-shell or 14 | # lorri. See https://input-output-hk.github.io/haskell.nix/user-guide/development/ 15 | shell = cardanoRTViewHaskellPackages.shellFor { 16 | name = "cabal-dev-shell"; 17 | 18 | packages = _: lib.attrValues cardanoRTViewHaskellPackages.projectPackages; 19 | 20 | # These programs will be available inside the nix-shell. 21 | buildInputs = with haskellPackages; [ 22 | cabal-install 23 | stylish-haskell 24 | nix 25 | niv 26 | pkgconfig 27 | ] ++ lib.optionals (! minimal) [ 28 | ghcid 29 | pkgs.git 30 | hlint 31 | stylish-haskell 32 | weeder 33 | ]; 34 | 35 | # Prevents cabal from choosing alternate plans, so that 36 | # *all* dependencies are provided by Nix. 37 | exactDeps = true; 38 | 39 | inherit withHoogle; 40 | }; 41 | 42 | devops = pkgs.stdenv.mkDerivation { 43 | name = "devops-shell"; 44 | buildInputs = [ 45 | niv 46 | ]; 47 | shellHook = '' 48 | echo "DevOps Tools" \ 49 | | ${figlet}/bin/figlet -f banner -c \ 50 | | ${lolcat}/bin/lolcat 51 | 52 | echo "NOTE: you may need to export GITHUB_TOKEN if you hit rate limits with niv" 53 | echo "Commands: 54 | * niv update - update package 55 | 56 | " 57 | ''; 58 | }; 59 | 60 | in 61 | 62 | shell // { inherit devops; } 63 | -------------------------------------------------------------------------------- /src/Cardano/RTView.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView 2 | ( runCardanoRTView 3 | ) where 4 | 5 | import Control.Concurrent.Async (async, waitAnyCancel) 6 | import Control.Concurrent.STM.TVar (TVar, newTVarIO) 7 | import Control.Monad (void) 8 | import Data.Text (Text) 9 | import qualified Data.Text.IO as TIO 10 | 11 | import Cardano.BM.Backend.Switchboard (addUserDefinedBackend) 12 | import Cardano.BM.Data.Backend (Backend (..)) 13 | import qualified Cardano.BM.Setup as Setup 14 | import Cardano.BM.Trace (Trace, logNotice) 15 | import Cardano.BM.Tracing (appendName) 16 | 17 | import Cardano.RTView.Acceptor (launchMetricsAcceptor) 18 | import Cardano.RTView.CLI (RTViewParams (..)) 19 | import Cardano.RTView.Config (prepareConfigAndParams) 20 | import Cardano.RTView.GUI.Elements (TmpElements, initialTmpElements) 21 | import Cardano.RTView.EKG (EKGStores, mkEKGStores) 22 | import Cardano.RTView.ErrorBuffer (ErrorBuffer, effectuate, realize, unrealize) 23 | import Cardano.RTView.NodeState.Types (NodesState, initialNodesState) 24 | import Cardano.RTView.NodeState.Updater (launchNodeStateUpdater) 25 | import Cardano.RTView.Notifications.CheckEvents (launchNotifications) 26 | import Cardano.RTView.Notifications.Types (NotificationSettings) 27 | import Cardano.RTView.WebServer (launchWebServer) 28 | 29 | -- | Run the service. 30 | runCardanoRTView :: RTViewParams -> IO () 31 | runCardanoRTView params' = do 32 | TIO.putStrLn "RTView: real-time watching for Cardano nodes" 33 | 34 | (config, notifySettings, params, acceptors) <- prepareConfigAndParams params' 35 | 36 | (tr :: Trace IO Text, switchBoard) <- Setup.setupTrace_ config "cardano-rt-view" 37 | let accTr = appendName "acceptor" tr 38 | 39 | -- Initialise own backend (error buffer). 40 | be :: ErrorBuffer Text <- realize config 41 | let ebBe = MkBackend { bEffectuate = effectuate be 42 | , bUnrealize = unrealize be 43 | } 44 | addUserDefinedBackend switchBoard ebBe "ErrorBufferBK" 45 | 46 | logNotice tr "Starting service; hit CTRL-C to terminate..." 47 | 48 | -- This TVar contains state (info, metrics) for all nodes we receive metrics from. 49 | nsTVar :: TVar NodesState <- newTVarIO =<< initialNodesState config 50 | -- This TVar contains temporary Elements which should be deleted explicitly. 51 | tmpElsTVar :: TVar TmpElements <- newTVarIO $ initialTmpElements acceptors 52 | -- This TVar contains complete notification settings. 53 | notifyTVar :: TVar NotificationSettings <- newTVarIO notifySettings 54 | -- This TVar contains EKG-stores for all acceptors. 55 | ekgStoresTVar :: TVar EKGStores <- newTVarIO =<< mkEKGStores acceptors 56 | 57 | let nsTr = appendName "nodeState" tr 58 | wsTr = appendName "webServer" tr 59 | ntTr = appendName "notifications" tr 60 | -- Launch 4 threads: 61 | -- 1. acceptor plugin (it launches |TraceAcceptor| plugin), 62 | -- 2. node state updater (it gets metrics from |LogBuffer| and updates NodeState), 63 | -- 3. web server (it serves requests from user's browser and shows nodes' metrics in the real time). 64 | -- 4. notifications: check events and notify the user about them. 65 | acceptorThr <- async $ launchMetricsAcceptor config accTr switchBoard 66 | updaterThr <- async $ launchNodeStateUpdater nsTr switchBoard be nsTVar ekgStoresTVar 67 | serverThr <- async $ launchWebServer wsTr config nsTVar tmpElsTVar notifyTVar params acceptors 68 | notifyThr <- async $ launchNotifications ntTr nsTVar notifyTVar 69 | 70 | void $ waitAnyCancel [acceptorThr, updaterThr, serverThr, notifyThr] 71 | -------------------------------------------------------------------------------- /src/Cardano/RTView/Acceptor.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.Acceptor 2 | ( launchMetricsAcceptor 3 | ) where 4 | 5 | import Control.Monad (forever) 6 | import Data.Text (Text) 7 | import System.Time.Extra (sleep) 8 | 9 | import Cardano.BM.Backend.Switchboard (Switchboard) 10 | import qualified Cardano.BM.Backend.TraceAcceptor as TraceAcceptor 11 | import Cardano.BM.Configuration (Configuration) 12 | import Cardano.BM.IOManager (withIOManager) 13 | import Cardano.BM.Plugin (loadPlugin) 14 | import Cardano.BM.Trace (Trace) 15 | 16 | -- | It is assumed that there's at least one cardano-node process 17 | -- that sends its metrics as |LogObject|s via |TraceForwarder|. 18 | -- These |LogObject|s will be accepted by |TraceAcceptor|s and 19 | -- redirected to the corresponding tracers and finally stored 20 | -- in the |LogBuffer|. 21 | launchMetricsAcceptor 22 | :: Configuration 23 | -> Trace IO Text 24 | -> Switchboard Text 25 | -> IO () 26 | launchMetricsAcceptor config accTr switchBoard = 27 | withIOManager $ \iomgr -> do 28 | TraceAcceptor.plugin iomgr config accTr switchBoard >>= loadPlugin switchBoard 29 | forever $ sleep 1 30 | -------------------------------------------------------------------------------- /src/Cardano/RTView/CLI.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | 4 | module Cardano.RTView.CLI 5 | ( RTViewParams (..) 6 | , defaultRTViewParams 7 | , defaultRTVConfig 8 | , defaultRTVNotifications 9 | , defaultRTVStatic 10 | , defaultRTVPort 11 | , defaultRTVActiveNodeLife 12 | , parseRTViewParams 13 | ) where 14 | 15 | import Data.Aeson (FromJSON (..), ToJSON, (.:), (.:?), (.!=), withObject) 16 | import GHC.Generics (Generic) 17 | import Options.Applicative (HasCompleter, HasMetavar, HasName, HasValue, Mod, Parser, 18 | auto, bashCompleter, completer, help, long, metavar, option, 19 | showDefault, strOption, value) 20 | 21 | -- | Type for CLI parameters required for the service. 22 | data RTViewParams = RTViewParams 23 | { rtvConfig :: !FilePath 24 | , rtvNotifications :: !FilePath 25 | , rtvStatic :: !FilePath 26 | , rtvPort :: !Int 27 | , rtvActiveNodeLife :: !Int 28 | } deriving (Generic, ToJSON) 29 | 30 | instance FromJSON RTViewParams where 31 | parseJSON = withObject "RTViewParams" $ \v -> RTViewParams 32 | <$> v .: "rtvConfig" 33 | <*> v .:? "rtvNotifications" .!= defaultRTVNotifications 34 | <*> v .: "rtvStatic" 35 | <*> v .: "rtvPort" 36 | <*> v .: "rtvActiveNodeLife" 37 | 38 | defaultRTViewParams :: RTViewParams 39 | defaultRTViewParams = RTViewParams 40 | { rtvConfig = defaultRTVConfig 41 | , rtvNotifications = defaultRTVNotifications 42 | , rtvStatic = defaultRTVStatic 43 | , rtvPort = defaultRTVPort 44 | , rtvActiveNodeLife = defaultRTVActiveNodeLife 45 | } 46 | 47 | defaultRTVConfig, defaultRTVNotifications, defaultRTVStatic :: FilePath 48 | defaultRTVConfig = "" 49 | defaultRTVNotifications = "" 50 | defaultRTVStatic = "static" 51 | 52 | defaultRTVPort :: Int 53 | defaultRTVPort = 8024 54 | 55 | defaultRTVActiveNodeLife :: Int 56 | defaultRTVActiveNodeLife = 120 57 | 58 | parseRTViewParams :: Parser RTViewParams 59 | parseRTViewParams = 60 | RTViewParams 61 | <$> parseFilePath 62 | "config" 63 | "file" 64 | "Configuration file for RTView service. If not provided, interactive dialog will be started." 65 | defaultRTVConfig 66 | <*> parseFilePath 67 | "notifications" 68 | "file" 69 | "Configuration file for notifications" 70 | defaultRTVNotifications 71 | <*> parseFilePath 72 | "static" 73 | "directory" 74 | "Directory with static content" 75 | defaultRTVStatic 76 | <*> parseInt 77 | "port" 78 | "The port number" 79 | "PORT" 80 | defaultRTVPort 81 | <*> parseInt 82 | "active-node-life" 83 | "Active node lifetime, in seconds" 84 | "TIME" 85 | defaultRTVActiveNodeLife 86 | 87 | -- Aux parsers 88 | 89 | parseFilePath 90 | :: String 91 | -> String 92 | -> String 93 | -> FilePath 94 | -> Parser FilePath 95 | parseFilePath optname completion desc defaultPath = 96 | let flags :: (HasCompleter f, HasMetavar f, HasName f, HasValue f) 97 | => Mod f FilePath 98 | flags = long optname 99 | <> metavar "FILEPATH" 100 | <> help desc 101 | <> completer (bashCompleter completion) 102 | <> value defaultPath 103 | in strOption $ if null defaultPath 104 | then flags 105 | else flags <> showDefault 106 | 107 | parseInt 108 | :: String 109 | -> String 110 | -> String 111 | -> Int 112 | -> Parser Int 113 | parseInt optname desc metavar' defaultValue = 114 | option auto ( 115 | long optname 116 | <> metavar metavar' 117 | <> help desc 118 | <> value defaultValue 119 | <> showDefault 120 | ) 121 | -------------------------------------------------------------------------------- /src/Cardano/RTView/EKG.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.EKG 2 | ( EKGStores 3 | , isItEKGMetric 4 | , mkEKGStores 5 | , storeEKGMetrics 6 | ) where 7 | 8 | import Control.Concurrent.STM.TVar (TVar, modifyTVar', readTVarIO) 9 | import Control.Monad (forM) 10 | import Control.Monad.STM (atomically) 11 | import Data.HashMap.Strict (HashMap, (!)) 12 | import qualified Data.HashMap.Strict as HM 13 | import Data.Int (Int64) 14 | import Data.Text (Text) 15 | import qualified Data.Text as T 16 | import Data.Time.Clock (UTCTime) 17 | import System.Metrics (Store, createGauge, newStore, sampleAll) 18 | import System.Metrics.Gauge (Gauge, set) 19 | 20 | import Cardano.BM.Data.Aggregated (Measurable (..)) 21 | import Cardano.BM.Data.Configuration (RemoteAddrNamed (..)) 22 | import Cardano.BM.Data.LogItem (LOContent (..), LOMeta (..), LogObject (..)) 23 | 24 | type NodeName = Text 25 | type MetricName = Text 26 | type Gauges = HashMap MetricName (Gauge, UTCTime) 27 | -- We have to store gauges separately to be able to update them later. 28 | type EKGStores = HashMap NodeName (Store, Gauges) 29 | 30 | mkEKGStores 31 | :: [RemoteAddrNamed] 32 | -> IO EKGStores 33 | mkEKGStores acceptors = do 34 | stores <- forM acceptors $ \(RemoteAddrNamed nameOfNode _) -> do 35 | emptyStore <- newStore 36 | let gaugesForThisStore = HM.empty 37 | return (nameOfNode, (emptyStore, gaugesForThisStore)) 38 | return $ HM.fromList stores 39 | 40 | isItEKGMetric :: (Text, LogObject Text) -> Bool 41 | isItEKGMetric (_, LogObject _ _ aContent) = 42 | case aContent of 43 | LogValue vName _ -> any (\s -> s `T.isInfixOf` vName) ekgMetricSigns 44 | _ -> False 45 | where 46 | ekgMetricSigns = ["Stat.", "Mem.", "Sys.", "Net.", "IO.", "RTS."] 47 | 48 | storeEKGMetrics 49 | :: [(Text, LogObject Text)] 50 | -> TVar EKGStores 51 | -> IO () 52 | storeEKGMetrics [] _ = return () 53 | storeEKGMetrics metrics ekgStoresTVar = do 54 | updatedEKGStores <- doStoreEKGMetrics 0 metrics =<< readTVarIO ekgStoresTVar 55 | atomically $ modifyTVar' ekgStoresTVar (const updatedEKGStores) 56 | 57 | doStoreEKGMetrics 58 | :: Int 59 | -> [(Text, LogObject Text)] 60 | -> EKGStores 61 | -> IO EKGStores 62 | doStoreEKGMetrics ix metrics stores = 63 | if length metrics == ix 64 | then return stores 65 | else do 66 | let (loggerName, LogObject _ aMeta (LogValue metricName metricValue)) = metrics !! ix 67 | loggerNameParts = filter (not . T.null) $ T.splitOn "." loggerName 68 | nameOfNode = loggerNameParts !! 3 69 | rawValue :: Int64 70 | rawValue = 71 | case metricValue of 72 | Nanoseconds n -> fromIntegral n 73 | Microseconds m -> fromIntegral m 74 | Bytes b -> fromIntegral b 75 | PureI i -> fromIntegral i 76 | _ -> 0 77 | (storeForThisNode, gaugesForThisStore) = stores ! nameOfNode 78 | ts = tstamp aMeta 79 | metricIsHere <- metricAlreadyStored metricName storeForThisNode 80 | updatedGauges <- 81 | if metricIsHere 82 | then do 83 | -- Since metric is already here, just update its value. 84 | updateGauge metricName gaugesForThisStore rawValue 85 | -- Update timestamp for this metric. 86 | return $ HM.adjust (\(g, _) -> (g, ts)) metricName gaugesForThisStore 87 | else do 88 | -- There is no such a metric, create it and set the value. 89 | newGauge <- createGauge metricName storeForThisNode 90 | set newGauge rawValue 91 | return $ HM.insert metricName (newGauge, ts) gaugesForThisStore 92 | 93 | let updatedStores = HM.adjust (const (storeForThisNode, updatedGauges)) nameOfNode stores 94 | doStoreEKGMetrics (ix + 1) metrics updatedStores 95 | 96 | metricAlreadyStored 97 | :: Text 98 | -> Store 99 | -> IO Bool 100 | metricAlreadyStored metricName store = HM.member metricName <$> sampleAll store 101 | 102 | updateGauge 103 | :: Text 104 | -> Gauges 105 | -> Int64 106 | -> IO () 107 | updateGauge metricName gauges rawValue = set gauge rawValue 108 | where 109 | (gauge, _) = gauges ! metricName 110 | -------------------------------------------------------------------------------- /src/Cardano/RTView/ErrorBuffer.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE MultiParamTypeClasses #-} 3 | 4 | module Cardano.RTView.ErrorBuffer 5 | ( ErrorBuffer 6 | , readErrorBuffer 7 | , effectuate 8 | , realize 9 | , unrealize 10 | ) where 11 | 12 | import Control.Concurrent.MVar (MVar, modifyMVar, newMVar, readMVar) 13 | import qualified Control.Concurrent.STM.TBQueue as TBQ 14 | import Control.Monad (when) 15 | import Control.Monad.STM (atomically) 16 | import Numeric.Natural (Natural) 17 | import Data.Aeson (FromJSON) 18 | import qualified Data.Text.IO as TIO 19 | import System.IO (stderr) 20 | 21 | import Cardano.BM.Data.Backend (BackendKind (..), IsBackend (..), IsEffectuator (..)) 22 | import Cardano.BM.Data.LogItem (LOContent (..), LOMeta (..), LogObject (..), LoggerName) 23 | import Cardano.BM.Data.Severity (Severity (..)) 24 | 25 | -- | All |LogObject|s accepted by |TraceAcceptor| plugin 26 | -- will be decoded and traced to 'cardano-rt-view.acceptor'. 27 | -- Because of RView configuration all these |LogObject|s 28 | -- will be sent to |LogBufferBK| and |ErrorBufferBK|, 29 | -- but |ErrorBufferBK| is storing only errors. 30 | -- Later NodesState.Updater will extract errors from |ErrorBufferBK| 31 | -- and display them in UI ("Errors" tab). 32 | newtype ErrorBuffer a = ErrorBuffer 33 | { getErrBuf :: MVar (ErrorBufferInternal a) 34 | } 35 | 36 | newtype ErrorBufferInternal a 37 | = ErrorBufferInternal 38 | { errQueue :: TBQ.TBQueue (LoggerName, LogObject a) 39 | } 40 | 41 | -- | Once we read the current content of the queue, it should be cleaned. 42 | readErrorBuffer :: ErrorBuffer a -> IO [(LoggerName, LogObject a)] 43 | readErrorBuffer buffer = 44 | modifyMVar (getErrBuf buffer) $ \currentBuffer -> do 45 | loList <- atomically $ TBQ.flushTBQueue (errQueue currentBuffer) 46 | -- LogObjects are flushed, clean it up. 47 | queue <- atomically $ TBQ.newTBQueue queueMaxSize 48 | return (ErrorBufferInternal queue, loList) 49 | 50 | instance IsEffectuator ErrorBuffer a where 51 | effectuate buffer lo@(LogObject loname lometa locontent) = do 52 | currentEB <- readMVar (getErrBuf buffer) 53 | let queue = errQueue currentEB 54 | noCapacity <- atomically $ TBQ.isFullTBQueue queue 55 | if noCapacity 56 | then handleOverflow buffer 57 | else when isError $ 58 | atomically $ TBQ.writeTBQueue queue ("#buffered." <> loname, lo) 59 | where 60 | -- Only Error-messages should be stored in the queue. 61 | isError = 62 | case locontent of 63 | LogValue _ _ -> False 64 | LogError _ -> True 65 | _ -> severity lometa >= Warning 66 | 67 | handleOverflow _ = TIO.hPutStrLn stderr "Notice: overflow in ErrorBuffer, dropping log items!" 68 | 69 | instance FromJSON a => IsBackend ErrorBuffer a where 70 | bekind _ = UserDefinedBK "ErrorBufferBK" 71 | 72 | realize _ = do 73 | queue <- atomically $ TBQ.newTBQueue queueMaxSize 74 | ErrorBuffer <$> newMVar (ErrorBufferInternal queue) 75 | 76 | unrealize _ = return () 77 | 78 | queueMaxSize :: Natural 79 | queueMaxSize = 1000 80 | -------------------------------------------------------------------------------- /src/Cardano/RTView/GUI/JS/Charts.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.GUI.JS.Charts 2 | ( prepareChartsJS 3 | -- Charts JS snippets. 4 | , memoryUsageChartJS 5 | , cpuUsageChartJS 6 | , diskUsageChartJS 7 | , networkUsageChartJS 8 | -- Charts updaters. 9 | , updateMemoryUsageChartJS 10 | , updateCPUUsageChartJS 11 | , updateDiskUsageChartJS 12 | , updateNetworkUsageChartJS 13 | , resizeChartJS 14 | ) where 15 | 16 | prepareChartsJS :: String 17 | prepareChartsJS = 18 | "window.charts = new Map();" 19 | 20 | memoryUsageChartJS :: String 21 | memoryUsageChartJS = concat 22 | [ "var ctx = document.getElementById(%1).getContext('2d');" 23 | , "var chart = new Chart(ctx, {" 24 | , " type: 'line'," 25 | , " data: {" 26 | , " labels: []," 27 | , " datasets: [{" 28 | , " label: 'Memory | kernel'," 29 | , " backgroundColor: '#FF8000'," 30 | , " borderColor: '#FF8000'," 31 | , " data: []," 32 | , " fill: false" 33 | , " },{" 34 | , " label: 'Memory | RTS'," 35 | , " backgroundColor: '#DC143C'," 36 | , " borderColor: '#DC143C'," 37 | , " data: []," 38 | , " fill: false" 39 | , " }]" 40 | , " }," 41 | , " options: {" 42 | , " responsive: true," 43 | , " scales: {" 44 | , " yAxes: [{" 45 | , " display: true," 46 | , " scaleLabel: {" 47 | , " display: true," 48 | , " labelString: 'MB'" 49 | , " }," 50 | , " ticks: {" 51 | , " min: 0" 52 | , " }" 53 | , " }]" 54 | , " }" 55 | , " }" 56 | , "});" 57 | , "window.charts.set(%1, chart);" 58 | ] 59 | 60 | cpuUsageChartJS :: String 61 | cpuUsageChartJS = concat 62 | [ "var ctx = document.getElementById(%1).getContext('2d');" 63 | , "var chart = new Chart(ctx, {" 64 | , " type: 'line'," 65 | , " data: {" 66 | , " labels: []," 67 | , " datasets: [{" 68 | , " label: 'CPU | total'," 69 | , " backgroundColor: '#FE2E2E'," 70 | , " borderColor: '#FE2E2E'," 71 | , " data: []," 72 | , " fill: false" 73 | , " },{" 74 | , " label: 'CPU | code'," 75 | , " backgroundColor: '#008B8B'," 76 | , " borderColor: '#008B8B'," 77 | , " data: []," 78 | , " fill: false" 79 | , " },{" 80 | , " label: 'CPU | GC'," 81 | , " backgroundColor: '#8B0000'," 82 | , " borderColor: '#8B0000'," 83 | , " data: []," 84 | , " fill: false" 85 | , " }]" 86 | , " }," 87 | , " options: {" 88 | , " responsive: true," 89 | , " scales: {" 90 | , " yAxes: [{" 91 | , " display: true," 92 | , " scaleLabel: {" 93 | , " display: true," 94 | , " labelString: 'Percent'" 95 | , " }," 96 | , " ticks: {" 97 | , " min: 0" 98 | , " }" 99 | , " }]" 100 | , " }" 101 | , " }" 102 | , "});" 103 | , "window.charts.set(%1, chart);" 104 | ] 105 | 106 | diskUsageChartJS :: String 107 | diskUsageChartJS = concat 108 | [ "var ctx = document.getElementById(%1).getContext('2d');" 109 | , "var chart = new Chart(ctx, {" 110 | , " type: 'line'," 111 | , " data: {" 112 | , " labels: []," 113 | , " datasets: [{" 114 | , " label: 'Disk | RD'," 115 | , " backgroundColor: '#0080FF'," 116 | , " borderColor: '#0080FF'," 117 | , " data: []," 118 | , " fill: false" 119 | , " },{" 120 | , " label: 'Disk | WR'," 121 | , " backgroundColor: '#D358F7'," 122 | , " borderColor: '#D358F7'," 123 | , " data: []," 124 | , " fill: false" 125 | , " }]" 126 | , " }," 127 | , " options: {" 128 | , " responsive: true," 129 | , " scales: {" 130 | , " yAxes: [{" 131 | , " display: true," 132 | , " scaleLabel: {" 133 | , " display: true," 134 | , " labelString: 'KB/s'" 135 | , " }," 136 | , " ticks: {" 137 | , " min: 0" 138 | , " }" 139 | , " }]" 140 | , " }" 141 | , " }" 142 | , "});" 143 | , "window.charts.set(%1, chart);" 144 | ] 145 | 146 | networkUsageChartJS :: String 147 | networkUsageChartJS = concat 148 | [ "var ctx = document.getElementById(%1).getContext('2d');" 149 | , "var chart = new Chart(ctx, {" 150 | , " type: 'line'," 151 | , " data: {" 152 | , " labels: []," 153 | , " datasets: [{" 154 | , " label: 'Network | IN'," 155 | , " backgroundColor: '#D7DF01'," 156 | , " borderColor: '#D7DF01'," 157 | , " data: []," 158 | , " fill: false" 159 | , " },{" 160 | , " label: 'Network | OUT'," 161 | , " backgroundColor: '#00FF80'," 162 | , " borderColor: '#00FF80'," 163 | , " data: []," 164 | , " fill: false" 165 | , " }]" 166 | , " }," 167 | , " options: {" 168 | , " responsive: true," 169 | , " scales: {" 170 | , " yAxes: [{" 171 | , " display: true," 172 | , " scaleLabel: {" 173 | , " display: true," 174 | , " labelString: 'KB/s'" 175 | , " }," 176 | , " ticks: {" 177 | , " min: 0" 178 | , " }" 179 | , " }]" 180 | , " }" 181 | , " }" 182 | , "});" 183 | , "window.charts.set(%1, chart);" 184 | ] 185 | 186 | -- Chart updaters. 187 | -- Please note that after 900 data points (which are collected in every 30 minutes) 188 | -- we remove outdated points. It allows to avoid too compressed, narrow charts. 189 | -- This is a temporary solution, it will be improved in the future releases. 190 | 191 | updateMemoryUsageChartJS 192 | , updateCPUUsageChartJS 193 | , updateDiskUsageChartJS 194 | , updateNetworkUsageChartJS :: String 195 | updateMemoryUsageChartJS = updateDoubleDatasetChartJS 196 | updateCPUUsageChartJS = updateThreeDatasetsChartJS 197 | updateDiskUsageChartJS = updateDoubleDatasetChartJS 198 | updateNetworkUsageChartJS = updateDoubleDatasetChartJS 199 | 200 | {- 201 | updateSingleDatasetChartJS :: String 202 | updateSingleDatasetChartJS = concat 203 | [ "window.charts.get(%1).data.labels.push(%2);" 204 | , "var len = window.charts.get(%1).data.labels.length;" 205 | , "if (len == 900) {" 206 | , " window.charts.get(%1).data.datasets[0].data.splice(0, len);" 207 | , " window.charts.get(%1).data.labels.splice(0, len);" 208 | , "}" 209 | , "window.charts.get(%1).data.datasets[0].data.push(%3);" 210 | , "window.charts.get(%1).update({duration: 0});" 211 | , "window.charts.get(%1).resize();" 212 | ] 213 | -} 214 | 215 | updateDoubleDatasetChartJS :: String 216 | updateDoubleDatasetChartJS = concat 217 | [ "window.charts.get(%1).data.labels.push(%2);" 218 | , "var len = window.charts.get(%1).data.labels.length;" 219 | , "if (len == 900) {" 220 | , " window.charts.get(%1).data.datasets[0].data.splice(0, len);" 221 | , " window.charts.get(%1).data.datasets[1].data.splice(0, len);" 222 | , " window.charts.get(%1).data.labels.splice(0, len);" 223 | , "}" 224 | , "window.charts.get(%1).data.datasets[0].data.push(%3);" 225 | , "window.charts.get(%1).data.datasets[1].data.push(%4);" 226 | , "window.charts.get(%1).update({duration: 0});" 227 | , "window.charts.get(%1).resize();" 228 | ] 229 | 230 | updateThreeDatasetsChartJS :: String 231 | updateThreeDatasetsChartJS = concat 232 | [ "window.charts.get(%1).data.labels.push(%2);" 233 | , "var len = window.charts.get(%1).data.labels.length;" 234 | , "if (len == 900) {" 235 | , " window.charts.get(%1).data.datasets[0].data.splice(0, len);" 236 | , " window.charts.get(%1).data.datasets[1].data.splice(0, len);" 237 | , " window.charts.get(%1).data.datasets[2].data.splice(0, len);" 238 | , " window.charts.get(%1).data.labels.splice(0, len);" 239 | , "}" 240 | , "window.charts.get(%1).data.datasets[0].data.push(%3);" 241 | , "window.charts.get(%1).data.datasets[1].data.push(%4);" 242 | , "window.charts.get(%1).data.datasets[2].data.push(%5);" 243 | , "window.charts.get(%1).update({duration: 0});" 244 | , "window.charts.get(%1).resize();" 245 | ] 246 | 247 | -- During changing of panes' size we have to explicitly recise charts. 248 | resizeChartJS :: String 249 | resizeChartJS = "window.charts.get(%1).resize();" 250 | -------------------------------------------------------------------------------- /src/Cardano/RTView/GUI/JS/Utils.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.GUI.JS.Utils 2 | ( copyTextToClipboard 3 | , downloadCSVFile 4 | , goToTab 5 | ) where 6 | 7 | copyTextToClipboard :: String 8 | copyTextToClipboard = concat 9 | [ "const listener = function(ev) {" 10 | , " ev.preventDefault();" 11 | , " ev.clipboardData.setData('text/plain', %1);" 12 | , "};" 13 | , "document.addEventListener('copy', listener);" 14 | , "document.execCommand('copy');" 15 | , "document.removeEventListener('copy', listener);" 16 | ] 17 | 18 | downloadCSVFile :: String 19 | downloadCSVFile = concat 20 | [ "var element = document.createElement('a');" 21 | , "element.setAttribute('href', 'data:application/csv;charset=utf-8,' + %2);" 22 | , "element.setAttribute('download', %1);" 23 | , "element.style.display = 'none';" 24 | , "document.body.appendChild(element);" 25 | , "element.click();" 26 | , "document.body.removeChild(element);" 27 | ] 28 | 29 | goToTab :: String 30 | goToTab = concat 31 | [ "var element = document.getElementById(%1);" 32 | , "element.click();" 33 | ] 34 | -------------------------------------------------------------------------------- /src/Cardano/RTView/GUI/Markup/OwnInfo.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE LambdaCase #-} 3 | {-# LANGUAGE RecordWildCards #-} 4 | 5 | module Cardano.RTView.GUI.Markup.OwnInfo 6 | ( mkOwnInfo 7 | ) where 8 | 9 | import Control.Monad (forM, void) 10 | import Data.List (find, intersperse) 11 | import qualified Data.Text as T 12 | import Data.Version (showVersion) 13 | import System.FilePath.Posix (takeDirectory) 14 | 15 | import qualified Graphics.UI.Threepenny as UI 16 | import Graphics.UI.Threepenny.Core (Element, UI, element, liftIO, set, string, (#), (#+)) 17 | 18 | import Cardano.BM.Configuration (Configuration) 19 | import qualified Cardano.BM.Configuration.Model as CM 20 | import Cardano.BM.Data.Output (ScribeDefinition (..), ScribeKind (..)) 21 | 22 | import Cardano.RTView.CLI (RTViewParams (..)) 23 | import Cardano.RTView.Config (configFileIsProvided, notificationsFileIsProvided, 24 | logFilesDir, savedConfigurationFile, 25 | savedNotificationsFile, savedRTViewParamsFile) 26 | import Cardano.RTView.GUI.Elements (HTMLClass (..), (#.), hideIt) 27 | import Cardano.RTView.GUI.JS.Utils (copyTextToClipboard) 28 | import Cardano.RTView.Git.Rev (gitRev) 29 | import Cardano.RTView.SupportedNodes (supportedNodesVersions) 30 | import Paths_cardano_rt_view (version) 31 | 32 | mkOwnInfo 33 | :: Configuration 34 | -> RTViewParams 35 | -> UI Element 36 | mkOwnInfo config params = do 37 | closeButton <- UI.img #. [W3DisplayTopright, RTViewInfoClose] 38 | # set UI.src "/static/images/times.svg" 39 | # set UI.title__ "Close" 40 | versions <- nodesVersions 41 | (pathToConfigFile, pathToNotificationsFile, pathToParamsFile, pathToLogsDir) 42 | <- liftIO $ getPaths config params 43 | 44 | copyPathToConfigFile 45 | <- UI.img #. [RTViewInfoCopyPathIcon] 46 | # set UI.src "/static/images/clipboard.svg" 47 | # set UI.title__ "Copy this path to clipboard" 48 | copyPathToNotificationsFile 49 | <- UI.img #. [RTViewInfoCopyPathIcon] 50 | # set UI.src "/static/images/clipboard.svg" 51 | # set UI.title__ "Copy this path to clipboard" 52 | copyPathToParamsFile 53 | <- UI.img #. [RTViewInfoCopyPathIcon] 54 | # set UI.src "/static/images/clipboard.svg" 55 | # set UI.title__ "Copy this path to clipboard" 56 | copyPathToLogsDir 57 | <- UI.img #. [RTViewInfoCopyPathIcon] 58 | # set UI.src "/static/images/clipboard.svg" 59 | # set UI.title__ "Copy this path to clipboard" 60 | 61 | void $ UI.onEvent (UI.click copyPathToConfigFile) $ \_ -> 62 | UI.runFunction $ UI.ffi copyTextToClipboard pathToConfigFile 63 | void $ UI.onEvent (UI.click copyPathToNotificationsFile) $ \_ -> 64 | UI.runFunction $ UI.ffi copyTextToClipboard pathToNotificationsFile 65 | void $ UI.onEvent (UI.click copyPathToParamsFile) $ \_ -> 66 | UI.runFunction $ UI.ffi copyTextToClipboard pathToParamsFile 67 | void $ UI.onEvent (UI.click copyPathToLogsDir) $ \_ -> 68 | UI.runFunction $ UI.ffi copyTextToClipboard pathToLogsDir 69 | 70 | let rtViewVersion = showVersion version 71 | 72 | ownInfo <- 73 | UI.div #. [W3Modal] #+ 74 | [ UI.div #. [W3ModalContent, W3AnimateTop, W3Card4] #+ 75 | [ UI.div #. [W3Container, RTViewInfoTop] #+ 76 | [ element closeButton 77 | , UI.h2 #+ [ string "RTView Info" ] 78 | ] 79 | , UI.div #. [W3Container, W3Row, RTViewInfoContainer] #+ 80 | [ UI.div #. [W3Half] #+ 81 | [ vSpacer 82 | , UI.div #+ [string "Version" # set UI.title__ "Version of RTView"] 83 | , UI.div #+ [string "Commit" # set UI.title__ "Git commit RTView was built from"] 84 | , UI.div #+ [string "Platform" # set UI.title__ "Platform RTView is working on"] 85 | , vSpacer 86 | , UI.div #+ [string "Supported nodes" # set UI.title__ "Versions of the nodes RTView was tested with"] 87 | , vSpacer 88 | , UI.div #+ [string "Configuration file" # set UI.title__ "The path to RTView configuration file"] 89 | , UI.div #+ [string "Notifications file" # set UI.title__ "The path to RTView notifications file"] 90 | , UI.div #+ [string "Parameters file" # set UI.title__ "The path to RTView parameters file"] 91 | , UI.div #+ [string "Logs directory" # set UI.title__ "The path to RTView logs directory"] 92 | , vSpacer 93 | ] 94 | , UI.div #. [W3Half, NodeInfoValues] #+ 95 | [ vSpacer 96 | , UI.div #+ 97 | [ UI.anchor #. [CommitLink] 98 | # set UI.href ("https://github.com/input-output-hk/cardano-rt-view/releases/tag/" 99 | <> rtViewVersion) 100 | # set UI.target "_blank" 101 | # set UI.title__ ("See release tag " 102 | <> rtViewVersion <> " in cardano-rt-view repository") 103 | # set UI.text rtViewVersion 104 | ] 105 | , UI.div #+ 106 | [ UI.anchor #. [CommitLink] 107 | # set UI.href ("https://github.com/input-output-hk/cardano-rt-view/commit/" 108 | <> gitRev) 109 | # set UI.target "_blank" 110 | # set UI.title__ "Browse cardano-rt-view repository on this commit" 111 | # set UI.text (take 7 gitRev) 112 | ] 113 | , UI.div #+ [string rtViewPlaform] 114 | , vSpacer 115 | , UI.div #+ (intersperse (string ", ") versions) 116 | , vSpacer 117 | , UI.div #+ 118 | [ string (preparePathIfNeeded pathToConfigFile) # set UI.title__ pathToConfigFile 119 | , element copyPathToConfigFile 120 | ] 121 | , UI.div #+ 122 | [ string (preparePathIfNeeded pathToNotificationsFile) # set UI.title__ pathToNotificationsFile 123 | , element copyPathToNotificationsFile 124 | ] 125 | , UI.div #+ 126 | [ string (preparePathIfNeeded pathToParamsFile) # set UI.title__ pathToParamsFile 127 | , element copyPathToParamsFile 128 | ] 129 | , UI.div #+ 130 | [ string (preparePathIfNeeded pathToLogsDir) # set UI.title__ pathToLogsDir 131 | , element copyPathToLogsDir 132 | ] 133 | , vSpacer 134 | ] 135 | ] 136 | ] 137 | ] 138 | void $ UI.onEvent (UI.click closeButton) $ \_ -> do 139 | element ownInfo # hideIt 140 | 141 | return ownInfo 142 | 143 | vSpacer :: UI Element 144 | vSpacer = UI.div #. [NodeInfoVSpacer] #+ [] 145 | 146 | rtViewPlaform :: String 147 | #if defined(mingw32_HOST_OS) 148 | rtViewPlaform = "Windows" 149 | #elif defined(linux_HOST_OS) 150 | rtViewPlaform = "Linux" 151 | #elif defined(darwin_HOST_OS) 152 | rtViewPlaform = "macOS" 153 | #else 154 | rtViewPlaform = "Unknown" 155 | #endif 156 | 157 | nodesVersions :: UI [UI Element] 158 | nodesVersions = 159 | forM supportedNodesVersions $ \ver -> do 160 | element <$> UI.anchor #. [CommitLink] 161 | # set UI.href ("https://github.com/input-output-hk/cardano-node/releases/tag/" 162 | <> T.unpack ver) 163 | # set UI.target "_blank" 164 | # set UI.title__ ("See release tag " <> T.unpack ver <> " in cardano-node repository") 165 | # set UI.text (T.unpack ver) 166 | 167 | getPaths 168 | :: Configuration 169 | -> RTViewParams 170 | -> IO (FilePath, FilePath, FilePath, FilePath) 171 | getPaths config params = do 172 | logsDir <- 173 | (find (\sd -> scKind sd == FileSK) <$> CM.getSetupScribes config) >>= \case 174 | Nothing -> logFilesDir 175 | Just sd -> return . takeDirectory . T.unpack . scName $ sd 176 | configFile <- 177 | if configFileIsProvided params 178 | then return $ rtvConfig params 179 | else savedConfigurationFile 180 | notificationsFile <- 181 | if notificationsFileIsProvided params 182 | then return $ rtvNotifications params 183 | else savedNotificationsFile 184 | paramsFile <- savedRTViewParamsFile 185 | return (configFile, notificationsFile, paramsFile, logsDir) 186 | 187 | preparePathIfNeeded :: FilePath -> String 188 | preparePathIfNeeded aPath = if tooLongPath then shortenedPath else aPath 189 | where 190 | tooLongPath = len > 20 191 | len = length aPath 192 | shortenedPath = take 10 aPath <> "..." <> drop (len - 10) aPath 193 | -------------------------------------------------------------------------------- /src/Cardano/RTView/Git/Rev.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module Cardano.RTView.Git.Rev 4 | ( gitRev 5 | ) where 6 | 7 | import Data.FileEmbed (dummySpaceWith) 8 | import Data.Text (Text) 9 | import qualified Data.Text as T 10 | import Data.Text.Encoding (decodeUtf8) 11 | 12 | import Cardano.RTView.Git.RevTH (gitRevFromGit) 13 | 14 | gitRev :: String 15 | gitRev | gitRevEmbed /= zeroRev = T.unpack gitRevEmbed 16 | | T.null fromGit = T.unpack zeroRev 17 | | otherwise = T.unpack fromGit 18 | where 19 | -- Git revision embedded after compilation using 20 | -- Data.FileEmbed.injectWith. If nothing has been injected, 21 | -- this will be filled with 0 characters. 22 | gitRevEmbed :: Text 23 | gitRevEmbed = decodeUtf8 $(dummySpaceWith "gitrev" 40) 24 | 25 | -- Git revision found during compilation by running git. If 26 | -- git could not be run, then this will be empty. 27 | fromGit = T.strip (T.pack $(gitRevFromGit)) 28 | 29 | zeroRev :: Text 30 | zeroRev = "0000000000000000000000000000000000000000" 31 | -------------------------------------------------------------------------------- /src/Cardano/RTView/Git/RevTH.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module Cardano.RTView.Git.RevTH 4 | ( gitRevFromGit 5 | ) where 6 | 7 | import Control.Exception (handleJust) 8 | import qualified Language.Haskell.TH as TH 9 | import System.Exit (ExitCode (..)) 10 | import System.IO.Error (ioeGetErrorType, isDoesNotExistErrorType) 11 | import System.Process (readProcessWithExitCode) 12 | 13 | -- | RTView git revision found by running git rev-parse. 14 | -- If git could not be executed, an empty string will be returned. 15 | gitRevFromGit :: TH.Q TH.Exp 16 | gitRevFromGit = TH.LitE . TH.StringL <$> TH.runIO runGitRevParse 17 | where 18 | runGitRevParse :: IO String 19 | runGitRevParse = handleJust missingGit (const $ pure "") $ do 20 | (exitCode, output, _) <- 21 | readProcessWithExitCode "git" ["rev-parse", "--verify", "HEAD"] "" 22 | pure $ case exitCode of 23 | ExitSuccess -> output 24 | _ -> "" 25 | 26 | missingGit e = if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Cardano/RTView/NodeState/CSV.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.NodeState.CSV 2 | ( mkCSVWithErrorsForHref 3 | ) where 4 | 5 | import Data.Csv (ToField (..), ToNamedRecord (..), (.=), 6 | encodeByName, header, namedRecord) 7 | import qualified Data.ByteString.Char8 as BSC 8 | import qualified Data.ByteString.Lazy as BSL 9 | import qualified Data.Text as T 10 | import Data.Time.Clock (UTCTime (..)) 11 | import Data.Word (Word8) 12 | import GHC.Base (unsafeChr) 13 | 14 | import Cardano.BM.Data.Severity (Severity (..)) 15 | 16 | import Cardano.RTView.NodeState.Types (NodeError (..)) 17 | 18 | instance ToField UTCTime where 19 | toField = BSC.pack . show 20 | 21 | instance ToField Severity where 22 | toField = BSC.pack . show 23 | 24 | instance ToNamedRecord NodeError where 25 | toNamedRecord (NodeError ts sev msg _) = 26 | namedRecord 27 | [ "Timestamp" .= ts 28 | , "Severity" .= sev 29 | , "Message" .= msg 30 | ] 31 | 32 | mkCSVWithErrorsForHref :: [NodeError] -> String 33 | mkCSVWithErrorsForHref allErrors = prepareForHref csv 34 | where 35 | csv = map w2c . BSL.unpack . encodeByName header' $ allErrors 36 | header' = header ["Timestamp", "Severity", "Message"] 37 | w2c :: Word8 -> Char 38 | w2c = unsafeChr . fromIntegral 39 | 40 | prepareForHref :: String -> String 41 | prepareForHref = 42 | T.unpack 43 | . T.replace " " "%20" 44 | . T.replace "," "%2C" 45 | . T.replace "!" "%21" 46 | . T.replace "#" "%23" 47 | . T.replace "$" "%24" 48 | . T.replace "&" "%26" 49 | . T.replace "'" "%27" 50 | . T.replace "(" "%28" 51 | . T.replace ")" "%29" 52 | . T.replace "*" "%2A" 53 | . T.replace "+" "%2B" 54 | . T.replace "/" "%2F" 55 | . T.replace ":" "%3A" 56 | . T.replace ";" "%3B" 57 | . T.replace "=" "%3D" 58 | . T.replace "?" "%3F" 59 | . T.replace "@" "%40" 60 | . T.replace "[" "%5B" 61 | . T.replace "]" "%5D" 62 | . T.pack 63 | -------------------------------------------------------------------------------- /src/Cardano/RTView/NodeState/Parsers.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | 3 | module Cardano.RTView.NodeState.Parsers 4 | ( extractPeersInfo 5 | ) where 6 | 7 | import Data.Aeson (Object, (.:)) 8 | import qualified Data.Aeson as A 9 | import Data.Text (Text) 10 | 11 | import Cardano.RTView.NodeState.Types (PeerInfo (..)) 12 | 13 | extractPeersInfo :: Object -> [PeerInfo] 14 | extractPeersInfo peersObj = 15 | case result of 16 | A.Success (ConnectedPeers cPeers) -> 17 | flip map cPeers $ \p -> 18 | PeerInfo 19 | { piEndpoint = peerAddress p 20 | , piBytesInF = peerBytesInF p 21 | , piReqsInF = peerReqsInF p 22 | , piBlocksInF = peerBlocksInF p 23 | , piSlotNumber = peerSlotNo p 24 | , piStatus = peerStatus p 25 | } 26 | A.Error _ -> [] 27 | where 28 | result :: A.Result ConnectedPeers 29 | result = A.fromJSON $ A.Object peersObj 30 | 31 | -- Types for decoding from JSON-representation. 32 | 33 | newtype ConnectedPeers = ConnectedPeers [ConnectedPeer] 34 | 35 | data ConnectedPeer 36 | = ConnectedPeer 37 | { peerBytesInF :: !Text 38 | , peerReqsInF :: !Text 39 | , peerBlocksInF :: !Text 40 | , peerAddress :: !Text 41 | , peerSlotNo :: !Text 42 | , peerStatus :: !Text 43 | } 44 | 45 | instance A.FromJSON ConnectedPeers where 46 | parseJSON = A.withObject "ConnectedPeers" $ \v -> ConnectedPeers 47 | <$> v .: "peers" 48 | 49 | instance A.FromJSON ConnectedPeer where 50 | parseJSON = A.withObject "ConnectedPeer" $ \v -> ConnectedPeer 51 | <$> v .: "peerBytesInF" 52 | <*> v .: "peerReqsInF" 53 | <*> v .: "peerBlocksInF" 54 | <*> v .: "peerAddress" 55 | <*> v .: "peerSlotNo" 56 | <*> v .: "peerStatus" 57 | -------------------------------------------------------------------------------- /src/Cardano/RTView/Notifications/CheckEvents.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RecordWildCards #-} 2 | 3 | module Cardano.RTView.Notifications.CheckEvents 4 | ( launchNotifications 5 | ) where 6 | 7 | import Control.Concurrent.STM.TVar (TVar, readTVarIO) 8 | import Control.Monad (forM, forever, when) 9 | import qualified Data.HashMap.Strict as HM 10 | import Data.Text (Text, pack) 11 | import Data.Time.Clock (getCurrentTime) 12 | import System.Time.Extra (sleep) 13 | 14 | import Cardano.BM.Data.Severity (Severity (..)) 15 | import Cardano.BM.Trace (Trace, logDebug) 16 | 17 | import Cardano.RTView.NodeState.Types 18 | import Cardano.RTView.Notifications.Types 19 | import Cardano.RTView.Notifications.Send (sendNotifications) 20 | 21 | launchNotifications 22 | :: Trace IO Text 23 | -> TVar NodesState 24 | -> TVar NotificationSettings 25 | -> IO () 26 | launchNotifications tr nsTVar notifyTVar = forever $ do 27 | nSet@NotificationSettings {..} <- readTVarIO notifyTVar 28 | logDebug tr $ "Notifications enabled: " <> pack (show nsEnabled) 29 | when nsEnabled $ do 30 | logDebug tr $ "Notifications are enabled, current settings: " <> pack (show nSet) 31 | notifyEvents <- checkNotifyEvents nsTVar nSet 32 | sendNotifications tr nSet notifyEvents 33 | sleep $ fromIntegral nsCheckPeriodInSec 34 | 35 | checkNotifyEvents 36 | :: TVar NodesState 37 | -> NotificationSettings 38 | -> IO [NotifyEvent] 39 | checkNotifyEvents nsTVar settings = do 40 | let EventsToNotify {..} = nsEventsToNotify settings 41 | currentNodesState <- readTVarIO nsTVar 42 | errs <- checkErrors errorsEvents currentNodesState 43 | bErrs <- checkBlockchainErrors blockchainEvents currentNodesState 44 | return $ errs ++ bErrs 45 | 46 | checkErrors 47 | :: ErrorsEvents 48 | -> NodesState 49 | -> IO [NotifyEvent] 50 | checkErrors ErrorsEvents {..} nodesState = 51 | concat <$> forM (HM.toList nodesState) mkErrEventsForNode 52 | where 53 | mkErrEventsForNode (nameOfNode, nodeState) = do 54 | let ErrorsMetrics {..} = nodeErrors nodeState 55 | errorsShouldBeNotified = filter errorShouldBeNotified errors 56 | return $ map (\(NodeError ts sev msg _) -> NotifyEvent ts nameOfNode (mkErrMessage sev msg)) 57 | errorsShouldBeNotified 58 | 59 | errorShouldBeNotified (NodeError _ sev _ _) = 60 | case sev of 61 | Warning -> aboutWarnings 62 | Error -> aboutErrors 63 | Critical -> aboutCriticals 64 | Alert -> aboutAlerts 65 | Emergency -> aboutEmergencies 66 | _ -> False 67 | 68 | mkErrMessage sev msg = 69 | "Error occurred: severity '" <> pack (show sev) <> "', message: " <> msg 70 | 71 | checkBlockchainErrors 72 | :: BlockchainEvents 73 | -> NodesState 74 | -> IO [NotifyEvent] 75 | checkBlockchainErrors BlockchainEvents {..} nodesState = 76 | concat <$> forM (HM.toList nodesState) mkBCEventsForNode 77 | where 78 | mkBCEventsForNode (nameOfNode, nodeState) = do 79 | let ForgeMetrics {..} = forgeMetrics nodeState 80 | ts <- getCurrentTime 81 | let slotsMissedEv = 82 | if slotsMissedNumber > 0 && aboutMissedSlots 83 | then [NotifyEvent ts nameOfNode $ pack (show slotsMissedNumber) <> " slots were missed!"] 84 | else [] 85 | cannotForgeEv = 86 | if nodeCannotForge > 0 && aboutCannotForge 87 | then [NotifyEvent ts nameOfNode $ "Node couldn't forge " <> pack (show aboutCannotForge) <> " times!"] 88 | else [] 89 | return $ slotsMissedEv ++ cannotForgeEv 90 | -------------------------------------------------------------------------------- /src/Cardano/RTView/Notifications/Send.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.Notifications.Send 2 | ( sendNotifications 3 | , sendTestEmail 4 | ) where 5 | 6 | import Data.Text (Text) 7 | 8 | import Cardano.BM.Trace (Trace) 9 | 10 | import Cardano.RTView.Notifications.Types 11 | import Cardano.RTView.Notifications.Send.Email (createAndSendEmails, 12 | createAndSendTestEmail) 13 | 14 | sendNotifications 15 | :: Trace IO Text 16 | -> NotificationSettings 17 | -> [NotifyEvent] 18 | -> IO () 19 | sendNotifications _ _ [] = return () 20 | sendNotifications tr settings notifyEvents = do 21 | -- Current release provides only email-notifications. 22 | let eSettings = emailSettings . nsHowToNotify $ settings 23 | createAndSendEmails tr eSettings notifyEvents 24 | 25 | sendTestEmail 26 | :: NotificationSettings 27 | -> IO Text 28 | sendTestEmail settings = do 29 | let eSettings = emailSettings . nsHowToNotify $ settings 30 | createAndSendTestEmail eSettings 31 | -------------------------------------------------------------------------------- /src/Cardano/RTView/Notifications/Send/Email.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE RecordWildCards #-} 3 | 4 | module Cardano.RTView.Notifications.Send.Email 5 | ( createAndSendEmails 6 | , createAndSendTestEmail 7 | ) where 8 | 9 | import Control.Exception (SomeException, try) 10 | import Control.Monad (void) 11 | import Control.Monad.Extra (whenJust) 12 | import Data.Text (Text) 13 | import qualified Data.Text as T 14 | import qualified Data.Text.Lazy as LT 15 | 16 | import Network.Mail.SMTP (sendMailWithLogin', sendMailWithLoginSTARTTLS', 17 | sendMailWithLoginTLS') 18 | import Network.Mail.Mime (Address (..), Mail (..), simpleMail') 19 | 20 | import Cardano.BM.Trace (Trace, logError, logDebug, logNotice) 21 | 22 | import Cardano.RTView.Notifications.Types 23 | 24 | createAndSendEmails 25 | :: Trace IO Text 26 | -> EmailSettings 27 | -> [NotifyEvent] 28 | -> IO () 29 | createAndSendEmails _ _ [] = return () 30 | createAndSendEmails tr eSettings events = do 31 | -- To keep email traffic as low as possible, 32 | -- we create only one email with all events in it. 33 | let email = createEmail eSettings events 34 | logDebug tr $ "Email notifications, new email: " <> T.pack (show email) 35 | void $ sendEmail (Just tr) eSettings email 36 | 37 | createAndSendTestEmail 38 | :: EmailSettings 39 | -> IO Text 40 | createAndSendTestEmail eSettings = 41 | sendEmail Nothing eSettings $ createTestEmail eSettings 42 | 43 | createEmail 44 | :: EmailSettings 45 | -> [NotifyEvent] 46 | -> Mail 47 | createEmail EmailSettings {..} events = 48 | simpleMail' to from emSubject body 49 | where 50 | to = Address Nothing emEmailTo 51 | from = Address (Just "Cardano RTView") emEmailFrom 52 | body = LT.fromStrict . T.intercalate "\n\n" $ header : map mkBodyPart events 53 | header = "Cardano RTView Notification" 54 | mkBodyPart NotifyEvent {..} = 55 | T.pack (show evTime) <> ", from the node '" <> evNodeName <> "': " <> evMessage 56 | 57 | createTestEmail 58 | :: EmailSettings 59 | -> Mail 60 | createTestEmail EmailSettings {..} = 61 | simpleMail' to from emSubject body 62 | where 63 | to = Address Nothing emEmailTo 64 | from = Address (Just "Cardano RTView") emEmailFrom 65 | body = "This is a test email from Cardano RTView. Congrats: your email notification settings are correct!" 66 | 67 | sendEmail 68 | :: Maybe (Trace IO Text) 69 | -> EmailSettings 70 | -> Mail 71 | -> IO Text 72 | sendEmail tr EmailSettings {..} mail = 73 | if cannotBeSent 74 | then logAndReturn logError cannotBeSentMessage 75 | else try (sender host port user pass mail) >>= \case 76 | Left (e :: SomeException) -> logAndReturn logError $ unableToSendMessage <> T.pack (show e) 77 | Right _ -> logAndReturn logNotice sentMessage 78 | where 79 | sender = case emSSL of 80 | TLS -> sendMailWithLoginTLS' 81 | StartTLS -> sendMailWithLoginSTARTTLS' 82 | NoSSL -> sendMailWithLogin' 83 | host = T.unpack emServerHost 84 | port = fromIntegral emServerPort 85 | user = T.unpack emUsername 86 | pass = T.unpack emPassword 87 | cannotBeSent = null host 88 | || port == 0 89 | || null user 90 | || null pass 91 | || T.null emEmailTo 92 | cannotBeSentMessage = "Email cannot be sent: please fill in all inputs marked by an asterisk." 93 | unableToSendMessage = "Unable to send email: " 94 | sentMessage = "Yay! Email notification to " <> emEmailTo <> " sent." 95 | logAndReturn logger message = whenJust tr (flip logger message) >> return message 96 | -------------------------------------------------------------------------------- /src/Cardano/RTView/Notifications/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE LambdaCase #-} 4 | 5 | module Cardano.RTView.Notifications.Types 6 | ( NotifyEvent (..) 7 | , NotificationSettings (..) 8 | , ErrorsEvents (..) 9 | , BlockchainEvents (..) 10 | , EventsToNotify (..) 11 | , HowToNotify (..) 12 | , SSL (..) 13 | , EmailSettings (..) 14 | , initialNotificationSettings 15 | ) where 16 | 17 | import Control.DeepSeq (NFData (..)) 18 | import Data.Aeson (FromJSON, ToJSON) 19 | import Data.Text (Text) 20 | import Data.Time.Clock (UTCTime (..)) 21 | import GHC.Generics (Generic) 22 | 23 | -- | Some event we should notify about. 24 | data NotifyEvent = NotifyEvent 25 | { evTime :: !UTCTime 26 | , evNodeName :: !Text 27 | , evMessage :: !Text 28 | } deriving (Eq, Generic, NFData, Show) 29 | 30 | -- | Complete notification settings. 31 | data NotificationSettings = NotificationSettings 32 | { nsEnabled :: !Bool 33 | , nsCheckPeriodInSec :: !Int 34 | , nsEventsToNotify :: !EventsToNotify 35 | , nsHowToNotify :: !HowToNotify 36 | } deriving (Generic, NFData, Show, FromJSON, ToJSON) 37 | 38 | data ErrorsEvents = ErrorsEvents 39 | { aboutWarnings :: !Bool 40 | , aboutErrors :: !Bool 41 | , aboutCriticals :: !Bool 42 | , aboutAlerts :: !Bool 43 | , aboutEmergencies :: !Bool 44 | } deriving (Generic, NFData, Show, FromJSON, ToJSON) 45 | 46 | data BlockchainEvents = BlockchainEvents 47 | { aboutMissedSlots :: !Bool 48 | , aboutCannotForge :: !Bool 49 | } deriving (Generic, NFData, Show, FromJSON, ToJSON) 50 | 51 | data EventsToNotify = EventsToNotify 52 | { errorsEvents :: !ErrorsEvents 53 | , blockchainEvents :: !BlockchainEvents 54 | } deriving (Generic, NFData, Show, FromJSON, ToJSON) 55 | 56 | data HowToNotify = HowToNotify 57 | { emailSettings :: !EmailSettings 58 | } deriving (Generic, NFData, Show, FromJSON, ToJSON) 59 | 60 | data SSL 61 | = TLS 62 | | StartTLS 63 | | NoSSL 64 | deriving (Eq, Generic, NFData, Show, Read, FromJSON, ToJSON) 65 | 66 | data EmailSettings = EmailSettings 67 | { emServerHost :: !Text 68 | , emServerPort :: !Int 69 | , emUsername :: !Text 70 | , emPassword :: !Text 71 | , emSSL :: !SSL 72 | , emEmailFrom :: !Text 73 | , emEmailTo :: !Text 74 | , emSubject :: !Text 75 | } deriving (Generic, NFData, Show, FromJSON, ToJSON) 76 | 77 | initialNotificationSettings :: NotificationSettings 78 | initialNotificationSettings = NotificationSettings 79 | { nsEnabled = True 80 | , nsCheckPeriodInSec = 120 81 | , nsEventsToNotify = 82 | EventsToNotify 83 | { errorsEvents = 84 | ErrorsEvents 85 | { aboutWarnings = True 86 | , aboutErrors = True 87 | , aboutCriticals = True 88 | , aboutAlerts = True 89 | , aboutEmergencies = True 90 | } 91 | , blockchainEvents = 92 | BlockchainEvents 93 | { aboutMissedSlots = True 94 | , aboutCannotForge = True 95 | } 96 | } 97 | , nsHowToNotify = 98 | HowToNotify 99 | { emailSettings = 100 | EmailSettings 101 | { emServerHost = "" 102 | , emServerPort = 587 103 | , emUsername = "" 104 | , emPassword = "" 105 | , emSSL = TLS 106 | , emEmailFrom = "" 107 | , emEmailTo = "" 108 | , emSubject = "Cardano RTView Notification" 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Cardano/RTView/SupportedNodes.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.SupportedNodes 2 | ( supportedNodesVersions 3 | , showSupportedNodesVersions 4 | ) where 5 | 6 | import Data.Text (Text, intercalate) 7 | 8 | -- | Both RTView and 'cardano-node' are under active development. 9 | -- Since the things change frequently, RTView maintain the list 10 | -- of nodes' versions that works with this release of RTView. 11 | supportedNodesVersions :: [Text] 12 | supportedNodesVersions = 13 | [ "1.24.1" 14 | , "1.24.2" 15 | , "1.25.0" 16 | ] 17 | 18 | showSupportedNodesVersions :: Text 19 | showSupportedNodesVersions = intercalate ", " supportedNodesVersions 20 | -------------------------------------------------------------------------------- /src/Cardano/RTView/WebServer.hs: -------------------------------------------------------------------------------- 1 | module Cardano.RTView.WebServer 2 | ( launchWebServer 3 | ) where 4 | 5 | import Control.Concurrent.STM.TVar (TVar) 6 | import Control.Monad (void) 7 | import Data.Text (Text) 8 | import qualified Graphics.UI.Threepenny as UI 9 | import Graphics.UI.Threepenny.Core (UI, liftIO, onEvent, set, (#), (#+)) 10 | import Graphics.UI.Threepenny.Timer (interval, start, tick, timer) 11 | 12 | import Cardano.BM.Configuration (Configuration) 13 | import Cardano.BM.Data.Configuration (RemoteAddrNamed (..)) 14 | import Cardano.BM.Trace (Trace, appendName, logNotice) 15 | 16 | import Cardano.RTView.CLI (RTViewParams (..)) 17 | import Cardano.RTView.GUI.CSS.Style (ownCSS) 18 | import Cardano.RTView.GUI.Elements (TmpElements, pageTitle) 19 | import Cardano.RTView.GUI.Markup.PageBody (mkPageBody) 20 | import Cardano.RTView.GUI.Updater (updateGUI) 21 | import Cardano.RTView.NodeState.Types (NodesState) 22 | import Cardano.RTView.Notifications.Types (NotificationSettings) 23 | 24 | launchWebServer 25 | :: Trace IO Text 26 | -> Configuration 27 | -> TVar NodesState 28 | -> TVar TmpElements 29 | -> TVar NotificationSettings 30 | -> RTViewParams 31 | -> [RemoteAddrNamed] 32 | -> IO () 33 | launchWebServer tr config nsTVar tmpElsTVar notifyTVar params acceptors = 34 | UI.startGUI wsConfig $ mainPage tr config nsTVar tmpElsTVar notifyTVar params acceptors 35 | where 36 | wsConfig = UI.defaultConfig 37 | { UI.jsStatic = Just $ rtvStatic params 38 | , UI.jsPort = Just $ rtvPort params 39 | -- By default it listens on 127.0.0.1, but it cannot be accessed 40 | -- from another machine, so change it to 0.0.0.0. 41 | , UI.jsAddr = Just "0.0.0.0" 42 | } 43 | 44 | mainPage 45 | :: Trace IO Text 46 | -> Configuration 47 | -> TVar NodesState 48 | -> TVar TmpElements 49 | -> TVar NotificationSettings 50 | -> RTViewParams 51 | -> [RemoteAddrNamed] 52 | -> UI.Window 53 | -> UI () 54 | mainPage tr config nsTVar tmpElsTVar notifyTVar params acceptors window = do 55 | liftIO $ logNotice tr "Web page loading..." 56 | 57 | void $ return window # set UI.title pageTitle 58 | 59 | -- It is assumed that CSS files are available at 'pathToStatic/css/'. 60 | UI.addStyleSheet window "w3.css" 61 | embedOwnCSS window 62 | 63 | -- It is assumed that JS files are available at 'pathToStatic/js/'. 64 | addJavaScript window "chart.js" 65 | 66 | -- Make page's body (HTML markup). 67 | (pageBody, nodesStateElems) <- mkPageBody config 68 | nsTVar 69 | tmpElsTVar 70 | notifyTVar 71 | params 72 | window 73 | acceptors 74 | let guiTr = appendName "GUI" tr 75 | -- Start the timer for GUI update. Every second it will 76 | -- call a function which updates node state elements on the page. 77 | guiUpdateTimer <- timer # set interval 1000 78 | void $ onEvent (tick guiUpdateTimer) $ \_ -> 79 | updateGUI guiTr window nsTVar tmpElsTVar params nodesStateElems 80 | start guiUpdateTimer 81 | 82 | void $ UI.element pageBody 83 | 84 | -- | Add JS library stored locally. 85 | addJavaScript 86 | :: UI.Window 87 | -> FilePath 88 | -> UI () 89 | addJavaScript w filename = void $ do 90 | el <- UI.mkElement "script" # set UI.src ("/static/js/" ++ filename) 91 | UI.getHead w #+ [UI.element el] 92 | 93 | -- | We generate our own CSS using 'clay' package, so embed it in the page's header. 94 | embedOwnCSS 95 | :: UI.Window 96 | -> UI () 97 | embedOwnCSS w = void $ do 98 | el <- UI.mkElement "style" # set UI.html ownCSS 99 | UI.getHead w #+ [UI.element el] 100 | -------------------------------------------------------------------------------- /static/images/bars.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/bell-slash.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/blockchain.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/bugs.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/cardano-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/clipboard.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/cpu.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/disk.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/dropdown-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/dropdown-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/dropdown-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/file-download.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/hide.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/info-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/info.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/key.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/memory.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/mempool.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/network.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/peers.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/question.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/resources.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/rts.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/search.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/show.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/sort.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/times.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/images/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Input Output (Hong Kong) Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/README.md: -------------------------------------------------------------------------------- 1 | # RTView: Automatic Testing 2 | 3 | ## How It Works 4 | 5 | RTView is a separate process that receives `LogObject`s from another process (for example, `cardano-node`) and displays them on HTML-page. It can be shown like this: 6 | 7 | ``` 8 | Process 1 Process 2 Process 3 9 | +--------------+ [LogObject] +-----------------+ web-requests +-------------+ 10 | | cardano-node | ------------> | cardano-rt-view | <-------------- | web browser | 11 | +--------------+ +-----------------+ --------------> +-------------+ 12 | HTML-page 13 | ``` 14 | 15 | To test it automatically, we use additional scripts and programs: 16 | 17 | 1. `sender.sh` - it takes `LogObject`s from predefined JSON-file and sends them to RTView (via UNIX-socket). 18 | 2. `analyzer` - it analyzes RTView UI (complete HTML-page). 19 | 20 | It can be shown like this: 21 | 22 | ``` 23 | Process 1 Process 2 24 | +-----------+ [LogObject] +-----------------+ web-requests +----------+ 25 | logObjects.json --> | sender.sh | ------------> | cardano-rt-view | <-------------- | analyzer | 26 | +-----------+ +-----------------+ --------------> +----------+ 27 | HTML-page 28 | ``` 29 | 30 | Please note that `analyzer` doesn't analyze RTView UI (complete HTML-page) by itself. Instead, it launches the real web browser and use it to analyze the page automatically, using Selenium standalone server and `webdriver` package. It can be shown like this: 31 | 32 | ``` 33 | +----------+ +-------------+ +---------+ web-commands +-----------------+ 34 | | analyzer | ----> | GeckoDriver | ----> | Firefox | -------------> | cardano-rt-view | 35 | +----------+ +-------------+ +---------+ +-----------------+ 36 | \ 37 | \ +-----------------+ 38 | `-------> | Selenium server | 39 | +-----------------+ 40 | ``` 41 | 42 | ## Required Software 43 | 44 | Please make sure you have these commands in your `PATH`: 45 | 46 | 1. `jq` to minimize predefined JSON-file with `LogObject`s. 47 | 2. `nc` to send `LogObject`s from predefined JSON-file to `cardano-rt-view` (via UNIX socket). 48 | 3. `firefix` to interact with RTView UI (complete HTML-page), 49 | 4. `geckodriver` to interact with Firefox, 50 | 5. `java` to launch `selenium-server-standalone`. 51 | 52 | Please note that you have to provide full path to `selenium-server-standalone` file (something like `selenium-server-standalone-3.141.59.jar`) to `runTest.sh` script (see below). `selenium-server-standalone` will be used by `webdriver` package. This server can be downloaded [here](https://www.selenium.dev/downloads/). 53 | 54 | ## How to Run It 55 | 56 | Run `./runTest.sh PATH_TO_SELENIUM_SERVER_JAR` script which launches: 57 | 58 | 1. `cardano-rt-view` process (in the background), 59 | 2. `sender.sh` script, 60 | 3. `selenium-server-standalone` process (in the background), 61 | 4. `analyzer` process. 62 | 63 | ( can be something like `--nix` to select the builder) 64 | 65 | `analyzer` process launches Firefox web browser and sends corresponding web-commands to it. The results returned by `analyzer` are the results of the test. 66 | 67 | ## What Do We Test? 68 | 69 | `analyzer` checks the visual behavior of RTView UI (complete HTML-page): 70 | 71 | 1. checks if node can be hidden/shown, 72 | 2. validates the real values of displayed metrics (for comparing them with values from predefined JSON-file with `LogObject`s). 73 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/configuration/rt-view-config.yaml: -------------------------------------------------------------------------------- 1 | # global filter; messages must have at least this severity to pass: 2 | minSeverity: Debug 3 | 4 | # global file rotation settings: 5 | rotation: 6 | rpLogLimitBytes: 5000000 7 | rpKeepFilesNum: 10 8 | rpMaxAgeHours: 24 9 | 10 | # these backends are initialized: 11 | setupBackends: 12 | - KatipBK 13 | - LogBufferBK 14 | - TraceAcceptorBK 15 | 16 | # if not indicated otherwise, then messages are passed to these backends: 17 | defaultBackends: 18 | - KatipBK 19 | 20 | # here we set up outputs of logging in 'katip': 21 | setupScribes: 22 | - scKind: StdoutSK 23 | scName: stdout 24 | scFormat: ScText 25 | - scKind: FileSK 26 | scFormat: ScJson 27 | scName: "logs/acceptor.json" 28 | 29 | # if not indicated otherwise, then log output is directed to this: 30 | defaultScribes: 31 | - - StdoutSK 32 | - stdout 33 | - - FileSK 34 | - "logs/acceptor.json" 35 | 36 | traceAcceptAt: 37 | - nodeName: "a" 38 | remoteAddr: 39 | tag: RemotePipe 40 | contents: "logs/rt-view-pipe-0" 41 | 42 | # more options which can be passed as key-value pairs: 43 | options: 44 | mapBackends: 45 | cardano-rt-view.acceptor: 46 | - LogBufferBK 47 | - kind: UserDefinedBK 48 | name: ErrorBufferBK 49 | - KatipBK 50 | 51 | 52 | # NodeId: 4 53 | # Protocol: RealPBFT 54 | # NumCoreNodes: 1 55 | # RequiresNetworkMagic: RequiresNoMagic 56 | # PBftSignatureThreshold: 0.5 57 | # TurnOnLogging: True 58 | # ViewMode: SimpleView 59 | # TurnOnLogMetrics: False 60 | # ApplicationName: cardano-sl 61 | # ApplicationVersion: 1 62 | # LastKnownBlockVersion-Major: 0 63 | # LastKnownBlockVersion-Minor: 2 64 | # LastKnownBlockVersion-Alt: 0 65 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/logObjects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "loname": "cardano.node.release", 4 | "locontent": { 5 | "kind": "LogMessage", 6 | "message": "ByronProtocol" 7 | }, 8 | "lometa": { 9 | "tstamp": "2020-08-11T15:04:32.354943121Z", 10 | "hostname": "linux", 11 | "severity": "Notice", 12 | "privacy": "Public", 13 | "tid": "48" 14 | } 15 | }, 16 | { 17 | "loname": "cardano.node.version", 18 | "locontent": { 19 | "kind": "LogMessage", 20 | "message": "1.18.0" 21 | }, 22 | "lometa": { 23 | "tstamp": "2020-08-11T15:04:32.354943121Z", 24 | "hostname": "linux", 25 | "severity": "Notice", 26 | "privacy": "Public", 27 | "tid": "48" 28 | } 29 | }, 30 | { 31 | "loname": "cardano.node.commit", 32 | "locontent": { 33 | "kind": "LogMessage", 34 | "message": "a0eb636" 35 | }, 36 | "lometa": { 37 | "tstamp": "2020-08-11T15:04:32.354943121Z", 38 | "hostname": "linux", 39 | "severity": "Notice", 40 | "privacy": "Public", 41 | "tid": "48" 42 | } 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/rt-view-analyzer.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: rt-view-analyzer 3 | version: 0.3.0 4 | description: HTML page analyzer for RTView automatic testing 5 | license: Apache-2.0 6 | license-files: 7 | LICENSE 8 | NOTICE 9 | author: IOHK 10 | maintainer: operations@iohk.io 11 | build-type: Simple 12 | extra-source-files: README.md 13 | 14 | executable rt-view-analyzer 15 | main-is: Main.hs 16 | other-modules: Analyzers 17 | CLI 18 | Config 19 | -- other-extensions: 20 | build-depends: base >=4.12 && <4.14, 21 | cardano-rt-view, 22 | iohk-monitoring, 23 | text, 24 | webdriver 25 | hs-source-dirs: src 26 | default-language: Haskell2010 27 | default-extensions: OverloadedStrings 28 | 29 | ghc-options: -Wall -Werror 30 | -rtsopts 31 | "-with-rtsopts=-T" 32 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/scripts/runTest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #! nix-shell -i bash -p adoptopenjdk-jre-bin geckodriver 3 | 4 | # preparation 5 | BASEDIR=$(realpath $(dirname "$0")) 6 | 7 | prebuild 'cardano-rt-view' || exit 1 8 | prebuild 'rt-view-analyzer' || exit 1 9 | 10 | set -e 11 | 12 | readonly JSON_WITH_LOG_OBJECTS=../logObjects.json 13 | readonly UNIX_SOCKET=./logs/rt-view-pipe-0 14 | readonly SELENIUM_SERVER_JAR=$1 15 | 16 | readonly RT_VIEW_EXE=cardano-rt-view 17 | readonly RT_VIEW_CONFIG=../configuration/rt-view-config.yaml 18 | readonly RT_VIEW_STATIC_DIR=../../../static 19 | readonly RT_VIEW_WEB_PORT=8024 20 | 21 | echo "Remove old UNIX-socket..." 22 | rm -f "${UNIX_SOCKET}" 23 | 24 | echo "Launch cardano-rt-view..." 25 | run "${RT_VIEW_EXE}" --config "${RT_VIEW_CONFIG}" \ 26 | --static "${RT_VIEW_STATIC_DIR}" \ 27 | --port "${RT_VIEW_WEB_PORT}" & 28 | sleep 2 29 | 30 | test -e ${UNIX_SOCKET} 31 | 32 | echo "Launch sender.sh script..." 33 | ./sender.sh "${JSON_WITH_LOG_OBJECTS}" "${UNIX_SOCKET}" 34 | 35 | ## Since analyzer is using webdriver, we must run selenium-standalone-server first. 36 | ## By default it will listen 127.0.0.1:4444. 37 | nohup java -jar "${SELENIUM_SERVER_JAR}" & 38 | PID_SELENIUM=$! 39 | sleep 2 40 | 41 | ## Launch analyzer 42 | run rt-view-analyzer "${RT_VIEW_CONFIG}" "${JSON_WITH_LOG_OBJECTS}" "${RT_VIEW_WEB_PORT}" 43 | ANALYZER_STATUS=$? 44 | [ $ANALYZER_STATUS -eq 0 ] && echo "Test passed." || echo "Test failed: analyzer returned an error." 45 | sleep 1 46 | 47 | ## Stop selenium-standalone-server. 48 | kill ${PID_SELENIUM} 49 | sleep 1 50 | 51 | ## Stop cardano-rt-view. 52 | kill $(pgrep -f ${RT_VIEW_EXE}) 53 | sleep 1 54 | 55 | # Remove cardano-rt-view' logs. 56 | rm -rf logs 57 | 58 | # Remove nohup artifacts 59 | rm -f nohup.out 60 | 61 | echo "Done." 62 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/scripts/sender.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | readonly JSON_WITH_LOG_OBJECTS=$1 6 | readonly UNIX_SOCKET=$2 7 | 8 | readonly MINIFIED_JSON=/tmp/sender-logObjects-minified.json 9 | readonly PREPARED_FILE=/tmp/sender-hostname-with-logObjects 10 | readonly HOSTNAME="linux" 11 | 12 | # Please note that, due to the TraceAcceptor's code, the file that 13 | # will be passed to UNIX-socket must contain exactly 2 lines: 14 | # 1. host name 15 | # 2. [LogObject] encoded to JSON (minified, without newlines!). 16 | 17 | # Minify JSON from passed file. 18 | jq -c . < "${JSON_WITH_LOG_OBJECTS}" > "${MINIFIED_JSON}" 19 | # Prepare the file. 20 | echo "${HOSTNAME}" >> "${PREPARED_FILE}" 21 | cat "${MINIFIED_JSON}" >> "${PREPARED_FILE}" 22 | # Send it to passed UNIX socket (drop connection after that). 23 | nc -N -U "${UNIX_SOCKET}" < "${PREPARED_FILE}" 24 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/src/Analyzers.hs: -------------------------------------------------------------------------------- 1 | module Analyzers 2 | ( 3 | Units (..) 4 | , checkContentOf 5 | , checkIfMetricCanBeHiddenOrShown 6 | , waitFor 7 | ) 8 | where 9 | 10 | import Control.Concurrent (threadDelay) 11 | import Control.Monad (when) 12 | import Control.Monad.IO.Class (liftIO) 13 | import Data.Text (Text, unpack) 14 | import System.Exit (die) 15 | 16 | import Test.WebDriver 17 | 18 | data Units = Seconds | Milliseconds 19 | 20 | waitFor 21 | :: Int 22 | -> Units 23 | -> WD () 24 | waitFor howMany Seconds = liftIO . threadDelay $ howMany * 1000000 25 | waitFor howMany Milliseconds = liftIO . threadDelay $ howMany * 1000 26 | 27 | checkContentOf 28 | :: Text 29 | -> Text 30 | -> Text 31 | -> WD () 32 | checkContentOf elementId content label = do 33 | realContent <- findElem (ById elementId) >>= getText 34 | when (realContent /= content) $ 35 | liftIO . die $ unpack label <> " is wrong: expected " <> unpack content 36 | <> ", but got " <> unpack realContent 37 | 38 | checkIfMetricCanBeHiddenOrShown 39 | :: Text 40 | -> Text 41 | -> WD () 42 | checkIfMetricCanBeHiddenOrShown rowId checkId = do 43 | metricCheck <- findElem (ByCSS checkId) 44 | metricRow <- findElem (ById rowId) 45 | 46 | click metricCheck 47 | waitFor 100 Milliseconds 48 | 49 | isDisplayed metricRow >>= \visible -> when visible $ 50 | liftIO . die $ "Metric's row " <> unpack rowId <> " should be hidden now, but it's visible." 51 | 52 | click metricCheck 53 | waitFor 100 Milliseconds 54 | 55 | isDisplayed metricRow >>= \visible -> when (not visible) $ 56 | liftIO . die $ "Metric's row " <> unpack rowId <> " should be visible now, but it's hidden." 57 | 58 | {- 59 | checkIfNodeCanBeHiddenOrShown :: Text -> WD () 60 | checkIfNodeCanBeHiddenOrShown nodeName = do 61 | findElem (ByCSS "body > div.w3-bar.w3-large.TopBar > div:nth-child(3) > button") >>= click 62 | waitFor 2 Seconds 63 | 64 | nodeCheck <- findElem (ByCSS "body > div.w3-bar.w3-large.TopBar > div:nth-child(3) > div > div > input") 65 | waitFor 2 Seconds 66 | 67 | click nodeCheck 68 | waitFor 200 Milliseconds 69 | 70 | forM_ metricsForOneNode $ \metricCellId -> 71 | findElem (ById metricCellId) >>= isDisplayed >>= \visible -> when visible $ 72 | liftIO . die $ "Metric's cell " <> unpack metricCellId <> " should be hidden now, but it's visible." 73 | 74 | click nodeCheck 75 | waitFor 200 Milliseconds 76 | 77 | forM_ metricsForOneNode $ \metricCellId -> 78 | findElem (ById metricCellId) >>= isDisplayed >>= \visible -> when (not visible) $ 79 | liftIO . die $ "Metric's cell " <> unpack metricCellId <> " should be visible now, but it's hidden." 80 | where 81 | metricsForOneNode = map (\mName -> (pack . show $ mName) <> "-" <> nodeName) allMetricsNames 82 | -} 83 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/src/CLI.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | 3 | module CLI 4 | ( 5 | parseArguments 6 | ) 7 | where 8 | 9 | import System.Environment (getArgs) 10 | 11 | parseArguments :: IO (FilePath, FilePath, Int) 12 | parseArguments = 13 | getArgs >>= return . parseArgs >>= \case 14 | Left m -> putStrLn "Usage: analyzer " >> error m 15 | Right res -> return res 16 | where 17 | parseArgs [] = Left "No arguments" 18 | parseArgs (_a : []) = Left "Not enough arguments" 19 | parseArgs (_a : _b : []) = Left "Not enough arguments" 20 | parseArgs (conf : json : port : []) = Right (conf, json, (read port :: Int)) 21 | parseArgs _ = Left "Too many arguments" 22 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/src/Config.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | 4 | module Config 5 | ( 6 | getAcceptorInfoFrom 7 | , readRTViewConfig 8 | , wdConfig 9 | ) 10 | where 11 | 12 | import Control.Exception (IOException, catch, throwIO) 13 | import Control.Monad (when) 14 | import Data.Text (Text, pack) 15 | import Test.WebDriver 16 | 17 | import Cardano.BM.Configuration (Configuration, getAcceptAt, setup) 18 | import Cardano.BM.Data.Configuration (RemoteAddr (..), RemoteAddrNamed (..)) 19 | 20 | -- Please note that using of Firefox browser assumes that GeckoDriver is installed as well. 21 | wdConfig :: WDConfig 22 | wdConfig = defaultConfig { wdCapabilities = caps } 23 | where 24 | caps = defaultCaps { browser = firefox } 25 | 26 | readRTViewConfig :: FilePath -> IO Configuration 27 | readRTViewConfig fp = setup fp `catch` problems 28 | where 29 | problems :: IOException -> IO a 30 | problems e = do 31 | putStrLn $ "Exception while reading RTView configuration from: " <> fp 32 | throwIO e 33 | 34 | getAcceptorInfoFrom :: Configuration -> IO (Text, Text) 35 | getAcceptorInfoFrom rtViewConfig = 36 | getAcceptAt rtViewConfig >>= \case 37 | Just acceptors -> do 38 | when (null acceptors) $ error "Trace acceptor doesn't contain RemoteAddrNamed." 39 | -- We only need one acceptor to test RTView UI. 40 | let (fstAcceptor : _) = acceptors 41 | case remoteAddr fstAcceptor of 42 | RemotePipe unixSocket -> return (nodeName fstAcceptor, pack unixSocket) 43 | RemoteSocket _ _ -> error "Trace acceptor uses addr:port instead of UNIX socket." 44 | Nothing -> error "Trace acceptor not enabled in RTView config." 45 | -------------------------------------------------------------------------------- /test/rt-view-analyzer/src/Main.hs: -------------------------------------------------------------------------------- 1 | module Main 2 | ( 3 | main 4 | ) 5 | where 6 | 7 | import Test.WebDriver 8 | 9 | import Analyzers (Units (..), waitFor) 10 | import CLI (parseArguments) 11 | import Config (getAcceptorInfoFrom, readRTViewConfig, wdConfig) 12 | 13 | main :: IO () 14 | main = do 15 | (pathToRTViewConfig, _logObjectsJSON, rtViewWebPort) <- parseArguments 16 | rtViewConfig <- readRTViewConfig pathToRTViewConfig 17 | (_nodeName, _unixSocket) <- getAcceptorInfoFrom rtViewConfig 18 | 19 | runSession wdConfig . closeOnException $ do 20 | -- We always test cardano-rt-view launched locally, so we only need a web port. 21 | let rtViewPageURL = "http://127.0.0.1:" <> show rtViewWebPort 22 | openPage rtViewPageURL 23 | waitFor 2 Seconds 24 | 25 | -- TODO 26 | 27 | closeSession 28 | -------------------------------------------------------------------------------- /test/suite/Test/Cardano/RTView/HtmlCheck.hs: -------------------------------------------------------------------------------- 1 | module Test.Cardano.RTView.HtmlCheck 2 | ( checkPageHtml 3 | ) where 4 | 5 | checkPageHtml :: IO () 6 | checkPageHtml = putStrLn "checkPageHtml is passed!" 7 | 8 | -------------------------------------------------------------------------------- /test/suite/cardano-rt-view-test.hs: -------------------------------------------------------------------------------- 1 | import Test.Tasty 2 | import Test.Tasty.HUnit 3 | 4 | import Test.Cardano.RTView.HtmlCheck 5 | 6 | main :: IO () 7 | main = defaultMain tests 8 | 9 | tests :: TestTree 10 | tests = testGroup "Tests" 11 | [ testCase "2+2=4" $ 12 | 2+2 @?= 4 13 | -- , testCase "7 is even" $ 14 | -- assertBool "Oops, 7 is odd" (even 7) 15 | ] 16 | --------------------------------------------------------------------------------