|
2 | 2 |
|
3 | 3 | #include <ethminer-buildinfo.h>
|
4 | 4 |
|
5 |
| -ApiServer::ApiServer(AbstractServerConnector *conn, serverVersion_t type, Farm &farm, bool &readonly) : AbstractServer(*conn, type), m_farm(farm) |
| 5 | +ApiServer::ApiServer(boost::asio::io_service& io_service, int portnum, bool readonly, Farm& f) : |
| 6 | + m_acceptor(io_service, tcp::endpoint(tcp::v4(), portnum)), |
| 7 | + m_io_strand(io_service), |
| 8 | + m_farm(f) |
6 | 9 | {
|
7 |
| - this->bindAndAddMethod(Procedure("miner_getstat1", PARAMS_BY_NAME, JSON_OBJECT, NULL), &ApiServer::getMinerStat1); |
8 |
| - this->bindAndAddMethod(Procedure("miner_getstathr", PARAMS_BY_NAME, JSON_OBJECT, NULL), &ApiServer::getMinerStatHR); |
9 |
| - if (!readonly) { |
10 |
| - this->bindAndAddMethod(Procedure("miner_restart", PARAMS_BY_NAME, JSON_OBJECT, NULL), &ApiServer::doMinerRestart); |
11 |
| - this->bindAndAddMethod(Procedure("miner_reboot", PARAMS_BY_NAME, JSON_OBJECT, NULL), &ApiServer::doMinerReboot); |
| 10 | + m_readonly.store(readonly, std::memory_order_relaxed); |
| 11 | +} |
| 12 | + |
| 13 | +void ApiServer::start() |
| 14 | +{ |
| 15 | + // cnote << "ApiServer::start"; |
| 16 | + if (m_acceptor.local_endpoint().port() == 0) return; |
| 17 | + m_running.store(true, std::memory_order_relaxed); |
| 18 | + |
| 19 | + cnote << "Api server listening for connections on port " + to_string(m_acceptor.local_endpoint().port()); |
| 20 | + |
| 21 | + m_workThread = std::thread{ boost::bind(&ApiServer::begin_accept, this) }; |
| 22 | + |
| 23 | +} |
| 24 | + |
| 25 | +void ApiServer::stop() |
| 26 | +{ |
| 27 | + m_acceptor.cancel(); |
| 28 | + m_running.store(false, std::memory_order_relaxed); |
| 29 | + |
| 30 | + // Dispose all sessions (if any) |
| 31 | + m_sessions.clear(); |
| 32 | + |
| 33 | +} |
| 34 | + |
| 35 | +void ApiServer::begin_accept() |
| 36 | +{ |
| 37 | + if (!isRunning()) return; |
| 38 | + |
| 39 | + dev::setThreadName("Api"); |
| 40 | + std::shared_ptr<ApiConnection> session = std::make_shared<ApiConnection>(m_acceptor.get_io_service(), ++lastSessionId, m_readonly, m_farm); |
| 41 | + m_acceptor.async_accept(session->socket(), m_io_strand.wrap(boost::bind(&ApiServer::handle_accept, this, session, boost::asio::placeholders::error))); |
| 42 | +} |
| 43 | + |
| 44 | +void ApiServer::handle_accept(std::shared_ptr<ApiConnection> session, boost::system::error_code ec) |
| 45 | +{ |
| 46 | + // Start new connection |
| 47 | + // cnote << "ApiServer::handle_accept"; |
| 48 | + if (!ec) { |
| 49 | + session->onDisconnected([&](int id) |
| 50 | + { |
| 51 | + // Destroy pointer to session |
| 52 | + auto it = find_if(m_sessions.begin(), m_sessions.end(), [&id](const std::shared_ptr<ApiConnection> session) {return session->getId() == id; }); |
| 53 | + if (it != m_sessions.end()) { |
| 54 | + auto index = std::distance(m_sessions.begin(), it); |
| 55 | + m_sessions.erase(m_sessions.begin() + index); |
| 56 | + } |
| 57 | + |
| 58 | + }); |
| 59 | + session->start(); |
| 60 | + m_sessions.push_back(session); |
| 61 | + cnote << "New api session from" << session->socket().remote_endpoint(); |
| 62 | + |
| 63 | + } |
| 64 | + else { |
| 65 | + session.reset(); |
| 66 | + } |
| 67 | + |
| 68 | + // Resubmit new accept |
| 69 | + begin_accept(); |
| 70 | + |
| 71 | +} |
| 72 | + |
| 73 | +void ApiConnection::disconnect() |
| 74 | +{ |
| 75 | + // cnote << "ApiConnection::disconnect"; |
| 76 | + |
| 77 | + // Cancel pending operations |
| 78 | + m_socket.cancel(); |
| 79 | + |
| 80 | + if (m_socket.is_open()) { |
| 81 | + |
| 82 | + boost::system::error_code ec; |
| 83 | + m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); |
| 84 | + m_socket.close(ec); |
| 85 | + } |
| 86 | + |
| 87 | + if (m_onDisconnected) { m_onDisconnected(this->getId()); } |
| 88 | + |
| 89 | +} |
| 90 | + |
| 91 | +void ApiConnection::start() |
| 92 | +{ |
| 93 | + // cnote << "ApiConnection::start"; |
| 94 | + recvSocketData(); |
| 95 | +} |
| 96 | + |
| 97 | +void ApiConnection::processRequest(Json::Value& requestObject) |
| 98 | +{ |
| 99 | + Json::Value jRes; |
| 100 | + jRes["jsonrpc"] = "2.0"; |
| 101 | + |
| 102 | + // Strict sanity checks over jsonrpc v2 |
| 103 | + if ( |
| 104 | + (!requestObject.isMember("jsonrpc") || requestObject["jsonrpc"].empty() || !requestObject["jsonrpc"].isString() || requestObject.get("jsonrpc", ".") != "2.0") || |
| 105 | + (!requestObject.isMember("method") || requestObject["method"].empty() || !requestObject["method"].isString()) || |
| 106 | + (!requestObject.isMember("id") || requestObject["id"].empty() || !requestObject["id"].isUInt()) |
| 107 | + ) |
| 108 | + { |
| 109 | + jRes["id"] = Json::nullValue; |
| 110 | + jRes["error"]["code"] = -32600; |
| 111 | + jRes["error"]["message"] = "Invalid Request"; |
| 112 | + sendSocketData(jRes); |
| 113 | + return; |
| 114 | + } |
| 115 | + |
| 116 | + |
| 117 | + // Process messages |
| 118 | + std::string _method = requestObject.get("method", "").asString(); |
| 119 | + jRes["id"] = requestObject.get("id", 0).asInt(); |
| 120 | + |
| 121 | + |
| 122 | + if (_method == "miner_getstat1") |
| 123 | + { |
| 124 | + jRes["result"] = getMinerStat1(); |
| 125 | + } |
| 126 | + else if (_method == "miner_getstathr") |
| 127 | + { |
| 128 | + jRes["result"] = getMinerStatHR(); |
| 129 | + } |
| 130 | + else if (_method == "miner_shuffle") |
| 131 | + { |
| 132 | + |
| 133 | + // Gives nonce scrambler a new range |
| 134 | + cnote << "Miner Shuffle requested"; |
| 135 | + jRes["result"] = true; |
| 136 | + m_farm.shuffle(); |
| 137 | + |
12 | 138 | }
|
| 139 | + else if (_method == "miner_restart") |
| 140 | + { |
| 141 | + // Send response to client of success |
| 142 | + // and invoke an async restart |
| 143 | + // to prevent locking |
| 144 | + if (m_readonly.load(std::memory_order_relaxed)) |
| 145 | + { |
| 146 | + jRes["error"]["code"] = -32601; |
| 147 | + jRes["error"]["message"] = "Method not available"; |
| 148 | + } |
| 149 | + else |
| 150 | + { |
| 151 | + cnote << "Miner Restart requested"; |
| 152 | + jRes["result"] = true; |
| 153 | + m_farm.restart_async(); |
| 154 | + } |
| 155 | + |
| 156 | + } |
| 157 | + else if (_method == "miner_reboot") |
| 158 | + { |
| 159 | + |
| 160 | + // Not implemented yet |
| 161 | + jRes["error"]["code"] = -32601; |
| 162 | + jRes["error"]["message"] = "Method not implemented"; |
| 163 | + |
| 164 | + } |
| 165 | + else |
| 166 | + { |
| 167 | + |
| 168 | + // Any other method not found |
| 169 | + jRes["error"]["code"] = -32601; |
| 170 | + jRes["error"]["message"] = "Method not found"; |
| 171 | + } |
| 172 | + |
| 173 | + // Send response |
| 174 | + sendSocketData(jRes); |
| 175 | + |
13 | 176 | }
|
14 | 177 |
|
15 |
| -void ApiServer::getMinerStat1(const Json::Value& request, Json::Value& response) |
| 178 | +void ApiConnection::recvSocketData() |
| 179 | +{ |
| 180 | + // cnote << "ApiConnection::recvSocketData"; |
| 181 | + boost::asio::async_read_until(m_socket, m_recvBuffer, "\n", |
| 182 | + m_io_strand.wrap(boost::bind(&ApiConnection::onRecvSocketDataCompleted, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred))); |
| 183 | + |
| 184 | +} |
| 185 | + |
| 186 | +void ApiConnection::onRecvSocketDataCompleted(const boost::system::error_code& ec, std::size_t bytes_transferred) |
| 187 | +{ |
| 188 | + // cnote << "ApiConnection::onRecvSocketDataCompleted"; |
| 189 | + // Due to the nature of io_service's queue and |
| 190 | + // the implementation of the loop this event may trigger |
| 191 | + // late after clean disconnection. Check status of connection |
| 192 | + // before triggering all stack of calls |
| 193 | + |
| 194 | + if (!ec && bytes_transferred > 0) { |
| 195 | + |
| 196 | + // Extract received message |
| 197 | + std::istream is(&m_recvBuffer); |
| 198 | + std::string message; |
| 199 | + getline(is, message); |
| 200 | + |
| 201 | + if (m_socket.is_open()) { |
| 202 | + |
| 203 | + if (!message.empty()) { |
| 204 | + |
| 205 | + // Test validity of chunk and process |
| 206 | + Json::Value jMsg; |
| 207 | + Json::Reader jRdr; |
| 208 | + if (jRdr.parse(message, jMsg)) { |
| 209 | + processRequest(jMsg); |
| 210 | + } |
| 211 | + else { |
| 212 | + Json::Value jRes; |
| 213 | + jRes["jsonrpc"] = "2.0"; |
| 214 | + jRes["id"] = Json::nullValue; |
| 215 | + jRes["error"]["code"] = -32700; |
| 216 | + jRes["error"]["message"] = "Parse Error"; |
| 217 | + sendSocketData(jRes); |
| 218 | + } |
| 219 | + |
| 220 | + } |
| 221 | + |
| 222 | + // Eventually keep reading from socket |
| 223 | + recvSocketData(); |
| 224 | + |
| 225 | + } |
| 226 | + |
| 227 | + |
| 228 | + } |
| 229 | + else |
| 230 | + { |
| 231 | + if (m_socket.is_open()) { |
| 232 | + disconnect(); |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | +} |
| 237 | + |
| 238 | +void ApiConnection::sendSocketData(Json::Value const & jReq) { |
| 239 | + |
| 240 | + if (!m_socket.is_open()) |
| 241 | + return; |
| 242 | + |
| 243 | + std::ostream os(&m_sendBuffer); |
| 244 | + os << m_jWriter.write(jReq); // Do not add lf. It's added by writer. |
| 245 | + |
| 246 | + async_write(m_socket, m_sendBuffer, |
| 247 | + m_io_strand.wrap(boost::bind(&ApiConnection::onSendSocketDataCompleted, this, boost::asio::placeholders::error))); |
| 248 | + |
| 249 | +} |
| 250 | + |
| 251 | +void ApiConnection::onSendSocketDataCompleted(const boost::system::error_code& ec) { |
| 252 | + |
| 253 | + if (ec) disconnect(); |
| 254 | + |
| 255 | +} |
| 256 | + |
| 257 | +Json::Value ApiConnection::getMinerStat1() |
16 | 258 | {
|
17 |
| - (void) request; // unused |
18 | 259 |
|
19 | 260 | auto runningTime = std::chrono::duration_cast<std::chrono::minutes>(steady_clock::now() - this->m_farm.farmLaunched());
|
20 | 261 |
|
@@ -52,20 +293,23 @@ void ApiServer::getMinerStat1(const Json::Value& request, Json::Value& response)
|
52 | 293 | gpuIndex++;
|
53 | 294 | }
|
54 | 295 |
|
55 |
| - response[0] = ethminer_get_buildinfo()->project_version; //miner version. |
56 |
| - response[1] = toString(runningTime.count()); // running time, in minutes. |
57 |
| - response[2] = totalMhEth.str(); // total ETH hashrate in MH/s, number of ETH shares, number of ETH rejected shares. |
58 |
| - response[3] = detailedMhEth.str(); // detailed ETH hashrate for all GPUs. |
59 |
| - response[4] = totalMhDcr.str(); // total DCR hashrate in MH/s, number of DCR shares, number of DCR rejected shares. |
60 |
| - response[5] = detailedMhDcr.str(); // detailed DCR hashrate for all GPUs. |
61 |
| - response[6] = tempAndFans.str(); // Temperature and Fan speed(%) pairs for all GPUs. |
62 |
| - response[7] = poolAddresses.str(); // current mining pool. For dual mode, there will be two pools here. |
63 |
| - response[8] = invalidStats.str(); // number of ETH invalid shares, number of ETH pool switches, number of DCR invalid shares, number of DCR pool switches. |
| 296 | + Json::Value jRes; |
| 297 | + |
| 298 | + jRes[0] = ethminer_get_buildinfo()->project_version; //miner version. |
| 299 | + jRes[1] = toString(runningTime.count()); // running time, in minutes. |
| 300 | + jRes[2] = totalMhEth.str(); // total ETH hashrate in MH/s, number of ETH shares, number of ETH rejected shares. |
| 301 | + jRes[3] = detailedMhEth.str(); // detailed ETH hashrate for all GPUs. |
| 302 | + jRes[4] = totalMhDcr.str(); // total DCR hashrate in MH/s, number of DCR shares, number of DCR rejected shares. |
| 303 | + jRes[5] = detailedMhDcr.str(); // detailed DCR hashrate for all GPUs. |
| 304 | + jRes[6] = tempAndFans.str(); // Temperature and Fan speed(%) pairs for all GPUs. |
| 305 | + jRes[7] = poolAddresses.str(); // current mining pool. For dual mode, there will be two pools here. |
| 306 | + jRes[8] = invalidStats.str(); // number of ETH invalid shares, number of ETH pool switches, number of DCR invalid shares, number of DCR pool switches. |
| 307 | + |
| 308 | + return jRes; |
64 | 309 | }
|
65 | 310 |
|
66 |
| -void ApiServer::getMinerStatHR(const Json::Value& request, Json::Value& response) |
| 311 | +Json::Value ApiConnection::getMinerStatHR() |
67 | 312 | {
|
68 |
| - (void) request; // unused |
69 | 313 |
|
70 | 314 | //TODO:give key-value format
|
71 | 315 | auto runningTime = std::chrono::duration_cast<std::chrono::minutes>(steady_clock::now() - this->m_farm.farmLaunched());
|
@@ -103,34 +347,24 @@ void ApiServer::getMinerStatHR(const Json::Value& request, Json::Value& response
|
103 | 347 | gpuIndex++;
|
104 | 348 | }
|
105 | 349 |
|
106 |
| - response["version"] = version.str(); // miner version. |
107 |
| - response["runtime"] = runtime.str(); // running time, in minutes. |
| 350 | + Json::Value jRes; |
| 351 | + |
| 352 | + jRes["version"] = version.str(); // miner version. |
| 353 | + jRes["runtime"] = runtime.str(); // running time, in minutes. |
108 | 354 | // total ETH hashrate in MH/s, number of ETH shares, number of ETH rejected shares.
|
109 |
| - response["ethhashrate"] = (p.rate()); |
110 |
| - response["ethhashrates"] = detailedMhEth; |
111 |
| - response["ethshares"] = s.getAccepts(); |
112 |
| - response["ethrejected"] = s.getRejects(); |
113 |
| - response["ethinvalid"] = s.getFailures(); |
114 |
| - response["ethpoolsw"] = 0; |
| 355 | + jRes["ethhashrate"] = (p.rate()); |
| 356 | + jRes["ethhashrates"] = detailedMhEth; |
| 357 | + jRes["ethshares"] = s.getAccepts(); |
| 358 | + jRes["ethrejected"] = s.getRejects(); |
| 359 | + jRes["ethinvalid"] = s.getFailures(); |
| 360 | + jRes["ethpoolsw"] = 0; |
115 | 361 | // Hardware Info
|
116 |
| - response["temperatures"] = temps; // Temperatures(C) for all GPUs |
117 |
| - response["fanpercentages"] = fans; // Fans speed(%) for all GPUs |
118 |
| - response["powerusages"] = powers; // Power Usages(W) for all GPUs |
119 |
| - response["pooladdrs"] = poolAddresses.str(); // current mining pool. For dual mode, there will be two pools here. |
120 |
| -} |
| 362 | + jRes["temperatures"] = temps; // Temperatures(C) for all GPUs |
| 363 | + jRes["fanpercentages"] = fans; // Fans speed(%) for all GPUs |
| 364 | + jRes["powerusages"] = powers; // Power Usages(W) for all GPUs |
| 365 | + jRes["pooladdrs"] = poolAddresses.str(); // current mining pool. For dual mode, there will be two pools here. |
121 | 366 |
|
122 |
| -void ApiServer::doMinerRestart(const Json::Value& request, Json::Value& response) |
123 |
| -{ |
124 |
| - (void) request; // unused |
125 |
| - (void) response; // unused |
126 |
| - |
127 |
| - this->m_farm.restart(); |
128 |
| -} |
| 367 | + return jRes; |
129 | 368 |
|
130 |
| -void ApiServer::doMinerReboot(const Json::Value& request, Json::Value& response) |
131 |
| -{ |
132 |
| - (void) request; // unused |
133 |
| - (void) response; // unused |
134 |
| - |
135 |
| - // Not supported |
136 | 369 | }
|
| 370 | + |
0 commit comments