diff --git a/src/weatherreport/.gitignore b/src/weatherreport/.gitignore new file mode 100644 index 00000000000..a74403bdd99 --- /dev/null +++ b/src/weatherreport/.gitignore @@ -0,0 +1,13 @@ +doc/ +deps/ +ebin/* +log/ +edoc/ +index.html +riaknostic +*.png +pkg/ +erl_crash.dump +.eunit/ +*~ +#*# \ No newline at end of file diff --git a/src/weatherreport/.manifest b/src/weatherreport/.manifest new file mode 100644 index 00000000000..0fb9b124969 --- /dev/null +++ b/src/weatherreport/.manifest @@ -0,0 +1,5 @@ +src +riaknostic +doc +LICENSE +README.md diff --git a/src/weatherreport/.travis.yml b/src/weatherreport/.travis.yml new file mode 100644 index 00000000000..ec6b937b095 --- /dev/null +++ b/src/weatherreport/.travis.yml @@ -0,0 +1,8 @@ +language: erlang +notifications: + disabled: true +env: + - R15B + - R14B04 + - R14B03 + - R14B02 diff --git a/src/weatherreport/DEVELOPMENT.md b/src/weatherreport/DEVELOPMENT.md new file mode 100644 index 00000000000..13337ab6720 --- /dev/null +++ b/src/weatherreport/DEVELOPMENT.md @@ -0,0 +1,86 @@ +# Riaknostic Development + +Riaknostic requires a sane GNU build system and a recent version of +Erlang. It has `lager` and `getopt` as dependencies, so those must be +compatible with your version of Erlang. Release versions are currently +built with Erlang version R14B03, while development versions are targeted at Erlang version R14B04. + +See the `rebar.config` file for more details. + +To build Riaknostic, simply run `make`: + +```bash +$ make +./rebar get-deps +==> riaknostic (get-deps) +Pulling lager from {git,"git://github.com/basho/lager",{branch,"master"}} +Cloning into lager... +Pulling getopt from {git,"git://github.com/jcomellas/getopt.git","2981dfe"} +Cloning into getopt... +==> lager (get-deps) +==> getopt (get-deps) +./rebar compile +==> lager (compile) +Compiled src/lager_util.erl +Compiled src/lager_transform.erl +Compiled src/lager_sup.erl +Compiled src/lager_mochiglobal.erl +Compiled src/lager_stdlib.erl +Compiled src/lager_handler_watcher_sup.erl +Compiled src/lager_handler_watcher.erl +Compiled src/lager_trunc_io.erl +Compiled src/lager_crash_log.erl +Compiled src/lager_file_backend.erl +Compiled src/lager_app.erl +Compiled src/lager.erl +Compiled src/lager_console_backend.erl +Compiled src/lager_format.erl +Compiled src/error_logger_lager_h.erl +==> getopt (compile) +Compiled src/getopt.erl +==> riaknostic (compile) +Compiled src/riaknostic_check.erl +Compiled src/riaknostic_util.erl +Compiled src/riaknostic_node.erl +Compiled src/riaknostic_check_ring_size.erl +Compiled src/riaknostic_check_ring_membership.erl +Compiled src/riaknostic_config.erl +Compiled src/riaknostic_check_memory_use.erl +Compiled src/riaknostic_check_nodes_connected.erl +Compiled src/riaknostic_check_dumps.erl +Compiled src/riaknostic.erl +Compiled src/riaknostic_check_disk.erl +./rebar escriptize +==> lager (escriptize) +==> getopt (escriptize) +==> riaknostic (escriptize) +``` + +Now you can invoke the script manually via the below command: + +```bash +$ ./riaknostic --etc ~/code/riak/rel/riak/etc --base ~/code/riak/rel/riak --user `whoami` [other options] +``` + +To generate the edoc reference, use `make docs` and then open the +`doc/index.html` file in your browser. Detailed discussion of the +internal APIs that you can use in developing new diagnostics is found +in the edocs. + +## Contributing + +Have an idea for a diagnostic? Want to improve the way Riaknostic works? Fork the [github repository](https://github.com/basho/riaknostic) and send us a pull-request with your changes! The code is documented with `edoc`, so give the [API Docs](http://riaknostic.basho.com/edoc/index.html) a read before you contribute. + +### Developing for Riaknostic Without a Riak Instance + +If you want to run the `riaknostic` script while developing, and you don't have it hooked up to your local Riak, you can invoke it directly like so: + +```bash +./riaknostic --etc ~/code/riak/rel/riak/etc --base ~/code/riak/rel/riak --user `whoami` [other options] +``` + +The extra options are usually assigned by the `riak-admin` script for you, but here's how to set them: + +* `--etc`: Where your Riak configuration directory is, in the example above it's in the generated directory of a source checkout of Riak. +* `--base`: The "base" directory of Riak, usually the root of the generated directory or `/usr/lib/riak` on Linux, for example. Scan the `riak-admin` script for how the `RUNNER_BASE_DIR` variable is assigned on your platform. +* `--user`: What user/UID the Riak node runs as. In a source checkout, it's the current user, on most systems, it's `riak`. \ No newline at end of file diff --git a/src/weatherreport/LICENSE b/src/weatherreport/LICENSE new file mode 100644 index 00000000000..e454a52586f --- /dev/null +++ b/src/weatherreport/LICENSE @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/src/weatherreport/Makefile b/src/weatherreport/Makefile new file mode 100644 index 00000000000..546f13c629e --- /dev/null +++ b/src/weatherreport/Makefile @@ -0,0 +1,85 @@ +.PHONY: rel stagedevrel deps test + +all: deps compile + +compile: + ./rebar compile + +deps: + ./rebar get-deps + +clean: + ./rebar clean + +distclean: clean + ./rebar delete-deps + +test: + ./rebar compile eunit + +escriptize: + ./rebar escriptize + +## +## Doc targets +## +docs: + ./rebar doc skip_deps=true + +pages: docs + cp priv/index.html doc/_index.html + cp priv/ForkMe_Blk.png doc/ + cp priv/*.jpg doc/ + git checkout gh-pages + mv doc/_index.html ./index.html + mv doc/ForkMe_Blk.png . + mv doc/*.jpg . + rm -rf edoc/* + cp -R doc/* edoc/ + git add . + git add -u + git commit + git push origin gh-pages + git checkout master + +## +## Release targets +## +VSN = `grep vsn src/riaknostic.app.src | cut -f 2 -d "\""` + +package: all docs + @mkdir -p pkg/riaknostic + @rm -rf pkg/riaknostic/* + @cat .manifest | xargs -n 1 -I % cp -R % pkg/riaknostic/. + @tar -czf pkg/riaknostic-$(VSN).tar.gz -C pkg riaknostic + +## +## Dialyzer targets +## +APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \ + xmerl webtool snmp public_key mnesia eunit syntax_tools compiler +COMBO_PLT = $(HOME)/.riak_combo_dialyzer_plt + +check_plt: compile + dialyzer --check_plt --plt $(COMBO_PLT) --apps $(APPS) + +build_plt: compile + dialyzer --build_plt --output_plt $(COMBO_PLT) --apps $(APPS) + +dialyzer: compile + @echo + @echo Use "'make check_plt'" to check PLT prior to using this target. + @echo Use "'make build_plt'" to build PLT prior to using this target. + @echo + @sleep 1 + dialyzer -Wno_return --plt $(COMBO_PLT) ebin deps/*/ebin | \ + fgrep -v -f ./dialyzer.ignore-warnings + +cleanplt: + @echo + @echo "Are you sure? It takes about 1/2 hour to re-build." + @echo Deleting $(COMBO_PLT) in 5 seconds. + @echo + sleep 5 + rm $(COMBO_PLT) + diff --git a/src/weatherreport/README.md b/src/weatherreport/README.md new file mode 100644 index 00000000000..627c8826349 --- /dev/null +++ b/src/weatherreport/README.md @@ -0,0 +1,146 @@ +# Riaknostic [![Build Status](https://secure.travis-ci.org/basho/riaknostic.png?branch=master)](http://travis-ci.org/basho/riaknostic) + +`riaknostic` is an escript and set of tools that diagnoses common problems which could affect a Riak node or cluster. When experiencing any problem with Riak, `riaknostic` should be the first thing run during troubleshooting. The tool is integrated with Riak via the `riak-admin` script. + +## Overview + +To diagnose problems with Riak, Riaknostic uses a series of checks which are derived from the experience of the Basho Client Services Team as well as numerous public discussions on the mailing list, IRC room, and other online media. + +Here is a basic example of using `riaknostic` followed immediately by the command's output: + +```bash +$ riak-admin diag +15:34:52.736 [warning] Riak crashed at Wed, 07 Dec 2011 21:47:50 GMT, leaving +crash dump in /srv/riak/log/erl_crash.dump. Please inspect or remove the file. +15:34:52.736 [notice] Data directory /srv/riak/data/bitcask is not mounted with 'noatime'. Please remount its disk with the 'noatime' flag to improve +performance. +``` + +As shown in the above output, Riaknostic tells us about two problems right away. First, an Erlang crash dump is present, indicating that Riak has experienced a crash. Second, a performance problem is mentioned (disk mounted without `noatime` argument)along with a helpful tip to resolve the issue. + +## Installation + +**Important**: If you are running Riak v1.3.0 or greater, you already have Riaknostic, so you can skip to the **Usage** section below. + +Riaknostic depends on features introduced by Erlang version R14B04, so verify that you've installed this version of Erlang before proceeding with installation. + +To install `riaknostic`, download the latest package version, and extract it within the directory shown for your operating system in the following table: + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlatformDirectory
Linux (Redhat, CentOS, Debian, Ubuntu)/usr/lib/riak/lib
Linux (Fedora)/usr/lib64/riak/lib
Solaris, OpenSolaris/opt/riak/lib
SmartOS (Joyent)/opt/local/lib/riak/lib
Mac OS/X or Self-built$RIAK/lib + (where $RIAK=rel/riak for source installs, + or the directory where you unpacked the package)
+ +An example Riaknostic installation for Linux looks like this: + +```bash +wget https://github.com/basho/riaknostic/downloads/riaknostic-1.0.2.tar.gz -P /tmp +cd /usr/lib/riak/lib +sudo tar xzvf /tmp/riaknostic-1.0.2.tar.gz +``` + +The package will expand to a `riaknostic/` directory which contains the `riaknostic` script, source code in the `src/` directory, and documentation. + +Now try it out! + +## Usage + +For most cases, you can just run the `riak-admin diag` command as given at the top of this README. However, sometimes you might want to know some extra detail or run only specific checks. For that, there are command-line options. Execute `riaknostic --help` to learn more about these options: + +```bash +riak-admin diag --help +Usage: riak-admin diag [-d ] [-l] [-h] [check_name ...] + + -d, --level Minimum message severity level (default: notice) + -l, --list Describe available diagnostic tasks + -h, --help Display help/usage + check_name A specific check to run +``` + +To get an idea of what checks will be run, use the `--list` option: + +```bash +riak-admin diag --list +Available diagnostic checks: + + disk Data directory permissions and atime + dumps Find crash dumps + memory_use Measure memory usage + nodes_connected Cluster node liveness + ring_membership Cluster membership validity + ring_size Ring size valid +``` + +If you want all the gory details about what Riaknostic is doing, you can run the checks at a more verbose logging level with the --level option: + +```bash +riak-admin diag --level debug +18:34:19.708 [debug] Lager installed handler lager_console_backend into lager_event +18:34:19.720 [debug] Lager installed handler error_logger_lager_h into error_logger +18:34:19.720 [info] Application lager started on node nonode@nohost +18:34:20.736 [debug] Not connected to the local Riak node, trying to connect. alive:false connect_failed:undefined +18:34:20.737 [debug] Starting distributed Erlang. +18:34:20.740 [debug] Supervisor net_sup started erl_epmd:start_link() at pid <0.42.0> +18:34:20.742 [debug] Supervisor net_sup started auth:start_link() at pid <0.43.0> +18:34:20.771 [debug] Supervisor net_sup started net_kernel:start_link(['riak_diag87813@127.0.0.1',longnames]) at pid <0.44.0> +18:34:20.771 [debug] Supervisor kernel_sup started erl_distribution:start_link(['riak_diag87813@127.0.0.1',longnames]) at pid <0.41.0> +18:34:20.781 [debug] Supervisor inet_gethost_native_sup started undefined at pid <0.49.0> +18:34:20.782 [debug] Supervisor kernel_safe_sup started inet_gethost_native:start_link() at pid <0.48.0> +18:34:20.834 [debug] Connected to local Riak node 'riak@127.0.0.1'. +18:34:20.939 [debug] Local RPC: os:getpid([]) [5000] +18:34:20.939 [debug] Running shell command: ps -o pmem,rss,command -p 83144 +18:34:20.946 [debug] Shell command output: +%MEM RSS COMMAND + 0.4 31004 /srv/riak/erts-5.8.4/bin/beam.smp -K true -A 64 -W w -- -root /srv/riak/rel/riak -progname riak -- -home /Users/sean -- -boot /srv/riak/releases/1.0.2/riak -embedded -config /srv/riak/etc/app.config -name riak@127.0.0.1 -setcookie riak -- console + +18:34:20.960 [warning] Riak crashed at Wed, 07 Dec 2011 21:47:50 GMT, leaving crash dump in /srv/riak/log/erl_crash.dump. Please inspect or remove the file. +18:34:20.961 [notice] Data directory /srv/riak/data/bitcask is not mounted with 'noatime'. Please remount its disk with the 'noatime' flag to improve performance. +18:34:20.961 [info] Riak process is using 0.4% of available RAM, totalling 31004 KB of real memory. +``` + +Most times you'll want to use the defaults, but any Syslog severity name will do (from most to least verbose): `debug, info, notice, warning, error, critical, alert, emergency`. + +Finally, if you want to run just a single diagnostic or a list of specific ones, you can pass their name(s): + +```bash +riak-admin diag dumps +18:41:24.083 [warning] Riak crashed at Wed, 07 Dec 2011 21:47:50 GMT, leaving crash dump in /srv/riak/log/erl_crash.dump. Please inspect or remove the file. +``` + +## Contributing + +0. Read DEVELOPMENT.md +1. Fork the project on [Github](https://github.com/basho/riaknostic). +2. Make your changes or additions on a "topic" branch, test and + document them. If you are making a new diagnostic, make sure you + give some module-level information about the checks it + performs. *Note*: diagnostics _should not_ make modifications to + Riak, only inspect things. +3. Push to your fork and send a pull-request. +4. A Basho Developer Advocate or Engineer will review your + pull-request and get back to you. \ No newline at end of file diff --git a/src/weatherreport/dialyzer.ignore-warnings b/src/weatherreport/dialyzer.ignore-warnings new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/weatherreport/doc/overview.edoc b/src/weatherreport/doc/overview.edoc new file mode 100644 index 00000000000..30dbd18af81 --- /dev/null +++ b/src/weatherreport/doc/overview.edoc @@ -0,0 +1,23 @@ +@author Basho Technologies, Inc. +@copyright 2011 Basho Technologies, Inc. +@version 1.0.0 +@title riaknostic: Automated diagnostic tools for Riak +@doc

riaknostic is an escript and set of tools that diagnoses common problems which could affect a Riak node or cluster. When experiencing any problem with Riak, riaknostic should be the first thing run during troubleshooting. The tool is integrated with Riak via the riak-admin script.

+ +
$ riak-admin diag
+ +

This documentation describes the riaknostic API and user interface commands. The application's core consists of 5 modules:

+ + + +

All other included modules are generally prefixed with riaknostic_check_ and are individual diagnostics that can be run.

+ +

riaknostic is licensed under the Apache v2 license.

+@end \ No newline at end of file diff --git a/src/weatherreport/priv/ForkMe_Blk.png b/src/weatherreport/priv/ForkMe_Blk.png new file mode 100644 index 00000000000..d50902d2dac Binary files /dev/null and b/src/weatherreport/priv/ForkMe_Blk.png differ diff --git a/src/weatherreport/priv/doctorbasho.jpg b/src/weatherreport/priv/doctorbasho.jpg new file mode 100644 index 00000000000..7e0895f4914 Binary files /dev/null and b/src/weatherreport/priv/doctorbasho.jpg differ diff --git a/src/weatherreport/priv/edoc.css b/src/weatherreport/priv/edoc.css new file mode 100644 index 00000000000..1d50defeb96 --- /dev/null +++ b/src/weatherreport/priv/edoc.css @@ -0,0 +1,130 @@ +/* Baseline rhythm */ +body { + font-size: 16px; + font-family: Helvetica, sans-serif; + margin: 8px; +} + +p { + font-size: 1em; /* 16px */ + line-height: 1.5em; /* 24px */ + margin: 0 0 1.5em 0; +} + +h1 { + font-size: 1.5em; /* 24px */ + line-height: 1em; /* 24px */ + margin-top: 1em; + margin-bottom: 0em; +} + +h2 { + font-size: 1.375em; /* 22px */ + line-height: 1.0909em; /* 24px */ + margin-top: 1.0909em; + margin-bottom: 0em; +} + +h3 { + font-size: 1.25em; /* 20px */ + line-height: 1.2em; /* 24px */ + margin-top: 1.2em; + margin-bottom: 0em; +} + +h4 { + font-size: 1.125em; /* 18px */ + line-height: 1.3333em; /* 24px */ + margin-top: 1.3333em; + margin-bottom: 0em; +} + +.class-for-16px { + font-size: 1em; /* 16px */ + line-height: 1.5em; /* 24px */ + margin-top: 1.5em; + margin-bottom: 0em; +} + +.class-for-14px { + font-size: 0.875em; /* 14px */ + line-height: 1.7143em; /* 24px */ + margin-top: 1.7143em; + margin-bottom: 0em; +} + +ul { + margin: 0 0 1.5em 0; +} + +/* Customizations */ +body { + color: #333; +} + +tt, code, pre { + font-family: "Andale Mono", "Inconsolata", "Monaco", "DejaVu Sans Mono", monospaced; +} + +tt, code { font-size: 0.875em } + +pre { + font-size: 0.875em; /* 14px */ + line-height: 1.7143em; /* 24px */ + margin: 0 1em 1.7143em; + padding: 0 1em; + background: #eee; +} + +.navbar img, hr { display: none } + +table { + border-collapse: collapse; +} + +h1 { + border-left: 0.5em solid #fa0; + padding-left: 0.5em; +} + +h2.indextitle { + font-size: 1.25em; /* 20px */ + line-height: 1.2em; /* 24px */ + margin: -8px -8px 0.6em; + background-color: #fa0; + color: white; + padding: 0.3em; +} + +ul.index { + list-style: none; + margin-left: 0em; + padding-left: 0; +} + +ul.index li { + display: inline; + padding-right: 0.75em +} + +div.spec p { + margin-bottom: 0; + padding-left: 1.25em; + background-color: #eee; +} + +h3.function { + border-left: 0.5em solid #fa0; + padding-left: 0.5em; + background: #fc9; +} +a, a:visited, a:hover, a:active { color: #C60 } +h2 a, h3 a { color: #333 } + +i { + font-size: 0.875em; /* 14px */ + line-height: 1.7143em; /* 24px */ + margin-top: 1.7143em; + margin-bottom: 0em; + font-style: normal; +} diff --git a/src/weatherreport/priv/index.html b/src/weatherreport/priv/index.html new file mode 100644 index 00000000000..13fda2e0d79 --- /dev/null +++ b/src/weatherreport/priv/index.html @@ -0,0 +1,183 @@ + + + + + Riaknostic + + + + +
+ +
+
+
+

Overview

+

Sometimes, things go wrong in Riak. How can you know what's + wrong? Riaknostic is here to help.

+
$ riak-admin diag
+15:34:52.736 [warning] Riak crashed at Wed, 07 Dec 2011 21:47:50 GMT, leaving crash dump in /srv/riak/log/erl_crash.dump. Please inspect or remove the file.
+15:34:52.736 [notice] Data directory /srv/riak/data/bitcask is not mounted with 'noatime'. Please remount its disk with the 'noatime' flag to improve performance.
+

Riaknostic, which is invoked via the above command, is a + small suite of diagnostic checks that can be run against + your Riak node to discover common problems and recommend how + to resolve them. These checks are derived from the experience + of the Basho Client Services Team as well as numerous + public discussions on the mailing list, IRC room, and other + online media.

+
+
+
+
+

Installation

+

After downloading the package, expand it in the directory below according to + your platform:

+ + + + + + + + + + + + + + + + + + + + + + +
PlatformDirectory
Linux (Redhat, CentOS, Debian, Ubuntu)/usr/lib/riak/lib
Linux (Fedora)/usr/lib64/riak/lib
Solaris, OpenSolaris/opt/riak/lib
Mac OS/X or Self-built$RIAK/lib + (where $RIAK=rel/riak for source installs, + or the directory where you unpacked the package)
+

For example, on Linux, I might do this:

+
$ wget https://github.com/basho/riaknostic/downloads/riaknostic-1.0.1.tar.gz -P /tmp
+$ cd /usr/lib/riak/lib
+$ sudo tar xzvf /tmp/riaknostic-1.0.1.tar.gz
+

The package will expand to a riaknostic/ + directory which contains the riaknostic script, + source code in the src/ directory and + documentation. Now try it out!

+
+
+

Usage

+

For most cases, you can just run the riak-admin + diag command as given at the top of the + page. However, sometimes you might want to know some extra + detail or run only specific checks. For that, there are + command-line options. Add --help to get the options:

+
$ riak-admin diag --help
+Usage: riak-admin diag [-d <level>] [-l] [-h] [check_name ...]
+
+  -d, --level		Minimum message severity level (default: notice)
+  -l, --list		Describe available diagnostic tasks
+  -h, --help		Display help/usage
+  check_name		A specific check to run
+

To get an idea of what checks will be run, use + the --list option:

+
$ riak-admin diag --list
+Available diagnostic checks:
+
+  disk                 Data directory permissions and atime
+  dumps                Find crash dumps
+  memory_use           Measure memory usage
+  nodes_connected      Cluster node liveness
+  ring_membership      Cluster membership validity
+  ring_size            Ring size valid
+

If you want all the gory details about what Riaknostic is + doing, you can run the checks at a more verbose logging + level with the --level option:

+
$ riak-admin diag --level debug
+18:34:19.708 [debug] Lager installed handler lager_console_backend into lager_event
+18:34:19.720 [debug] Lager installed handler error_logger_lager_h into error_logger
+18:34:19.720 [info] Application lager started on node nonode@nohost
+18:34:20.736 [debug] Not connected to the local Riak node, trying to connect. alive:false connect_failed:undefined
+18:34:20.737 [debug] Starting distributed Erlang.
+18:34:20.740 [debug] Supervisor net_sup started erl_epmd:start_link() at pid <0.42.0>
+18:34:20.742 [debug] Supervisor net_sup started auth:start_link() at pid <0.43.0>
+18:34:20.771 [debug] Supervisor net_sup started net_kernel:start_link(['riak_diag87813@127.0.0.1',longnames]) at pid <0.44.0>
+18:34:20.771 [debug] Supervisor kernel_sup started erl_distribution:start_link(['riak_diag87813@127.0.0.1',longnames]) at pid <0.41.0>
+18:34:20.781 [debug] Supervisor inet_gethost_native_sup started undefined at pid <0.49.0>
+18:34:20.782 [debug] Supervisor kernel_safe_sup started inet_gethost_native:start_link() at pid <0.48.0>
+18:34:20.834 [debug] Connected to local Riak node 'riak@127.0.0.1'.
+18:34:20.939 [debug] Local RPC: os:getpid([]) [5000]
+18:34:20.939 [debug] Running shell command: ps -o pmem,rss,command -p 83144
+18:34:20.946 [debug] Shell command output: 
+%MEM    RSS COMMAND
+ 0.4  31004 /srv/riak/erts-5.8.4/bin/beam.smp -K true -A 64 -W w -- -root /srv/riak/rel/riak -progname riak -- -home /Users/sean -- -boot /srv/riak/releases/1.0.2/riak -embedded -config /srv/riak/etc/app.config -name riak@127.0.0.1 -setcookie riak -- console
+
+18:34:20.960 [warning] Riak crashed at Wed, 07 Dec 2011 21:47:50 GMT, leaving crash dump in /srv/riak/log/erl_crash.dump. Please inspect or remove the file.
+18:34:20.961 [notice] Data directory /srv/riak/data/bitcask is not mounted with 'noatime'. Please remount its disk with the 'noatime' flag to improve performance.
+18:34:20.961 [info] Riak process is using 0.4% of available RAM, totalling 31004 KB of real memory.
+

Most times you'll want to use the defaults, but any + Syslog severity name will do (from most to least + verbose): debug, info, notice, warning, error, + critical, alert, emergency.

+

Finally, if you want to run just a single diagnostic or a list + of specific ones, you can pass their name(s):

+
$ riak-admin diag dumps
+18:41:24.083 [warning] Riak crashed at Wed, 07 Dec 2011 21:47:50 GMT, leaving crash dump in /srv/riak/log/erl_crash.dump. Please inspect or remove the file.
+
+
+
+

Contributing

+

Have an idea for a diagnostic? Want to improve the way + Riaknostic works? Fork + the github + repository and send us a pull-request with your + changes! The code is documented with edoc, + so give the API Docs a + read before you contribute.

+

If you want to run the riaknostic script + while developing and you don't have it hooked up to your + local Riak, you can invoke it directly like so:

+
$ ./riaknostic --etc ~/code/riak/rel/riak/etc --base ~/code/riak/rel/riak --user `whoami` [other options]
+

Those extra options are usually assigned by + the riak-admin script for you, but here's + how to set them:

+ + + + + + + + + + + + + +
--etcWhere your Riak configuration directory is, in the + example above it's in the generated directory of a + source checkout of Riak.
--baseThe "base" directory of Riak, usually the root of + the generated directory + or /usr/lib/riak on Linux, for + example. Scan the riak-admin script for + how the RUNNER_BASE_DIR variable is + assigned on your platform.
--userWhat user/UID the Riak node runs as. In a source + checkout, it's the current user, on most systems, + it's riak.
+
+
+
+ + diff --git a/src/weatherreport/rebar b/src/weatherreport/rebar new file mode 100755 index 00000000000..c66b3e62672 Binary files /dev/null and b/src/weatherreport/rebar differ diff --git a/src/weatherreport/rebar.config b/src/weatherreport/rebar.config new file mode 100644 index 00000000000..3f53c665ccb --- /dev/null +++ b/src/weatherreport/rebar.config @@ -0,0 +1,36 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +{escript_shebang, "#!/usr/bin/env escript\n"}. +{escript_comment, "%% -nocookie\n"}. + +{erl_opts, [debug_info, {parse_transform, lager_transform}]}. + +{escript_incl_apps, [lager, getopt, goldrush]}. + +{deps, [ + {lager, "2.0.3", {git, "git://github.com/basho/lager.git", {tag, "2.0.3"}}}, + {getopt, ".*", {git, "git://github.com/jcomellas/getopt.git", {tag, "v0.4.3"}}}, + {meck, "0.8.1", {git, "git://github.com/basho/meck.git", {tag, "0.8.1"}}} + ]}. + +{edoc_opts, [{stylesheet_file, "priv/edoc.css"}]}. diff --git a/src/weatherreport/riaknostic.sh b/src/weatherreport/riaknostic.sh deleted file mode 100644 index db4420a6eed..00000000000 --- a/src/weatherreport/riaknostic.sh +++ /dev/null @@ -1,47 +0,0 @@ -#/bin/bash - -echo "Running Riaknostic..." - -basedir=$1 - -release_dirs="$basedir/libexec/releases $basedir/releases $basedir" -for dir in $release_dirs -do - if [ -f "$dir/start_erl.data" ] - then - riak=$dir - echo "Riak node found: $dir" - break - fi -done - -if [ -z "$riak" ] -then - echo "Couldn't find a Riak installation" - exit 1 -fi - -admin_cmd=`which riak-admin riaksearch-admin | tail -n 1 2>/dev/null` - -if [ -z "$admin_cmd" ] -then - echo "Couldn't find Riak admin tool in \$PATH" - exit 1 -fi - -riak_version=`awk '{print $2}' $dir/start_erl.data` -riak_status=`$admin_cmd status` -riak_search=`echo "$riak_status" | grep riak_search_core_version | awk '{print $3}' | sed 's/<<"\(.*\)">>/\1/'` - -arch=`uname -p` -os=`uname -s` -os_version=`uname -r` - -if [ -z "$riak_search" ] -then - echo "Riak version: $riak_version" -else - echo "Riak Search version: $riak_version" -fi - -echo "Riak running on: $os $os_version (arch: $arch)" diff --git a/src/weatherreport/src/riaknostic.app.src b/src/weatherreport/src/riaknostic.app.src new file mode 100644 index 00000000000..096282bffe2 --- /dev/null +++ b/src/weatherreport/src/riaknostic.app.src @@ -0,0 +1,34 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- +{application, riaknostic, + [ + {description, "Diagnostic tools for Riak"}, + {vsn, "1.2.1"}, + {registered, []}, + {applications, [ + kernel, + stdlib, + inets, + lager + ]}, + {mod, { riaknostic_app, []}} + ]}. diff --git a/src/weatherreport/src/riaknostic.erl b/src/weatherreport/src/riaknostic.erl new file mode 100644 index 00000000000..ec05d4dc92b --- /dev/null +++ b/src/weatherreport/src/riaknostic.erl @@ -0,0 +1,172 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc

The riaknostic module is the entry point for +%% the escript. It is responsible for parsing command-line arguments +%% and switches, printing the available checks, listing the help text, +%% or running all or the specified checks, depending on the command +%% line.

+%% +%%

The getopt application and module is used +%% for command-line parsing. The defined switches and arguments are:

+%%
$ ./riaknostic --etc etc --base base --user user [-d level] [-l] [-h] [check_name...]
+%% +%% +%% +%% +%% +%% +%% +%% +%% +%%
--etc etcthe location of the Riak +%% configuration directory (set automatically by +%% riak-admin)
--base basethe base directory of +%% Riak, aka RUNNER_BASE_DIR (set automatically by +%% riak-admin)
--user userthe user that Riak runs as +%% (set automatically by riak-admin)
-d, --level level  the severity of +%% messages you want to see, defaulting to 'notice'. Equivalent to +%% syslog/lager severity levels.
-l, --listlists available checks, +%% that is, modules that implement riaknostic_check. A +%% "short name" will be given for ease-of-use.
-h, --help - print command usage +%% ("help")
check_namewhen given, a specific +%% check or list of checks to run
+%% @end +-module(riaknostic). +-export([main/1]). + +-define(OPTS, [ + {etc, undefined, "etc", string, undefined }, + {base, undefined, "base", string, undefined }, + {user, undefined, "user", string, undefined }, + {level, $d, "level", {atom, notice}, "Minimum message severity level (default: notice)"}, + {list, $l, "list", undefined, "Describe available diagnostic tasks" }, + % should we calc and interpolate the actual cwd for the below? + {export,undefined, "export",undefined, "Package system info in '$CWD/export.zip'" } + ]). + +%% @doc The main entry point for the riaknostic escript. +-spec main(CommandLineArguments::[string()]) -> any(). +main(Args) -> + application:load(riaknostic), + + case getopt:parse(?OPTS, Args) of + {ok, {Opts, NonOptArgs}} -> + case process_opts(Opts) of + list -> list_checks(); + run -> run(NonOptArgs); + export -> riaknostic_export:export() + end; + {error, Error} -> + io:format("Invalid option sequence given: ~w~n", [Error]) + end. + +list_checks() -> + Descriptions = [ {riaknostic_util:short_name(Mod), Mod:description()} || + Mod <- riaknostic_check:modules() ], + io:format("Available diagnostic checks:~n~n"), + lists:foreach(fun({Mod, Desc}) -> + io:format(" ~.20s ~s~n", [Mod, Desc]) + end, lists:sort(Descriptions)). + +run(InputChecks) -> + case riaknostic_config:prepare() of + {error, Reason} -> + io:format("Fatal error: ~s~n", [Reason]); + _ -> + ok + end, + Checks = case InputChecks of + [] -> + riaknostic_check:modules(); + _ -> + ShortNames = [{riaknostic_util:short_name(Mod), Mod} || Mod <- riaknostic_check:modules() ], + element(1, lists:foldr(fun validate_checks/2, {[], ShortNames}, InputChecks)) + end, + Messages = lists:foldl(fun(Mod, Acc) -> + Acc ++ riaknostic_check:check(Mod) + end, [], Checks), + case Messages of + [] -> + io:format("No diagnostic messages to report.~n"); + _ -> + %% Print the most critical messages first + LogLevelNum = lists:foldl( + fun({mask, Mask}, Acc) -> + Mask bor Acc; + (Level, Acc) when is_integer(Level) -> + {mask, Mask} = lager_util:config_to_mask(lager_util:num_to_level(Level)), + Mask bor Acc; + (_, Acc) -> + Acc + end, 0, lager:get_loglevels()), + FilteredMessages = lists:filter(fun({Level,_,_}) -> + lager_util:level_to_num(Level) =< LogLevelNum + end, Messages), + SortedMessages = lists:sort(fun({ALevel, _, _}, {BLevel, _, _}) -> + lager_util:level_to_num(ALevel) =< lager_util:level_to_num(BLevel) + end, FilteredMessages), + case SortedMessages of + [] -> + io:format("No diagnostic messages to report.~n"); + _ -> + lists:foreach(fun riaknostic_check:print/1, SortedMessages) + end + end. + +validate_checks(Check, {Mods, SNames}) -> + case lists:keyfind(Check, 1, SNames) of + {Check, Mod} -> + {[Mod|Mods], lists:delete({Check, Mod}, SNames)}; + _ -> + io:format("Unknown check '~s' specified, skipping.~n", [Check]), + {Mods, SNames} + end. + +process_opts(Opts) -> + process_opts(Opts, run). + +process_opts([], Result) -> + Result; +process_opts([H|T], Result) -> + process_opts(T, process_option(H, Result)). + +process_option({etc,Path}, Result) -> + application:set_env(riaknostic, etc, filename:absname(Path)), + Result; +process_option({base, Path}, Result) -> + application:set_env(riaknostic, base, filename:absname(Path)), + Result; +process_option({user, User}, Result) -> + application:set_env(riaknostic, user, User), + Result; +process_option({level, Level}, Result) -> + application:set_env(riaknostic, log_level, Level), + Result; +process_option(list, usage) -> %% Help should have precedence over listing checks + usage; +process_option(list, _) -> + list; +process_option(usage, _) -> + usage; +process_option(export, _) -> + export. diff --git a/src/weatherreport/src/riaknostic_check.erl b/src/weatherreport/src/riaknostic_check.erl new file mode 100644 index 00000000000..396ad568bf8 --- /dev/null +++ b/src/weatherreport/src/riaknostic_check.erl @@ -0,0 +1,102 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc

Enforces a common API among all diagnostic modules and +%% provides some automation around their execution.

+%%

Behaviour Specification

+%% +%%

description/0

+%%
-spec description() -> iodata().
+%%

A short description of what the diagnostic does, which will be +%% printed when the script is given the -l flag.

+%% +%%

valid/0

+%%
-spec valid() -> boolean().
+%%

Whether the diagnostic is valid to run. For example, some checks +%% require connectivity to the Riak node and hence call {@link +%% riaknostic_node:can_connect/0. riaknostic_node:can_connect()}.

+%% +%%

check/0

+%%
-spec check() -> [{lager:log_level(), term()}].
+%%

Runs the diagnostic, returning a list of pairs, where the first +%% is a severity level and the second is any term that is understood +%% by the format/1 callback.

+%% +%%

format/1

+%%
-spec format(term()) -> iodata() | {io:format(), [term()]}.
+%%

Formats terms that were returned from check/0 for +%% output to the console. Valid return values are an iolist (string, +%% binary, etc) or a pair of a format string and a list of terms, as +%% you would pass to {@link io:format/2. io:format/2}.

+%% @end + +-module(riaknostic_check). +-export([behaviour_info/1]). +-export([check/1, + modules/0, + print/1]). + +%% @doc The behaviour definition for diagnostic modules. +-spec behaviour_info(atom()) -> 'undefined' | [{atom(), arity()}]. +behaviour_info(callbacks) -> + [{description, 0}, + {valid, 0}, + {check, 0}, + {format, 1}]; +behaviour_info(_) -> + undefined. + +%% @doc Runs the diagnostic in the given module, if it is valid. Returns a +%% list of messages that will be printed later using print/1. +-spec check(Module::module()) -> [{lager:log_level(), module(), term()}]. +check(Module) -> + case Module:valid() of + true -> + [ {Level, Module, Message} || {Level, Message} <- Module:check() ]; + _ -> + [] + end. + +%% @doc Collects a list of diagnostic modules included in the +%% riaknostic application. +-spec modules() -> [module()]. +modules() -> + {ok, Mods} = application:get_key(riaknostic, modules), + [ M || M <- Mods, + Attr <- M:module_info(attributes), + {behaviour, [?MODULE]} =:= Attr orelse {behavior, [?MODULE]} =:= Attr ]. + + +%% @doc Formats and prints the given message via lager:log/3,4. The diagnostic +%% module's format/1 function will be called to provide a +%% human-readable message. It should return an iolist() or a 2-tuple +%% consisting of a format string and a list of terms. +-spec print({Level::lager:log_level(), Module::module(), Data::term()}) -> ok. +print({Level, Mod, Data}) -> + case Mod:format(Data) of + {Format, Terms} -> + riaknostic_util:log(Level, Format, Terms); + String -> + riaknostic_util:log(Level, String) + end. + + diff --git a/src/weatherreport/src/riaknostic_check_disk.erl b/src/weatherreport/src/riaknostic_check_disk.erl new file mode 100644 index 00000000000..3b3e14ffccf --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_disk.erl @@ -0,0 +1,165 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that checks permissions on data directories and +%% whether noatime is set. It will only check data directories of +%% known storage backends. +-module(riaknostic_check_disk). +-behaviour(riaknostic_check). + +%% The file that we will attempt to create and read under each data directory. +-define(TEST_FILE, "riaknostic.tmp"). + +%% A dependent chain of permissions checking functions. +-define(CHECKPERMFUNS, [fun check_is_dir/1, + fun check_is_writeable/1, + fun check_is_readable/1, + fun check_is_file_readable/1, + fun check_atime/1]). + +-include_lib("kernel/include/file.hrl"). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Data directory permissions and atime". + +-spec valid() -> true. +valid() -> + true. + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + DataDirs = riaknostic_config:data_directories(), + %% Add additional disk checks in the function below + lists:flatmap(fun(Dir) -> + check_directory_permissions(Dir) + end, + DataDirs). + +-spec format(term()) -> {io:format(), [term()]}. +format({disk_full, DataDir}) -> + {"Disk containing data directory ~s is full! " + "Please check that it is set to the correct location and that there are not " + "other files using up space intended for Riak.", [DataDir]}; +format({no_data_dir, DataDir}) -> + {"Data directory ~s does not exist. Please create it.", [DataDir]}; +format({no_write, DataDir}) -> + User = riaknostic_config:user(), + {"No write access to data directory ~s. Please make it writeable by the '~s' user.", [DataDir, User]}; +format({no_read, DataDir}) -> + User = riaknostic_config:user(), + {"No read access to data directory ~s. Please make it readable by the '~s' user.", [DataDir, User]}; +format({write_check, File}) -> + {"Write-test file ~s is a directory! Please remove it so this test can continue.", [File]}; +format({atime, Dir}) -> + {"Data directory ~s is not mounted with 'noatime'. " + "Please remount its disk with the 'noatime' flag to improve performance.", [Dir]}. + +%%% Private functions + +check_directory_permissions(Directory) -> + check_directory(Directory, ?CHECKPERMFUNS). + +%% Run a list of check functions against the given directory, +%% returning the first non-ok result. +check_directory(_, []) -> + []; +check_directory(Directory, [Check|Checks]) -> + case Check(Directory) of + ok -> + check_directory(Directory, Checks); + Message -> + [ Message ] + end. + +%% Check if the path is actually a directory +check_is_dir(Directory) -> + case filelib:is_dir(Directory) of + true -> + ok; + _ -> + {error, {no_data_dir, Directory}} + end. + +%% Check if the directory is writeable +check_is_writeable(Directory) -> + File = filename:join([Directory, ?TEST_FILE]), + case file:write_file(File, <<"ok">>) of + ok -> + ok; + {error, Error} when Error == enoent orelse Error == eacces -> + {error, {no_write, Directory}}; + {error, enospc} -> + {critical, {disk_full, Directory}}; + {error, eisdir} -> + {error, {write_check, File}} + end. + +%% Check if the directory is readable +check_is_readable(Directory) -> + case file:read_file_info(Directory) of + {ok, #file_info{access=Access}} when Access == read orelse + Access == read_write -> + ok; + {error, eacces} -> + {error, {no_read, Directory}}; + {error, Error} when Error == enoent orelse + Error == enotdir -> + {error, {no_data_dir, Directory}}; + _ -> + {error, {no_read, Directory}} + end. + +%% Check if the file we created is readable +check_is_file_readable(Directory) -> + File = filename:join([Directory, ?TEST_FILE]), + case file:read_file(File) of + {error, Error} when Error == eacces orelse + Error == enotdir -> + {error, {no_read, Directory}}; + {error, enoent} -> + {error, {write_check, File}}; + _ -> ok + end. + +%% Check if the directory is mounted with 'noatime' +check_atime(Directory) -> + File = filename:join([Directory, ?TEST_FILE]), + {ok, FileInfo1} = file:read_file_info(File), + timer:sleep(1001), + {ok, S} = file:open(File, [read]), + io:get_line(S, ''), + file:close(S), + {ok, FileInfo2} = file:read_file_info(File), + file:delete(File), + case (FileInfo1#file_info.atime =/= FileInfo2#file_info.atime) of + true -> + {notice, {atime, Directory}}; + _ -> + ok + end. + diff --git a/src/weatherreport/src/riaknostic_check_dumps.erl b/src/weatherreport/src/riaknostic_check_dumps.erl new file mode 100644 index 00000000000..a9a2bb775b7 --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_dumps.erl @@ -0,0 +1,79 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that detects the existence of Erlang-generated +%% crash dumps. It will also check whether the location that the crash +%% dump is written to has correct permissions. +-module(riaknostic_check_dumps). +-behaviour(riaknostic_check). + +-include_lib("kernel/include/file.hrl"). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Find crash dumps". + +-spec valid() -> true. +valid() -> + true. + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + CrashDumpConfig = riaknostic_config:get_vm_env("ERL_CRASH_DUMP"), + {DumpDir, DumpFile} = case CrashDumpConfig of + undefined -> + Cwd = riaknostic_config:base_dir(), + {Cwd, filename:absname([Cwd, "erl_crash.dump"])}; + File -> + AbsFile = filename:absname(File, riaknostic_config:base_dir()), + {filename:dirname(AbsFile), AbsFile} + end, + Messages = case file:read_file_info(DumpDir) of + {error, enoent} -> + [{error, {enoent, DumpDir}}]; + {error, _} -> + [{error, {eacces, DumpDir}}]; + {ok, #file_info{access=Access}} when Access =/= read_write -> + [{error, {eacces, DumpDir}}]; + _ -> + [] + end, + case filelib:is_file(DumpFile) of + true -> + [{warning, {crash_dump, DumpFile}}|Messages]; + _ -> + Messages + end. + +-spec format(term()) -> {io:format(), [term()]}. +format({eacces, Dir}) -> + {"Crash dump directory ~s is not writeable by Riak. Please set -env ERL_CRASH_DUMP /erl_crash.dump in vm.args to a writeable path.", [Dir]}; +format({enoent, Dir}) -> + {"Crash dump directory ~s does not exist. Please set -env ERL_CRASH_DUMP /erl_crash.dump in vm.args to a writeable path.", [Dir]}; +format({crash_dump, File}) -> + {ok, #file_info{mtime=MTime}} = file:read_file_info(File), + {"Riak crashed at ~s, leaving crash dump in ~s. Please inspect or remove the file.", [httpd_util:rfc1123_date(MTime), File]}. diff --git a/src/weatherreport/src/riaknostic_check_memory_use.erl b/src/weatherreport/src/riaknostic_check_memory_use.erl new file mode 100644 index 00000000000..9ea57a19d8d --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_memory_use.erl @@ -0,0 +1,61 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that checks Riak's current memory usage. If memory +%% usage is high, a warning message will be sent, otherwise only +%% informational messages. +-module(riaknostic_check_memory_use). +-behaviour(riaknostic_check). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Measure memory usage". + +-spec valid() -> boolean(). +valid() -> + riaknostic_node:can_connect(). + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + Pid = riaknostic_node:pid(), + Output = riaknostic_util:run_command("ps -o pmem,rss -p " ++ Pid), + [_,_,Percent, RealSize| _] = string:tokens(Output, "/n \n"), + Messages = [ + {info, {process_usage, Percent, RealSize}} + ], + case riaknostic_util:binary_to_float(list_to_binary(Percent)) >= 90 of + false -> + Messages; + true -> + [{critical, {high_memory, Percent}} | Messages] + end. + +-spec format(term()) -> {io:format(), [term()]}. +format({high_memory, Percent}) -> + {"Riak memory usage is HIGH: ~s% of available RAM", [Percent]}; +format({process_usage, Percent, Real}) -> + {"Riak process is using ~s% of available RAM, totalling ~s KB of real memory.", [Percent, Real]}. diff --git a/src/weatherreport/src/riaknostic_check_nodes_connected.erl b/src/weatherreport/src/riaknostic_check_nodes_connected.erl new file mode 100644 index 00000000000..3301d881430 --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_nodes_connected.erl @@ -0,0 +1,53 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic check that detects cluster members that are down. +-module(riaknostic_check_nodes_connected). +-behaviour(riaknostic_check). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Cluster node liveness". + +-spec valid() -> boolean(). +valid() -> + riaknostic_node:can_connect(). + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + Stats = riaknostic_node:stats(), + {connected_nodes, ConnectedNodes} = lists:keyfind(connected_nodes, 1, Stats), + {ring_members, RingMembers} = lists:keyfind(ring_members, 1, Stats), + {nodename, NodeName} = lists:keyfind(nodename, 1, Stats), + + [ {warning, {node_disconnected, N}} || N <- RingMembers, + N =/= NodeName, + lists:member(N, ConnectedNodes) == false]. + +-spec format(term()) -> {io:format(), [term()]}. +format({node_disconnected, Node}) -> + {"Cluster member ~s is not connected to this node. Please check whether it is down.", [Node]}. diff --git a/src/weatherreport/src/riaknostic_check_ring_membership.erl b/src/weatherreport/src/riaknostic_check_ring_membership.erl new file mode 100644 index 00000000000..8993fb1f991 --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_ring_membership.erl @@ -0,0 +1,66 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that checks whether the local node is a member of +%% the ring. This might arise when the node name in vm.args has +%% changed but the node has not been renamed in the ring. +-module(riaknostic_check_ring_membership). +-behaviour(riaknostic_check). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-include_lib("eunit/include/eunit.hrl"). + +-spec description() -> string(). +description() -> + "Cluster membership validity". + +-spec valid() -> boolean(). +valid() -> + riaknostic_node:can_connect(). + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + Stats = riaknostic_node:stats(), + {ring_members, RingMembers} = lists:keyfind(ring_members, 1, Stats), + {nodename, NodeName} = lists:keyfind(nodename, 1, Stats), + case lists:member(NodeName, RingMembers) of + true -> + []; + false -> + [{warning, {not_ring_member, NodeName}}] + end. + +check_test() -> + meck:new(riaknostic_node, [passthrough]), + meck:expect(riaknostic_node, stats, fun() -> [{ring_members, ["riak@127.0.0.1"]}, {nodename, ["notmember@127.0.0.1"]}] end), + ?assert(meck:validate(riaknostic_node)), + ?assertEqual([{warning, {not_ring_member, ["notmember@127.0.0.1"]}}], check()), + ?assertNotEqual([{warning, {not_ring_member, ["notequal@127.0.0.1"]}}], check()), + meck:unload(riaknostic_node). + +-spec format(term()) -> {io:format(), [term()]}. +format({not_ring_member, Nodename}) -> + {"Local node ~w is not a member of the ring. Please check that the -name setting in vm.args is correct.", [Nodename]}. diff --git a/src/weatherreport/src/riaknostic_check_ring_preflists.erl b/src/weatherreport/src/riaknostic_check_ring_preflists.erl new file mode 100644 index 00000000000..df09e403007 --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_ring_preflists.erl @@ -0,0 +1,51 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that checks the local ring for any preflists that do +%% not satisfy n_val +-module(riaknostic_check_ring_preflists). +-behaviour(riaknostic_check). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Check ring satisfies n_val". + +-spec valid() -> boolean(). +valid() -> + riaknostic_node:can_connect(). + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + case riaknostic_node:local_command(riak_core_ring_util, check_ring) of + [] -> []; + PrefLists -> [ {warning, {n_val_not_satisfied, PrefLists}} ] + end. + +-spec format(term()) -> {io:format(), [term()]}. +format({n_val_not_satisfied, PrefLists}) -> + {"The following preflists do not satisfy the n_val: ~p", [PrefLists]}. + diff --git a/src/weatherreport/src/riaknostic_check_ring_size.erl b/src/weatherreport/src/riaknostic_check_ring_size.erl new file mode 100644 index 00000000000..e796b7b23ef --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_ring_size.erl @@ -0,0 +1,76 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that compares the configured +%% ring_creation_size to the actual size of the ring. +-module(riaknostic_check_ring_size). +-behaviour(riaknostic_check). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Ring size valid". + +-spec valid() -> boolean(). +valid() -> + riaknostic_node:can_connect(). + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + Stats = riaknostic_node:stats(), + {ring_creation_size, RingSize} = lists:keyfind(ring_creation_size, 1, Stats), + {ring_num_partitions, NumPartitions} = lists:keyfind(ring_num_partitions, 1, Stats), +% {ring_members, RingMembers} = lists:keyfind(ring_members, 1, Stats), +% NumRingMembers = length(RingMembers), +% VnodesPerNode = erlang:round(RingSize / NumRingMembers), +% MinAcceptableVnodesPerNode = erlang:round(RingSize * 0.03), +% MaxRecommendedVnodesPerNode = erlang:round(RingSize * 0.7), + + lists:append([ + [ {notice, {ring_size_unequal, RingSize, NumPartitions}} || RingSize /= NumPartitions ], + [ {critical, {ring_size_not_exp2, RingSize}} || (RingSize band -(bnot RingSize)) /= RingSize] +% [ {notice, {ring_size_too_small, RingSize, NumRingMembers}} || VnodesPerNode =< MinAcceptableVnodesPerNode ], +% [ {notice, {too_few_nodes_for_ring, RingSize, NumRingMembers}} || VnodesPerNode >= MaxRecommendedVnodesPerNode ] + ]). + +-spec format(term()) -> {io:format(), [term()]}. +format({ring_size_unequal, S, P}) -> + {"The configured ring_creation_size (~B) is not equal to the number of partitions in the ring (~B). " + "Please verify that the ring_creation_size in app.config is correct.", [S, P]}; + +format({ring_size_not_exp2, S}) -> + {"The configured ring_creation_size (~B) should always be a power of 2. " + "Please reconfigure the ring_creation_size in app.config.", [S]}. + +%format({ring_size_too_small, S, N}) -> +% {"With a ring_creation_size (~B) and ~B nodes participating in the cluster, each node is responsible for less than 3% of the data. " +% " You have too many nodes for this size ring. " +% "Please consider migrating data to a cluster with 2 or 4x your current ring size.", [S, N]}; + +%format({too_few_nodes_for_ring, S, N}) -> +% {"With a ring_creation_size (~B) and ~B nodes participating in the cluster, each node is responsible for more than 70% of the data. " +% " You have too few nodes for this size ring. " +% "Please consider joining more nodes to your cluster.", [S, N]}. diff --git a/src/weatherreport/src/riaknostic_check_search.erl b/src/weatherreport/src/riaknostic_check_search.erl new file mode 100644 index 00000000000..509a642c50a --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_search.erl @@ -0,0 +1,57 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that checks if riak_search +%% is enabled on every node +-module(riaknostic_check_search). +-behaviour(riaknostic_check). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Check whether search is enabled on all nodes". + +-spec valid() -> boolean(). +valid() -> + riaknostic_node:can_connect_all(). + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + Stats = riaknostic_node:stats(), + {ring_members, RingMembers} = lists:keyfind(ring_members, 1, Stats), + + {SearchEnabled, _} = riaknostic_node:cluster_command(application, get_env, [riak_search, enabled]), + + {_, X} = lists:unzip(SearchEnabled), + NodesSearchEnabled = lists:zip(RingMembers, X), + + lists:append([ + [ {warning, {riak_search, NodesSearchEnabled}} || length(lists:usort(SearchEnabled)) > 1 ] + ]). + +-spec format(term()) -> {io:format(), [term()]}. +format({riak_search, Services}) -> + {"Search is not enabled on all nodes: ~p", [Services]}. diff --git a/src/weatherreport/src/riaknostic_check_strong_consistency.erl b/src/weatherreport/src/riaknostic_check_strong_consistency.erl new file mode 100644 index 00000000000..e5f0569bfc9 --- /dev/null +++ b/src/weatherreport/src/riaknostic_check_strong_consistency.erl @@ -0,0 +1,58 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Diagnostic that checks Riak's current memory usage. If memory +%% usage is high, a warning message will be sent, otherwise only +%% informational messages. +-module(riaknostic_check_strong_consistency). +-behaviour(riaknostic_check). + +-export([description/0, + valid/0, + check/0, + format/1]). + +-spec description() -> string(). +description() -> + "Strong consistency configuration valid". + +-spec valid() -> boolean(). +valid() -> + riaknostic_node:can_connect(). + +-spec check() -> [{lager:log_level(), term()}]. +check() -> + StrongConsistencyOption = riaknostic_config:get_app_env([riak_core, enable_consensus]), + { AAEOption, _ } = riaknostic_config:get_app_env([riak_kv, anti_entropy]), + maybe_strong_consistency_aae_misconfigured(StrongConsistencyOption, AAEOption). + +-spec maybe_strong_consistency_aae_misconfigured(boolean, on | off | any()) -> [ { term(), term() } ] | []. +maybe_strong_consistency_aae_misconfigured(true, off) -> + [ { critical, { strong_consistency_aae_misconfigured } } ]; +maybe_strong_consistency_aae_misconfigured(false, _) -> + []; +maybe_strong_consistency_aae_misconfigured(true, on) -> + []. + +-spec format(term()) -> {io:format(), [term()]}. +format({ strong_consistency_aae_misconfigured }) -> + { "Strong consistency has been enabled without AAE -- all consistent operations will timeout until AAE is enabled.", [] }. diff --git a/src/weatherreport/src/riaknostic_config.erl b/src/weatherreport/src/riaknostic_config.erl new file mode 100644 index 00000000000..43cd2148cfc --- /dev/null +++ b/src/weatherreport/src/riaknostic_config.erl @@ -0,0 +1,282 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Provides convenient access to Riak configuration values. When +%% the {@link riaknostic. riaknostic} module calls {@link +%% prepare/0. prepare/0}, Riak's app.config and +%% vm.args files will be parsed and memoized, and lager +%% will be started on the console at the configured severity level. +%% @end + +-module(riaknostic_config). + +-export([prepare/0, + data_directories/0, + get_app_env/1, + get_app_env/2, + get_vm_env/1, + base_dir/0, + etc_dir/0, + node_name/0, + cookie/0, + user/0]). + +%% @doc Prepares appropriate configuration so the riaknostic script +%% can run. This is called by the riaknostic module and you do +%% not need to invoke it. +-spec prepare() -> ok | {error, iodata()}. +prepare() -> + prepare([fun start_lager/0, fun load_app_config/0, fun load_vm_args/0]). + +prepare([]) -> + ok; +prepare([Fun|T]) -> + case Fun() of + {error, Reason} -> + {error, Reason}; + _ -> + prepare(T) + end. + +%% @doc Determines where Riak is configured to store data. Returns a +%% list of paths to directories defined by storage backends. +-spec data_directories() -> [ file:filename() ]. +data_directories() -> + KVBackend = get_app_env([riak_kv, storage_backend]), + SearchBackend = get_app_env([riak_search, storage_backend], merge_index_backend), + Dirs = case get_app_env([riak_search, enabled]) of + true -> + data_directory(KVBackend) ++ data_directory(SearchBackend); + _ -> + data_directory(KVBackend) + end, + [ filename:absname(Dir, base_dir()) || Dir <- Dirs, Dir =/= undefined ]. + +%% @doc Get a key out of the app.config file, or if it doesn't exist, +%% return the Default. You specify a nested key using a list of atoms, +%% e.g. [riak_kv, storage_backend]. +%% @see get_app_env/1 +-spec get_app_env([atom()], term()) -> term(). +get_app_env(Keys, Default) -> + case get_app_env(Keys) of + undefined -> + Default; + Value -> + Value + end. + +%% @doc Get a key out of the app.config file. You specify a nested +%% key using a list of atoms, e.g. [riak_kv, storage_backend]. +%% @equiv get_app_env(Keys, undefined) +-spec get_app_env([atom()]) -> undefined | term(). +get_app_env(Keys) -> + {ok, Env} = application:get_env(riaknostic, app_config), + find_nested_key(Keys, Env). + +%% @doc Get an -env flag out of the vm.args file. +-spec get_vm_env(string()) -> string() | undefined. +get_vm_env(Key) -> + case application:get_env(riaknostic, vm_env) of + undefined -> + undefined; + {ok, PList} -> + proplists:get_value(Key, PList) + end. + +%% @doc Determines the user/uid that the installed Riak runs as. +-spec user() -> string(). +user() -> + case application:get_env(riaknostic, user) of + {ok, Value} -> + Value; + _ -> undefined + end. + +%% @doc The base directory from which the Riak script runs. +-spec base_dir() -> file:filename(). +base_dir() -> + case application:get_env(riaknostic, base) of + undefined -> + filename:absname("."); + {ok, Path} -> + filename:absname(Path) + end. + +%% @doc The Riak configuration directory. +-spec etc_dir() -> file:filename(). +etc_dir() -> + case application:get_env(riaknostic, etc) of + undefined -> + filename:absname("./etc", base_dir()); + {ok, Path} -> + filename:absname(Path, base_dir()) + end. + +%% @doc The local Riak node name. Includes whether the node uses short +%% or long nodenames for distributed Erlang. +-spec node_name() -> {shortnames | longnames, Name::string()}. +node_name() -> + case application:get_env(riaknostic, node_name) of + undefined -> + undefined; + {ok, Node} -> + Node + end. + +%% @doc The Riak node's distributed Erlang cookie. +-spec cookie() -> atom(). +cookie() -> + case application:get_env(riaknostic, cookie) of + undefined -> + undefined; + {ok, Cookie} -> + list_to_atom(Cookie) + end. + +%% Private functions +start_lager() -> + application:load(lager), + case application:get_env(riaknostic, log_level) of + undefined -> + {error, "Log level not set!"}; + {ok, Level} -> + application:set_env(lager, crash_log, undefined), + application:set_env(lager, handlers, [{lager_console_backend, Level}]), + lager:start() + end. + +load_app_config() -> + {ok, [[AppConfig]]} = init:get_argument(config), + case file:consult(AppConfig) of + {ok, [Config]} -> + application:set_env(riaknostic, app_config, Config); + _ -> + {error, io_lib:format("Riak config file ~s is malformed!", [AppConfig])} + end. + +load_vm_args() -> + VmArgs = case init:get_argument(vm_args) of + {ok, [[X]]} -> X; + _ -> + %% This is a backup. If for some reason -vm_args isn't specified + %% then assume it lives in the same dir as app.config + {ok, [[AppConfig]]} = init:get_argument(config), + AppIndex = string:str(AppConfig, "app"), + ConfigIndex = string:rstr(AppConfig, "config"), + string:sub_string(AppConfig, 1, AppIndex - 1) ++ "vm" ++ + string:sub_string(AppConfig, AppIndex + 3, ConfigIndex-1) ++ "args" + end, + + case file:read_file(VmArgs) of + {error, Reason} -> + {error, io_lib:format("Could not read ~s, received error ~w!", [VmArgs, Reason])}; + {ok, Binary} -> + load_vm_args(Binary) + end. + +load_vm_args(Bin) when is_binary(Bin) -> + load_vm_args(re:split(Bin, "\s*\r?\n\s*", [{return, list}, trim])); +load_vm_args([]) -> + ok; +load_vm_args([[$#|_]|T]) -> + load_vm_args(T); +load_vm_args([""|T]) -> + load_vm_args(T); +load_vm_args(["-sname " ++ NodeName|T]) -> + application:set_env(riaknostic, node_name, {shortnames, string:strip(NodeName)}), + load_vm_args(T); +load_vm_args(["-name " ++ NodeName|T]) -> + application:set_env(riaknostic, node_name, {longnames, string:strip(NodeName)}), + load_vm_args(T); +load_vm_args(["-setcookie " ++ Cookie|T]) -> + application:set_env(riaknostic, cookie, string:strip(Cookie)), + load_vm_args(T); +load_vm_args(["-env " ++ Env|T]) -> + [Key, Value] = re:split(Env, "\s+", [{return, list}, trim]), + add_or_insert_env(vm_env, {Key, Value}), + load_vm_args(T); +load_vm_args([[$+|EmuFlags]|T]) -> + [Flag|Rest] = re:split(EmuFlags, "\s+", [{return,list}, trim]), + add_or_insert_env(emu_flags, {[$+|Flag], Rest}), + load_vm_args(T); +load_vm_args([[$-|InitFlags]|T]) -> + [Flag|Rest] = re:split(InitFlags, "\s+", [{return,list}, trim]), + add_or_insert_env(init_flags, {[$-|Flag], Rest}), + load_vm_args(T); +load_vm_args([Line|_]) -> + {error, io_lib:format("Erroneous line in vm.args: ~s", [Line])}. + +add_or_insert_env(Key, Value) -> + case application:get_env(riaknostic, Key) of + undefined -> + application:set_env(riaknostic, Key, [Value]); + {ok, List} -> + application:set_env(riaknostic, Key, [Value|List]) + end. + +find_nested_key(_, undefined) -> + undefined; +find_nested_key([], Val) -> + Val; +find_nested_key([Key|T], PList) -> + find_nested_key(T, proplists:get_value(Key, PList)). + +%% Determine the data directory(ies) for the configured storage backend +-spec data_directory(atom()) -> [ file:filename() ]. +data_directory(riak_kv_bitcask_backend) -> + [ get_app_env([bitcask, data_root]) ]; +data_directory(riak_kv_eleveldb_backend) -> + [ get_app_env([eleveldb, data_root]) ]; +data_directory(merge_index_backend) -> + [ get_app_env([merge_index, data_root]) ]; +data_directory(riak_kv_innostore_backend) -> + [ get_app_env([innostore, data_home_dir]), + get_app_env([innostore, log_group_home_dir]) ]; +data_directory(riak_kv_multi_backend) -> + [ multi_data_directory(Backend) || + Backend <- get_app_env([riak_kv, multi_backend]), + element(2, Backend) =/= riak_kv_memory_backend ]; +data_directory(_) -> %% Memory or unknown backend + []. + +%% Extracts data paths from multi_backend config +multi_data_directory({_, riak_kv_bitcask_backend, Props}) -> + case proplists:get_value(data_root, Props) of + undefined -> + get_app_env([bitcask, data_root]); + Path when is_list(Path) -> + Path + end; +multi_data_directory({_, riak_kv_eleveldb_backend, Props}) -> + case proplists:get_value(data_root, Props) of + undefined -> + get_app_env([eleveldb, data_root]); + Path when is_list(Path) -> + Path + end; +multi_data_directory({_, riak_kv_innostore_backend, Props}) -> + case proplists:get_value(data_home_dir, Props) of + undefined -> + get_app_env([innostore, data_home_dir]); + Path when is_list(Path) -> + Path + end. diff --git a/src/weatherreport/src/riaknostic_export.erl b/src/weatherreport/src/riaknostic_export.erl new file mode 100644 index 00000000000..65371c7ada0 --- /dev/null +++ b/src/weatherreport/src/riaknostic_export.erl @@ -0,0 +1,176 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Gather and export system stats and other +%% diagnostic output. +%% @end + +-module(riaknostic_export). +-export([export/0]). + +%% @doc wrapper for all the moving parts of the export function +%% XXX/evan terrible first draft error-handling +-spec export() -> ok | {error, string()}. +export() -> + TmpDir = prep_tmp_dir(), + CmdList = get_cmd_list(), + FileList = get_file_list(), + copy_to_dir(FileList, TmpDir), + Outputs = run_commands(CmdList), + write_to_file(Outputs, TmpDir), + package_files(TmpDir), + cleanup_tmp_dir(TmpDir). + +get_cmd_list() -> + List = [ + {"df", "df"} + ], + Stats = [ + {"iostat", "iostat 1 4"}, + {"vmstat", "vmstat 1 4"}, + {"sysctl-net", "sysctl net"} + ], + PerOS = case os:type() of + {unix, linux} -> + [ + {"swappiness", "sysctl vm.swappiness"} + ] ++ Stats; + {unix, darwin} -> + [ + ]; % unsupported for production + {unix, freebsd} -> + [ + ] ++ Stats; + {unix, openbsd} -> + [ + ] ++ Stats; + {unix, sunos} -> + [ + ]; + _ -> [] %maybe explicitly error here? + end, + List ++ PerOS. + +get_file_list() -> + List = [ + "/etc/hosts", % might want to omit + "/etc/hostname", + "/etc/riak/", %TODO get from ENV variable + "/etc/fstab", + "/var/log/riak/" %TODO get from ENV variable + ], + PerOS = case os:type() of + {unix, linux} -> + [ + "/etc/fstab" + ]; + {unix, darwin} -> []; % unsupported for production + {unix, freebsd} -> + [ + ]; + {unix, openbsd} -> + [ + ]; + {unix, sunos} -> + [ + ]; + _ -> [] %maybe explicitly error here? + end, + List ++ PerOS. + +run_commands(CmdList) -> + [{Name, run_command(Cmd)} || + {Name, Cmd} <- CmdList]. + +run_command(Cmd) -> + try + riaknostic_util:run_command(Cmd) + catch + Error:Reason -> + io:format("when running ~s got ~s:~s~n ~w~n", + [Cmd, Error, Reason, erlang:get_stacktrace()]), + "got error\n" + end. + + +copy_to_dir([], _Dir) -> + ok; +copy_to_dir(FileList, Dir) -> + [FileName|Tail] = FileList, + case lists:last(FileName) of + $/ -> + %%add files in this dir to tail + {ok, Files} = file:list_dir(FileName), + copy_to_dir([FileName ++ File || File <- Files] ++ Tail, + Dir); + _ -> + Res = file:copy(FileName, + Dir ++ filename:basename(FileName)), + case Res of + {ok, _} -> ok; + {error, Reason} -> + %TODO: Something better than a hard stop + io:format("Couldn't copy ~s. Error: ~s", + [FileName, Reason]) + end, + copy_to_dir(Tail, Dir) + end. + +write_to_file([], _Dir) -> + ok; +write_to_file(Outputs, Dir) -> + [H|T] = Outputs, + {Filename, Output} = H, + file:write_file(Dir ++ "/" ++ Filename, Output), + write_to_file(T, Dir). + +package_files(Dir) -> + % gathered everything, now package it; + {ok, NameList} = file:list_dir(Dir), + FileList = ["export/" ++ File || File <- NameList], + PrefLen = string:len(Dir) - string:len("export/"), + Prefix = string:sub_string(Dir, 1, PrefLen), + {ok, Cwd} = file:get_cwd(), + io:format("Writing export file: ~s~n", + [Cwd ++ "/export.zip"]), + zip:zip("export.zip", FileList, [{cwd, Prefix}]). + +prep_tmp_dir() -> + % this may not be a good idea + {A, B, C} = now(), + {ok, Cwd} = file:get_cwd(), + DirPrefix = Cwd ++ "/export" ++ integer_to_list(A+B+C), + file:make_dir(DirPrefix), + DirName = DirPrefix ++ "/export/", + file:make_dir(DirName), + DirName. + +cleanup_tmp_dir(DirName) -> + {ok, FileNames} = file:list_dir(DirName), + lists:map(fun(File) -> file:delete(DirName ++ File) end, + FileNames), + ok = file:del_dir(DirName), + PrefLen = string:len(DirName) - string:len("export/"), + Prefix = string:sub_string(DirName, 1, PrefLen), + ok = file:del_dir(Prefix). + + diff --git a/src/weatherreport/src/riaknostic_node.erl b/src/weatherreport/src/riaknostic_node.erl new file mode 100644 index 00000000000..7e0ee0e6cff --- /dev/null +++ b/src/weatherreport/src/riaknostic_node.erl @@ -0,0 +1,208 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Functions that help diagnostics interact with the local Riak +%% node or other members of the cluster. +-module(riaknostic_node). + +-export([can_connect/0, + can_connect_all/0, + stats/0, + pid/0, + local_command/2, + local_command/3, + local_command/4, + cluster_command/2, + cluster_command/3, + cluster_command/4 + ]). + +%% @doc Calls the given 0-arity module and function on the local Riak +%% node and returns the result of that call. +%% @equiv local_command(Module, Function, []) +%% @see can_connect/0. +-spec local_command(Module::atom(), Function::atom()) -> term(). +local_command(Module, Function) -> + local_command(Module, Function, []). + +%% @doc Calls the given module and function with the given arguments +%% on the local Riak node and returns the result of that call. +%% @equiv local_command(Module, Function, Args, 5000) +%% @see can_connect/0 +-spec local_command(Module::atom(), Function::atom(), Args::[term()]) -> term(). +local_command(Module, Function, Args) -> + local_command(Module, Function, Args, 5000). + +%% @doc Calls the given module and function with the given arguments +%% on the local Riak node and returns the result of that call, +%% returning an error if the call doesn't complete within the given +%% timeout. +%% @equiv rpc:call(RiakNodeName, Module, Function, Args, Timeout) +%% @see can_connect/0 +-spec local_command(Module::atom(), Function::atom(), Args::[term()], Timeout::integer()) -> term(). +local_command(Module, Function, Args, Timeout) -> + riaknostic_util:log(debug, "Local RPC: ~p:~p(~p) [~p]", [Module, Function, Args, Timeout]), + rpc:call(nodename(), Module, Function, Args, Timeout). + +%% @doc Calls the given 0-arity module and function on all members of +%% the Riak cluster. +%% @equiv cluster_command(Module, Function, []) +%% @see can_connect/0 +-spec cluster_command(Module::atom(), Function::atom()) -> term(). +cluster_command(Module, Function) -> + cluster_command(Module, Function, []). + +%% @doc Calls the given module and function with the given arguments +%% on all members of the Riak cluster. +%% @equiv cluster_command(Module, Function, Args, 5000) +%% @see can_connect/0 +-spec cluster_command(Module::atom(), Function::atom(), Args::[term()]) -> term(). +cluster_command(Module, Function, Args) -> + cluster_command(Module, Function, Args, 5000). + +%% @doc Calls the given module and function with the given arguments +%% on all members for the Riak cluster, returning an error if the call +%% doesn't complete within the given timeout. +%% @equiv rpc:multicall(RiakClusterMembers, Module, Function, Args, Timeout) +%% @see can_connect/0 +-spec cluster_command(Module::atom(), Function::atom(), Args::[term()], Timeout::integer()) -> term(). +cluster_command(Module, Function, Args, Timeout) -> + riaknostic_util:log(debug, "Cluster RPC: ~p:~p(~p) [~p]", [Module, Function, Args, Timeout]), + Stats = stats(), + {ring_members, RingMembers} = lists:keyfind(ring_members, 1, Stats), + rpc:multicall(RingMembers, Module, Function, Args, Timeout). + +%% @doc Retrieves the operating system's process ID of the local Riak +%% node. +%% @equiv local_command(os, getpid) +%% @see can_connect/0 +-spec pid() -> string(). +pid() -> + local_command(os, getpid). + +%% @doc Attempts to connect to the local Riak node if it is not +%% already, and returns whether connection was successful. +-spec can_connect() -> true | false. +can_connect() -> + case is_connected() of + true -> true; + false -> + riaknostic_util:log(debug, "Not connected to the local Riak node, trying to connect. alive:~p connect_failed:~p", [is_alive(), connect_failed()]), + maybe_connect() + end. + +-spec can_connect_all() -> true | false. +can_connect_all() -> + case is_connected() of + true -> + case riaknostic_check_nodes_connected:check() of + [] -> true; + _ -> false + end; + false -> false + end. + +%% @doc Fetches or returns previously fetched Riak statistics. +%% @see can_connect/0 +-spec stats() -> [proplists:property()]. +stats() -> + case has_stats() of + {ok, Stats} -> Stats; + _ -> fetch_stats() + end. + +%% Private functions +is_connected() -> + is_alive() andalso connect_failed() =/= true. + +maybe_connect() -> + case connect_failed() of + true -> false; + _ -> try_connect() + end. + +try_connect() -> + TargetNode = nodename(), + case is_alive() of + true -> ok; + _ -> start_net() + end, + case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of + {true, pong} -> + application:set_env(riaknostic, connect_failed, false), + riaknostic_util:log(debug, "Connected to local Riak node ~p.", [TargetNode]), + true; + _ -> + application:set_env(riaknostic, connect_failed, true), + lager:warning("Could not connect to the local Riak node ~p, some checks will not run.", [TargetNode]), + false + end. + +connect_failed() -> + case application:get_env(riaknostic, connect_failed) of + {ok, true} -> true; + undefined -> undefined; + _ -> false + end. + +start_net() -> + riaknostic_util:log(debug, "Starting distributed Erlang."), + {Type, RiakName} = riaknostic_config:node_name(), + ThisNode = append_node_suffix(RiakName, "_diag"), + {ok, _} = net_kernel:start([ThisNode, Type]), + erlang:set_cookie(node(), riaknostic_config:cookie()). + +nodename() -> + {_, Name} = riaknostic_config:node_name(), + case string:tokens(Name, "@") of + [_Node, _Host] -> + list_to_atom(Name); + [Node] -> + [_, Host] = string:tokens(atom_to_list(node()), "@"), + list_to_atom(lists:concat([Node, "@", Host])) + end. + +append_node_suffix(Name, Suffix) -> + case string:tokens(Name, "@") of + [Node, Host] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); + [Node] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid()])) + end. + +has_stats() -> + case application:get_env(riaknostic, local_stats) of + {ok, Stats} -> + {ok, Stats}; + undefined -> + false + end. + +fetch_stats() -> + riaknostic_util:log(debug, "Fetching local riak_kv_status."), + case local_command(riak_kv_status, statistics) of + [] -> []; + PList -> + application:set_env(riaknostic, local_stats, PList), + PList + end. + diff --git a/src/weatherreport/src/riaknostic_util.erl b/src/weatherreport/src/riaknostic_util.erl new file mode 100644 index 00000000000..5d370862b6c --- /dev/null +++ b/src/weatherreport/src/riaknostic_util.erl @@ -0,0 +1,89 @@ +%% ------------------------------------------------------------------- +%% +%% riaknostic - automated diagnostic tools for Riak +%% +%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% @doc Utility functions for riaknostic. +%% @end +-module(riaknostic_util). +-export([short_name/1, + run_command/1, + log/2,log/3, + binary_to_float/1]). + +%% @doc Converts a check module name into a short name that can be +%% used to refer to a check on the command line. For example, +%% riaknostic_check_disk becomes "disk". +-spec short_name(module()) -> iodata() | unicode:charlist(). +short_name(Mod) when is_atom(Mod) -> + re:replace(atom_to_list(Mod), "riaknostic_check_", "", [{return, list}]). + +%% @doc Runs a shell command and returns the output. stderr is +%% redirected to stdout so its output will be included. +-spec run_command(Command::iodata()) -> StdOut::iodata(). +run_command(Command) -> + riaknostic_util:log(debug, "Running shell command: ~s", [Command]), + Port = erlang:open_port({spawn,Command},[exit_status, stderr_to_stdout]), + do_read(Port, []). + +do_read(Port, Acc) -> + receive + {Port, {data, StdOut}} -> + riaknostic_util:log(debug, "Shell command output: ~n~s~n",[StdOut]), + do_read(Port, Acc ++ StdOut); + {Port, {exit_status, _}} -> + %%port_close(Port), + Acc; + Other -> + io:format("~w", [Other]), + do_read(Port, Acc) + end. + +%% @doc Converts a binary containing a text representation of a float +%% into a float type. +-spec binary_to_float(binary()) -> float(). +binary_to_float(Bin) -> + list_to_float(binary_to_list(Bin)). + +log(Level, Format, Terms) -> + case should_log(Level) of + true -> + io:format(lists:concat(["[", Level, "] ", Format, "~n"]), Terms); + false -> + ok + end, + lager:log(Level, self(), Format, Terms). + +log(Level, String) -> + case should_log(Level) of + true -> + io:format(lists:concat(["[", Level, "] ", String, "~n"])); + false -> + ok + end, + lager:log(Level, self(), String). + +should_log(Level) -> + AppLevel = case application:get_env(riaknostic, log_level) of + undefined -> info; + {ok, L0} -> L0 + end, + lager_util:level_to_num(AppLevel) >= lager_util:level_to_num(Level). +