1 /*
2 +----------------------------------------------------------------------+
3 | Swoole |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 2.0 of the Apache license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | http://www.apache.org/licenses/LICENSE-2.0.html |
9 | If you did not receive a copy of the Apache2.0 license and are unable|
10 | to obtain it through the world-wide-web, please send a note to |
11 | license@swoole.com so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Tianfeng Han <mikan.tenny@gmail.com> |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php_swoole_server.h"
18 #include "swoole_process_pool.h"
19 #include "php_swoole_http.h"
20 #include "php_swoole_x_arginfo.h"
21
22 #include <sstream>
23 #include <thread>
24
25 #include "nlohmann/json.hpp"
26
27 using json = nlohmann::json;
28
29 namespace swoole {
30
31 #ifdef TCP_INFO
32 static json get_socket_info(int fd);
33 #endif
34
handle_get_all_unix_sockets(Server * _server,const std::string & msg)35 static std::string handle_get_all_unix_sockets(Server *_server, const std::string &msg) {
36 auto _result = json::parse(msg);
37 if (!_result.is_object() || _result.find("type") == _result.end()) {
38 json return_value{
39 {"data", "require parameter type"},
40 {"code", 4003},
41 };
42 return return_value.dump();
43 }
44
45 std::string _type = _result["type"];
46 Worker *workers;
47 uint32_t worker_num;
48
49 if (_type == "event") {
50 workers = _server->gs->event_workers.workers;
51 worker_num = _server->worker_num;
52 } else {
53 workers = _server->gs->task_workers.workers;
54 worker_num = _server->task_worker_num;
55 }
56
57 json sockets = json::array();
58
59 SW_LOOP_N(worker_num) {
60 auto master_socket = workers[i].pipe_object->get_socket(true);
61 json master_socket_info = json::object({
62 {"fd", master_socket->fd},
63 {"events", master_socket->events},
64 {"total_recv_bytes", master_socket->total_recv_bytes},
65 {"total_send_bytes", master_socket->total_send_bytes},
66 {"out_buffer_size", master_socket->out_buffer ? master_socket->out_buffer->length() : 0},
67 });
68 sockets.push_back(master_socket_info);
69
70 auto worker_socket = workers[i].pipe_object->get_socket(false);
71 json worker_socket_info = json::object({
72 {"fd", worker_socket->fd},
73 {"events", worker_socket->events},
74 {"total_recv_bytes", worker_socket->total_recv_bytes},
75 {"total_send_bytes", worker_socket->total_send_bytes},
76 {"out_buffer_size", worker_socket->out_buffer ? worker_socket->out_buffer->length() : 0},
77 });
78 sockets.push_back(worker_socket_info);
79 }
80
81 json return_value{
82 {"data", sockets},
83 {"code", 0},
84 };
85 return return_value.dump();
86 }
87
handle_get_all_sockets(Server *,const std::string & msg)88 static std::string handle_get_all_sockets(Server *, const std::string &msg) {
89 if (sw_reactor() == nullptr) {
90 json return_value{
91 {"data", "No event loop created"},
92 {"code", 4004},
93 };
94 return return_value.dump();
95 }
96
97 json sockets = json::array();
98 sw_reactor()->foreach_socket([&sockets](int fd, network::Socket *socket) {
99 network::Address addr{};
100 if (socket->socket_type > SW_SOCK_UNIX_DGRAM || socket->socket_type < SW_SOCK_TCP) {
101 #ifdef SO_DOMAIN
102 struct stat fdstat;
103 if (fstat(fd, &fdstat) == -1) {
104 return;
105 }
106 mode_t type = fdstat.st_mode & S_IFMT;
107 if (type == S_IFSOCK) {
108 int domain;
109 if (socket->get_option(SOL_SOCKET, SO_DOMAIN, &domain) < 0) {
110 return;
111 }
112 int type;
113 if (socket->get_option(SOL_SOCKET, SO_TYPE, &type) < 0) {
114 return;
115 }
116 addr.type = network::Socket::convert_to_type(domain, type);
117 socket->get_name(&addr);
118 }
119 #else
120 return;
121 #endif
122 } else {
123 addr = socket->info;
124 }
125 json info = json::object({
126 {"fd", socket->fd},
127 {"address", addr.get_ip()},
128 {"port", addr.get_port()},
129 {"events", socket->events},
130 {"socket_type", socket->socket_type},
131 {"fd_type", socket->fd_type},
132 {"total_recv_bytes", socket->total_recv_bytes},
133 {"total_send_bytes", socket->total_send_bytes},
134 {"out_buffer_size", socket->out_buffer ? socket->out_buffer->length() : 0},
135 });
136 sockets.push_back(info);
137 });
138
139 json return_value{
140 {"data", sockets},
141 {"code", 0},
142 };
143 return return_value.dump();
144 }
145
handle_get_all_commands(Server * serv,const std::string & msg)146 static std::string handle_get_all_commands(Server *serv, const std::string &msg) {
147 json command_list = json::array();
148 for (auto kv : serv->commands) {
149 json info = json::object({
150 {"id", kv.second.id},
151 {"name", kv.second.name},
152 {"accepted_process_types", kv.second.accepted_process_types},
153 });
154 command_list.push_back(info);
155 };
156 json return_value{
157 {"data", command_list},
158 {"code", 0},
159 };
160 return return_value.dump();
161 }
162
163 #ifdef TCP_INFO
get_socket_info(int fd)164 static json get_socket_info(int fd) {
165 struct tcp_info info;
166 socklen_t len = sizeof(info);
167 if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len) < 0) {
168 json return_value{
169 {"data", "failed to getsockopt(TCP_INFO) for socket"},
170 {"code", 5001},
171 };
172 return return_value.dump();
173 }
174 #if defined(__FreeBSD__)
175 json jinfo{
176 {"state", info.tcpi_state},
177 {"ca_state", info.__tcpi_ca_state},
178 {"retransmits", info.__tcpi_retransmits},
179 {"probes", info.__tcpi_probes},
180 {"backoff", info.__tcpi_backoff},
181 {"options", info.tcpi_options},
182 {"snd_wscale", uint8_t(info.tcpi_snd_wscale)},
183 {"rcv_wscale", uint8_t(info.tcpi_rcv_wscale)},
184 {"rto", info.tcpi_rto},
185 {"ato", info.__tcpi_ato},
186 {"snd_mss", info.tcpi_snd_mss},
187 {"rcv_mss", info.tcpi_rcv_mss},
188 {"unacked", info.__tcpi_unacked},
189 {"sacked", info.__tcpi_sacked},
190 {"lost", info.__tcpi_lost},
191 {"retrans", info.__tcpi_retrans},
192 {"fackets", info.__tcpi_fackets},
193 {"last_data_sent", info.__tcpi_last_data_sent},
194 {"last_ack_sent", info.__tcpi_last_ack_sent},
195 {"last_data_recv", info.tcpi_last_data_recv},
196 {"last_ack_recv", info.__tcpi_last_ack_recv},
197 {"pmtu", info.__tcpi_pmtu},
198 {"rcv_ssthresh", info.__tcpi_rcv_ssthresh},
199 {"rtt", info.tcpi_rtt},
200 {"rttvar", info.tcpi_rttvar},
201 {"snd_ssthresh", info.tcpi_snd_ssthresh},
202 {"snd_cwnd", info.tcpi_snd_cwnd},
203 {"advmss", info.__tcpi_advmss},
204 {"reordering", info.__tcpi_reordering},
205 {"rcv_rtt", info.__tcpi_rcv_rtt},
206 {"rcv_space", info.tcpi_rcv_space},
207 {"total_retrans", 0},
208 };
209 #else
210 json jinfo{
211 {"state", info.tcpi_state},
212 {"ca_state", info.tcpi_ca_state},
213 {"retransmits", info.tcpi_retransmits},
214 {"probes", info.tcpi_probes},
215 {"backoff", info.tcpi_backoff},
216 {"options", info.tcpi_options},
217 {"snd_wscale", uint8_t(info.tcpi_snd_wscale)},
218 {"rcv_wscale", uint8_t(info.tcpi_rcv_wscale)},
219 {"rto", info.tcpi_rto},
220 {"ato", info.tcpi_ato},
221 {"snd_mss", info.tcpi_snd_mss},
222 {"rcv_mss", info.tcpi_rcv_mss},
223 {"unacked", info.tcpi_unacked},
224 {"sacked", info.tcpi_sacked},
225 {"lost", info.tcpi_lost},
226 {"retrans", info.tcpi_retrans},
227 {"fackets", info.tcpi_fackets},
228 {"last_data_sent", info.tcpi_last_data_sent},
229 {"last_ack_sent", info.tcpi_last_ack_sent},
230 {"last_data_recv", info.tcpi_last_data_recv},
231 {"last_ack_recv", info.tcpi_last_ack_recv},
232 {"pmtu", info.tcpi_pmtu},
233 {"rcv_ssthresh", info.tcpi_rcv_ssthresh},
234 {"rtt", info.tcpi_rtt},
235 {"rttvar", info.tcpi_rttvar},
236 {"snd_ssthresh", info.tcpi_snd_ssthresh},
237 {"snd_cwnd", info.tcpi_snd_cwnd},
238 {"advmss", info.tcpi_advmss},
239 {"reordering", info.tcpi_reordering},
240 {"rcv_rtt", info.tcpi_rcv_rtt},
241 {"rcv_space", info.tcpi_rcv_space},
242 {"total_retrans", info.tcpi_total_retrans},
243 };
244 #endif
245 return jinfo;
246 }
247 #endif
248
get_connection_info(Server * serv,Connection * conn)249 static json get_connection_info(Server *serv, Connection *conn) {
250 auto server_socket = serv->get_port_by_server_fd(conn->server_fd)->socket;
251 json info = json::object({
252 {"session_id", conn->session_id},
253 {"reactor_id", conn->reactor_id},
254 {"fd", conn->fd},
255 {"server_port",
256 std::string(server_socket->info.get_ip()) + ":" + std::to_string(server_socket->info.get_port())},
257 {"address", conn->info.get_ip()},
258 {"port", conn->info.get_port()},
259 {"overflow", conn->overflow},
260 {"connect_time", conn->connect_time},
261 {"last_recv_time", conn->last_recv_time},
262 {"last_send_time", conn->last_send_time},
263 {"last_dispatch_time", conn->last_dispatch_time},
264 {"recv_queued_bytes", conn->recv_queued_bytes},
265 {"send_queued_bytes", conn->send_queued_bytes},
266 {"total_recv_bytes", conn->socket->total_recv_bytes},
267 {"total_send_bytes", conn->socket->total_send_bytes},
268 {"uid", conn->uid},
269 });
270 return info;
271 }
272
handle_get_socket_info(Server * serv,const std::string & msg)273 static std::string handle_get_socket_info(Server *serv, const std::string &msg) {
274 auto _result = json::parse(msg);
275 if (!_result.is_object() || _result.find("fd") == _result.end()) {
276 json return_value{
277 {"data", "require parameter fd"},
278 {"code", 4003},
279 };
280 return return_value.dump();
281 }
282
283 #ifndef TCP_INFO
284 json return_value{
285 {"data", "platform unsupported"},
286 {"code", 5001},
287 };
288 #else
289 std::string _fd = _result["fd"];
290 int fd = std::atoi(_fd.c_str());
291 json return_value{
292 {"data", get_socket_info(fd)},
293 {"code", 0},
294 };
295 #endif
296 return return_value.dump();
297 }
298
handle_get_thread_info(Server * serv,const std::string & msg)299 static std::string handle_get_thread_info(Server *serv, const std::string &msg) {
300 ReactorThread *thread = serv->get_thread(SwooleTG.id);
301 std::stringstream ss;
302 ss << std::this_thread::get_id();
303 json jinfo{
304 {"tid", ss.str()},
305 {"id", thread->id},
306 {"dispatch_count", thread->dispatch_count},
307 {"event_num", SwooleTG.reactor->get_event_num()},
308 {"timer_num", SwooleTG.timer ? SwooleTG.timer->count() : 0},
309 };
310 json return_value{
311 {"data", jinfo},
312 {"code", 0},
313 };
314 return return_value.dump();
315 }
316
handle_get_manager_info(Server * serv,const std::string & msg)317 static std::string handle_get_manager_info(Server *serv, const std::string &msg) {
318 ProcessPool *pool = (ProcessPool *) &serv->gs->event_workers;
319 json jinfo{
320 {"pid", getpid()},
321 {"reload_count", pool->reload_count},
322 {"reload_last_time", pool->reload_last_time},
323 };
324 json return_value{
325 {"data", jinfo},
326 {"code", 0},
327 };
328 return return_value.dump();
329 }
330
get_socket_out_buffer_total_size()331 static size_t get_socket_out_buffer_total_size() {
332 if (!sw_reactor()) {
333 return 0;
334 }
335 size_t size = 0;
336 for (auto s : sw_reactor()->get_sockets()) {
337 if (s.second->out_buffer) {
338 size += s.second->out_buffer->length();
339 }
340 }
341 return size;
342 }
343
handle_get_memory_info(Server * serv,const std::string & msg)344 static std::string handle_get_memory_info(Server *serv, const std::string &msg) {
345 bool is_thread = serv->is_reactor_thread();
346
347 json jinfo{
348 {"server", sizeof(Server)},
349 {"workers", serv->get_all_worker_num() * sizeof(Worker)},
350 {"connection_list", serv->get_max_connection() * sizeof(Connection)},
351 {"session_list", SW_SESSION_LIST_SIZE * sizeof(Session)},
352 {"global_memory", dynamic_cast<GlobalMemory *>(sw_mem_pool())->get_memory_size()},
353 {"thread_global_memory", sw_tg_buffer()->size},
354 {"message_bus",
355 is_thread ? serv->get_thread(SwooleTG.id)->message_bus.get_memory_size()
356 : serv->message_bus.get_memory_size()},
357 {"socket_list", sw_reactor() ? sw_reactor()->get_sockets().size() * sizeof(network::Socket) : 0},
358 {"socket_out_buffer", get_socket_out_buffer_total_size()},
359 {"php_memory", is_thread ? 0 : zend_memory_usage(true)},
360 {"http_buffer", swoole_http_buffer ? swoole_http_buffer->size : 0},
361 {"http_form_data_buffer", swoole_http_form_data_buffer ? swoole_http_form_data_buffer->size : 0},
362 #ifdef SW_HAVE_COMPRESSION
363 {"zlib_buffer", swoole_zlib_buffer ? swoole_zlib_buffer->size : 0},
364 #else
365 {"zlib_buffer", 0},
366 #endif
367 };
368 json return_value{
369 {"data", jinfo},
370 {"code", 0},
371 };
372 return return_value.dump();
373 }
374
handle_get_connections(Server * serv,const std::string & msg)375 static std::string handle_get_connections(Server *serv, const std::string &msg) {
376 json list = json::array();
377 serv->foreach_connection([serv, &list](Connection *conn) {
378 if (serv->is_process_mode() && conn->reactor_id != SwooleTG.id) {
379 return;
380 }
381 if (serv->is_base_mode() && SwooleWG.worker && conn->reactor_id != SwooleWG.worker->id) {
382 return;
383 }
384 list.push_back(get_connection_info(serv, conn));
385 });
386 json return_value{
387 {"data", list},
388 {"code", 0},
389 };
390 return return_value.dump();
391 }
392
handle_get_connection_info(Server * serv,const std::string & msg)393 static std::string handle_get_connection_info(Server *serv, const std::string &msg) {
394 auto _result = json::parse(msg);
395 if (!_result.is_object() || _result.find("session_id") == _result.end()) {
396 json return_value{
397 {"data", "require parameter session_id"},
398 {"code", 4003},
399 };
400 return return_value.dump();
401 }
402
403 std::string _session_id = _result["session_id"];
404 int session_id = std::atoi(_session_id.c_str());
405 Connection *conn = serv->get_connection_verify(session_id);
406 if (!conn) {
407 json return_value{
408 {"data", "connection not exists"},
409 {"code", 4004},
410 };
411 return return_value.dump();
412 }
413
414 json return_value{
415 {"data", get_connection_info(serv, conn)},
416 {"code", 0},
417 };
418 return return_value.dump();
419 }
420
handle_get_all_ports(Server * serv,const std::string & msg)421 static std::string handle_get_all_ports(Server *serv, const std::string &msg) {
422 json _list = json::array();
423 for (auto port : serv->ports) {
424 json info = json::object({
425 {"host", port->host},
426 {"port", port->port},
427 {"backlog", port->backlog},
428 {"type", port->type},
429 {"ssl", port->ssl},
430 {"protocols", port->get_protocols()},
431 {"connection_num", (long) port->gs->connection_num},
432 });
433 _list.push_back(info);
434 };
435 json return_value{
436 {"data", _list},
437 {"code", 0},
438 };
439 return return_value.dump();
440 }
441
register_admin_server_commands(Server * serv)442 void register_admin_server_commands(Server *serv) {
443 serv->add_command("get_all_sockets", Server::Command::ALL_PROCESS, handle_get_all_sockets);
444 serv->add_command("get_all_commands", Server::Command::ALL_PROCESS, handle_get_all_commands);
445 serv->add_command("get_socket_info", Server::Command::ALL_PROCESS, handle_get_socket_info);
446 serv->add_command("get_thread_info", Server::Command::ALL_PROCESS, handle_get_thread_info);
447 serv->add_command("get_manager_info", Server::Command::MANAGER, handle_get_manager_info);
448 serv->add_command("get_thread_info", Server::Command::ALL_PROCESS, handle_get_thread_info);
449 serv->add_command("get_memory_info", Server::Command::ALL_PROCESS, handle_get_memory_info);
450 serv->add_command("get_all_unix_sockets", Server::Command::ALL_PROCESS, handle_get_all_unix_sockets);
451 serv->add_command("get_all_ports", Server::Command::MASTER, handle_get_all_ports);
452
453 int accepted_process_types;
454 if (serv->is_base_mode() || serv->single_thread) {
455 accepted_process_types = Server::Command::EVENT_WORKER | Server::Command::MASTER;
456 } else {
457 accepted_process_types = Server::Command::REACTOR_THREAD;
458 }
459 serv->add_command("get_connections", accepted_process_types, handle_get_connections);
460 serv->add_command("get_connection_info", accepted_process_types, handle_get_connection_info);
461 }
462 } // namespace swoole
463
464 typedef std::function<void(zend_object *obj)> objects_store_iterator;
465
object_valid(zend_object * obj)466 static inline bool object_valid(zend_object *obj) {
467 return obj && IS_OBJ_VALID(obj) && obj->handlers && obj->handlers->get_class_name;
468 }
469
objects_store_foreach(const objects_store_iterator & fn)470 static void objects_store_foreach(const objects_store_iterator &fn) {
471 for (uint32_t i = 0; i < EG(objects_store).top; i++) {
472 zend_object *obj = EG(objects_store).object_buckets[i];
473 if (object_valid(obj)) {
474 fn(obj);
475 }
476 }
477 }
478
object_store_count()479 static uint32_t object_store_count() {
480 uint32_t count = 0;
481 objects_store_foreach([&count](zend_object *obj) { count++; });
482 return count;
483 }
484
ZEND_FUNCTION(swoole_get_vm_status)485 ZEND_FUNCTION(swoole_get_vm_status) {
486 array_init(return_value);
487 add_assoc_long_ex(return_value, ZEND_STRL("object_num"), object_store_count());
488 add_assoc_long_ex(return_value, ZEND_STRL("resource_num"), zend_array_count(&EG(regular_list)));
489 }
490
ZEND_FUNCTION(swoole_get_objects)491 ZEND_FUNCTION(swoole_get_objects) {
492 zend_objects_store *objects = &EG(objects_store);
493 if (objects->top <= 1) {
494 RETURN_FALSE;
495 }
496
497 array_init(return_value);
498 objects_store_foreach([return_value](zend_object *obj) {
499 zval zobject;
500 ZVAL_OBJ(&zobject, obj);
501 zval_addref_p(&zobject);
502 add_next_index_zval(return_value, &zobject);
503 });
504 }
505
ZEND_FUNCTION(swoole_get_object_by_handle)506 ZEND_FUNCTION(swoole_get_object_by_handle) {
507 zend_long handle;
508 ZEND_PARSE_PARAMETERS_START(1, 1)
509 Z_PARAM_LONG(handle)
510 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
511
512 zend_objects_store *objects = &EG(objects_store);
513 if (objects->top <= 1 || handle >= objects->top) {
514 RETURN_FALSE;
515 }
516
517 zend_object *obj = objects->object_buckets[handle];
518 if (!object_valid(obj)) {
519 RETURN_FALSE;
520 }
521 GC_ADDREF(obj);
522 RETURN_OBJ(obj);
523 }
524