From d2f1badd373b96ce4fa0027dc2173d8d977a8505 Mon Sep 17 00:00:00 2001
From: Howard Hinnant <howard.hinnant@gmail.com>
Date: Mon, 6 Apr 2020 17:22:19 -0400
Subject: [PATCH] Create health_check rpc

* Gives a summary of the health of the node:
  Healthy, Warning, or Critical

* Last validated ledger age:
  <7s is Healthy,
  7s to 20s is Warning
  > 20s is Critcal

* If amendment blocked, Critical

* Number of peers:
  > 7 is Healthy
  1 to 7 is Warning
  0 is Critical

* server state:
  One of full, validating or proposing is Healthy
  One of syncing, tracking or connected is Warning
  All other states are Critical

* load factor:
  <= 100 is Healthy
  101 to 999 is Warning
  >= 1000 is Critical

* If not Healthy, info field contains data that is considered not
  Healthy.

Fixes: #2809
---
 Builds/CMake/RippledCore.cmake          |   1 +
 src/ripple/app/main/Main.cpp            |   1 +
 src/ripple/net/impl/RPCCall.cpp         |   1 +
 src/ripple/rpc/handlers/Handlers.h      |   2 +
 src/ripple/rpc/handlers/HealthCheck.cpp | 119 ++++++++++++++++++++++++
 src/ripple/rpc/impl/Handler.cpp         |   1 +
 6 files changed, 125 insertions(+)
 create mode 100644 src/ripple/rpc/handlers/HealthCheck.cpp

diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake
index 5dffaade794..1860c6afcee 100644
--- a/Builds/CMake/RippledCore.cmake
+++ b/Builds/CMake/RippledCore.cmake
@@ -573,6 +573,7 @@ target_sources (rippled PRIVATE
   src/ripple/rpc/handlers/FetchInfo.cpp
   src/ripple/rpc/handlers/GatewayBalances.cpp
   src/ripple/rpc/handlers/GetCounts.cpp
+  src/ripple/rpc/handlers/HealthCheck.cpp
   src/ripple/rpc/handlers/LedgerAccept.cpp
   src/ripple/rpc/handlers/LedgerCleanerHandler.cpp
   src/ripple/rpc/handlers/LedgerClosed.cpp
diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp
index 527f86f19f2..51f80c4f465 100644
--- a/src/ripple/app/main/Main.cpp
+++ b/src/ripple/app/main/Main.cpp
@@ -148,6 +148,7 @@ printHelp(const po::options_description& desc)
            "     gateway_balances [<ledger>] <issuer_account> [ <hotwallet> [ "
            "<hotwallet> ]]\n"
            "     get_counts\n"
+           "     health_check\n"
            "     json <method> <json>\n"
            "     ledger [<id>|current|closed|validated] [full]\n"
            "     ledger_accept\n"
diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp
index a565d4bc321..5999c80b88e 100644
--- a/src/ripple/net/impl/RPCCall.cpp
+++ b/src/ripple/net/impl/RPCCall.cpp
@@ -1237,6 +1237,7 @@ class RPCParser
             {"fetch_info", &RPCParser::parseFetchInfo, 0, 1},
             {"gateway_balances", &RPCParser::parseGatewayBalances, 1, -1},
             {"get_counts", &RPCParser::parseGetCounts, 0, 1},
+            {"health_check", &RPCParser::parseAsIs, 0, 0},
             {"json", &RPCParser::parseJson, 2, 2},
             {"json2", &RPCParser::parseJson2, 1, 1},
             {"ledger", &RPCParser::parseLedger, 0, 2},
diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h
index b9c47be9db5..38ba24e2ee6 100644
--- a/src/ripple/rpc/handlers/Handlers.h
+++ b/src/ripple/rpc/handlers/Handlers.h
@@ -71,6 +71,8 @@ doGatewayBalances(RPC::JsonContext&);
 Json::Value
 doGetCounts(RPC::JsonContext&);
 Json::Value
+doHealthCheck(RPC::JsonContext&);
+Json::Value
 doLedgerAccept(RPC::JsonContext&);
 Json::Value
 doLedgerCleaner(RPC::JsonContext&);
diff --git a/src/ripple/rpc/handlers/HealthCheck.cpp b/src/ripple/rpc/handlers/HealthCheck.cpp
new file mode 100644
index 00000000000..06128b7c83e
--- /dev/null
+++ b/src/ripple/rpc/handlers/HealthCheck.cpp
@@ -0,0 +1,119 @@
+//------------------------------------------------------------------------------
+/*
+    This file is part of rippled: https://github.com/ripple/rippled
+    Copyright (c) 2012-2014 Ripple Labs Inc.
+
+    Permission to use, copy, modify, and/or distribute this software for any
+    purpose  with  or without fee is hereby granted, provided that the above
+    copyright notice and this permission notice appear in all copies.
+
+    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
+    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
+    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include <ripple/app/misc/NetworkOPs.h>
+#include <ripple/json/json_value.h>
+#include <ripple/net/RPCErr.h>
+#include <ripple/protocol/jss.h>
+#include <ripple/rpc/Context.h>
+#include <ripple/rpc/impl/TransactionSign.h>
+#include <ripple/rpc/Role.h>
+
+namespace ripple {
+
+Json::Value
+doHealthCheck(RPC::JsonContext& context)
+{
+    bool constexpr humanReadable = true;
+    bool constexpr noAdmin = false;
+    bool constexpr noCounters = false;
+    auto info = context.netOps.getServerInfo(
+                    humanReadable, noAdmin, noCounters);
+
+    int last_validated_ledger_age = std::numeric_limits<int>::max();
+    if (info.isMember("validated_ledger"))
+        last_validated_ledger_age = info["validated_ledger"]["age"].asInt();
+    bool amendment_blocked = false;
+    if (info.isMember("amendment_blocked"))
+        amendment_blocked = true;
+    int number_peers = info["peers"].asInt();
+    std::string server_state = info["server_state"].asString();
+    auto load_factor = info["load_factor"].asDouble();
+
+    Json::Value ret = Json::objectValue;
+    enum {healthy, warning, critical};
+    int health = healthy;
+    auto set_health = [&health](int state)
+        {
+            if (health < state)
+                health = state;
+        };
+
+    if (last_validated_ledger_age >= 7)
+    {
+        ret[jss::info]["validated_ledger"] = last_validated_ledger_age;
+        if (last_validated_ledger_age < 20)
+            set_health(warning);
+        else
+            set_health(critical);
+    }
+
+    if (amendment_blocked)
+    {
+        ret[jss::info]["amendment_blocked"] = true;
+        set_health(critical);
+    }
+
+    if (number_peers <= 7)
+    {
+        ret[jss::info]["peers"] = number_peers;
+        if (number_peers != 0)
+            set_health(warning);
+        else
+            set_health(critical);
+    }
+
+    if (!(server_state == "full" || server_state == "validating" ||
+          server_state == "proposing"))
+    {
+        ret[jss::info]["server_state"] = server_state;
+        if (server_state == "syncing" || server_state == "tracking" ||
+            server_state == "connected")
+        {
+            set_health(warning);
+        }
+        else
+            set_health(critical);
+    }
+
+    if (load_factor > 100)
+    {
+        ret[jss::info]["load_factor"] = load_factor;
+        if (load_factor < 1000)
+            set_health(warning);
+        else
+            set_health(critical);
+    }
+
+    switch (health)
+    {
+    case healthy:
+        ret["health"] = "Healthy";
+        break;
+    case warning:
+        ret["health"] = "Warning";
+        break;
+    default:
+        ret["health"] = "Critical";
+        break;
+    }
+    return ret;
+}
+
+} // ripple
diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp
index b5fa9978540..4249699d465 100644
--- a/src/ripple/rpc/impl/Handler.cpp
+++ b/src/ripple/rpc/impl/Handler.cpp
@@ -87,6 +87,7 @@ Handler const handlerArray[]{
     {"feature", byRef(&doFeature), Role::ADMIN, NO_CONDITION},
     {"fee", byRef(&doFee), Role::USER, NEEDS_CURRENT_LEDGER},
     {"fetch_info", byRef(&doFetchInfo), Role::ADMIN, NO_CONDITION},
+    {"health_check", byRef(&doHealthCheck), Role::USER, NO_CONDITION},
     {"ledger_accept",
      byRef(&doLedgerAccept),
      Role::ADMIN,