2018-08-19 04:43:58 +00:00
|
|
|
#include <Common/TaskStatsInfoGetter.h>
|
2018-05-17 16:01:41 +00:00
|
|
|
#include <Common/Exception.h>
|
2018-08-19 04:43:58 +00:00
|
|
|
#include <Core/Types.h>
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 05:56:06 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#if defined(__linux__)
|
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
#include <common/unaligned.h>
|
|
|
|
|
2018-05-17 16:01:41 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <syscall.h>
|
2018-08-22 05:56:06 +00:00
|
|
|
#include <linux/genetlink.h>
|
|
|
|
#include <linux/netlink.h>
|
|
|
|
#include <linux/taskstats.h>
|
|
|
|
#include <linux/capability.h>
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-21 21:05:30 +00:00
|
|
|
|
2018-08-19 04:43:58 +00:00
|
|
|
/// Basic idea is motivated by "iotop" tool.
|
2018-06-04 14:16:27 +00:00
|
|
|
/// More info: https://www.kernel.org/doc/Documentation/accounting/taskstats.txt
|
2018-05-17 16:01:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int NETLINK_ERROR;
|
2018-08-22 00:41:30 +00:00
|
|
|
extern const int LOGICAL_ERROR;
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
/** The message contains:
|
|
|
|
* - Netlink protocol header;
|
|
|
|
* - Generic Netlink (is a sub-protocol of Netlink that we use) protocol header;
|
|
|
|
* - Payload
|
|
|
|
* -- that itself is a list of "Attributes" (sub-messages), each of them contains length (including header), type, and its own payload.
|
|
|
|
* -- and attribute payload may be represented by the list of embedded attributes.
|
|
|
|
*/
|
2018-06-04 14:16:27 +00:00
|
|
|
struct NetlinkMessage
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 03:11:46 +00:00
|
|
|
static size_t constexpr MAX_MSG_SIZE = 1024;
|
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
alignas(NLMSG_ALIGNTO) ::nlmsghdr header;
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
struct Attribute
|
|
|
|
{
|
|
|
|
::nlattr header;
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
alignas(NLMSG_ALIGNTO) char payload[0];
|
|
|
|
|
|
|
|
const Attribute * next() const
|
|
|
|
{
|
|
|
|
return reinterpret_cast<const Attribute *>(reinterpret_cast<const char *>(this) + NLA_ALIGN(header.nla_len));
|
|
|
|
}
|
|
|
|
};
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 03:12:55 +00:00
|
|
|
union alignas(NLMSG_ALIGNTO)
|
2018-08-22 02:54:24 +00:00
|
|
|
{
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
::genlmsghdr generic_header;
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 03:12:55 +00:00
|
|
|
union alignas(NLMSG_ALIGNTO)
|
2018-08-22 02:54:24 +00:00
|
|
|
{
|
|
|
|
char buf[MAX_MSG_SIZE];
|
|
|
|
Attribute attribute; /// First attribute. There may be more.
|
|
|
|
} payload;
|
|
|
|
};
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
::nlmsgerr error;
|
|
|
|
};
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
size_t payload_size() const
|
|
|
|
{
|
|
|
|
return header.nlmsg_len - sizeof(header) - sizeof(generic_header);
|
|
|
|
}
|
2018-06-04 14:16:27 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
const Attribute * end() const
|
|
|
|
{
|
|
|
|
return reinterpret_cast<const Attribute *>(reinterpret_cast<const char *>(this) + header.nlmsg_len);
|
|
|
|
}
|
2018-06-04 14:16:27 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
void send(int fd) const
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
const char * request_buf = reinterpret_cast<const char *>(this);
|
|
|
|
ssize_t request_size = header.nlmsg_len;
|
2018-06-04 14:16:27 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
::sockaddr_nl nladdr{};
|
|
|
|
nladdr.nl_family = AF_NETLINK;
|
2018-06-04 14:16:27 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
while (true)
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
ssize_t bytes_sent = ::sendto(fd, request_buf, request_size, 0, reinterpret_cast<const ::sockaddr *>(&nladdr), sizeof(nladdr));
|
|
|
|
|
|
|
|
if (bytes_sent <= 0)
|
|
|
|
{
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
throwFromErrno("Can't send a Netlink command", ErrorCodes::NETLINK_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bytes_sent > request_size)
|
|
|
|
throw Exception("Wrong result of sendto system call: bytes_sent is greater than request size", ErrorCodes::NETLINK_ERROR);
|
|
|
|
|
|
|
|
if (bytes_sent == request_size)
|
|
|
|
break;
|
|
|
|
|
|
|
|
request_buf += bytes_sent;
|
|
|
|
request_size -= bytes_sent;
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-22 02:54:24 +00:00
|
|
|
|
|
|
|
void receive(int fd)
|
|
|
|
{
|
|
|
|
ssize_t bytes_received = ::recv(fd, this, sizeof(*this), 0);
|
|
|
|
|
2018-08-29 21:30:39 +00:00
|
|
|
if (header.nlmsg_type == NLMSG_ERROR)
|
|
|
|
throw Exception("Can't receive Netlink response: error " + std::to_string(error.error), ErrorCodes::NETLINK_ERROR);
|
|
|
|
|
|
|
|
if (!NLMSG_OK((&header), bytes_received))
|
|
|
|
throw Exception("Can't receive Netlink response: wrong number of bytes received", ErrorCodes::NETLINK_ERROR);
|
2018-08-22 02:54:24 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
NetlinkMessage query(
|
|
|
|
int fd,
|
|
|
|
UInt16 type,
|
|
|
|
UInt32 pid,
|
|
|
|
UInt8 command,
|
|
|
|
UInt16 attribute_type,
|
|
|
|
const void * attribute_data,
|
|
|
|
int attribute_size)
|
|
|
|
{
|
|
|
|
NetlinkMessage request;
|
|
|
|
|
|
|
|
request.header.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); /// Length of both headers.
|
|
|
|
request.header.nlmsg_type = type;
|
|
|
|
request.header.nlmsg_flags = NLM_F_REQUEST; /// A request.
|
|
|
|
request.header.nlmsg_seq = 0;
|
|
|
|
request.header.nlmsg_pid = pid;
|
|
|
|
|
|
|
|
request.generic_header.cmd = command;
|
|
|
|
request.generic_header.version = 1;
|
|
|
|
|
|
|
|
request.payload.attribute.header.nla_type = attribute_type;
|
|
|
|
request.payload.attribute.header.nla_len = attribute_size + 1 + NLA_HDRLEN;
|
|
|
|
|
|
|
|
memcpy(&request.payload.attribute.payload, attribute_data, attribute_size);
|
|
|
|
|
|
|
|
request.header.nlmsg_len += NLMSG_ALIGN(request.payload.attribute.header.nla_len);
|
|
|
|
|
|
|
|
request.send(fd);
|
|
|
|
|
|
|
|
NetlinkMessage response;
|
|
|
|
response.receive(fd);
|
|
|
|
|
|
|
|
return response;
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
UInt16 getFamilyIdImpl(int fd)
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
NetlinkMessage answer = query(fd, GENL_ID_CTRL, getpid(), CTRL_CMD_GETFAMILY, CTRL_ATTR_FAMILY_NAME, TASKSTATS_GENL_NAME, strlen(TASKSTATS_GENL_NAME) + 1);
|
|
|
|
|
|
|
|
/// NOTE Why the relevant info is located in the second attribute?
|
|
|
|
const NetlinkMessage::Attribute * attr = answer.payload.attribute.next();
|
|
|
|
|
|
|
|
if (attr->header.nla_type != CTRL_ATTR_FAMILY_ID)
|
|
|
|
throw Exception("Received wrong attribute as an answer to GET_FAMILY Netlink command", ErrorCodes::NETLINK_ERROR);
|
|
|
|
|
|
|
|
return unalignedLoad<UInt16>(attr->payload);
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
|
|
|
|
2018-08-22 00:24:55 +00:00
|
|
|
|
|
|
|
bool checkPermissionsImpl()
|
|
|
|
{
|
|
|
|
/// See man getcap.
|
|
|
|
__user_cap_header_struct request{};
|
|
|
|
request.version = _LINUX_CAPABILITY_VERSION_1; /// It's enough to check just single CAP_NET_ADMIN capability we are interested.
|
|
|
|
request.pid = getpid();
|
|
|
|
|
|
|
|
__user_cap_data_struct response{};
|
|
|
|
|
|
|
|
/// Avoid dependency on 'libcap'.
|
|
|
|
if (0 != syscall(SYS_capget, &request, &response))
|
|
|
|
throwFromErrno("Cannot do 'capget' syscall", ErrorCodes::NETLINK_ERROR);
|
|
|
|
|
2018-08-29 21:30:39 +00:00
|
|
|
if (!((1 << CAP_NET_ADMIN) & response.effective))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/// Check that we can successfully initialize TaskStatsInfoGetter.
|
|
|
|
/// It will ask about family id through Netlink.
|
|
|
|
/// On some LXC containers we have capability but we still cannot use Netlink.
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
TaskStatsInfoGetter();
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
tryLogCurrentException(__PRETTY_FUNCTION__);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2018-06-14 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
UInt16 getFamilyId(int fd)
|
2018-06-14 14:29:42 +00:00
|
|
|
{
|
2018-08-22 00:24:55 +00:00
|
|
|
/// It is thread and exception safe since C++11 and even before.
|
2018-08-22 02:54:24 +00:00
|
|
|
static UInt16 res = getFamilyIdImpl(fd);
|
2018-08-22 00:24:55 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool TaskStatsInfoGetter::checkPermissions()
|
|
|
|
{
|
|
|
|
static bool res = checkPermissionsImpl();
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TaskStatsInfoGetter::TaskStatsInfoGetter()
|
|
|
|
{
|
2018-06-14 14:29:42 +00:00
|
|
|
netlink_socket_fd = ::socket(PF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
|
|
|
|
if (netlink_socket_fd < 0)
|
2018-08-21 16:49:20 +00:00
|
|
|
throwFromErrno("Can't create PF_NETLINK socket", ErrorCodes::NETLINK_ERROR);
|
2018-08-21 16:40:54 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
/// On some containerized environments, operation on Netlink socket could hang forever.
|
|
|
|
/// We set reasonably small timeout to overcome this issue.
|
|
|
|
|
2018-08-21 21:05:30 +00:00
|
|
|
struct timeval tv;
|
|
|
|
tv.tv_sec = 0;
|
|
|
|
tv.tv_usec = 50000;
|
|
|
|
|
2018-08-21 16:13:07 +00:00
|
|
|
if (0 != ::setsockopt(netlink_socket_fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char *>(&tv), sizeof(tv)))
|
2018-08-21 16:49:20 +00:00
|
|
|
throwFromErrno("Can't set timeout on PF_NETLINK socket", ErrorCodes::NETLINK_ERROR);
|
2018-06-14 14:29:42 +00:00
|
|
|
|
2018-08-19 04:43:58 +00:00
|
|
|
::sockaddr_nl addr{};
|
2018-06-14 14:29:42 +00:00
|
|
|
addr.nl_family = AF_NETLINK;
|
|
|
|
|
2018-08-19 04:43:58 +00:00
|
|
|
if (::bind(netlink_socket_fd, reinterpret_cast<const ::sockaddr *>(&addr), sizeof(addr)) < 0)
|
2018-08-21 16:49:20 +00:00
|
|
|
throwFromErrno("Can't bind PF_NETLINK socket", ErrorCodes::NETLINK_ERROR);
|
2018-06-14 14:29:42 +00:00
|
|
|
|
2018-08-22 00:24:55 +00:00
|
|
|
taskstats_family_id = getFamilyId(netlink_socket_fd);
|
2018-06-14 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-08-21 21:05:30 +00:00
|
|
|
|
2018-08-22 00:05:06 +00:00
|
|
|
void TaskStatsInfoGetter::getStat(::taskstats & out_stats, pid_t tid)
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
NetlinkMessage answer = query(netlink_socket_fd, taskstats_family_id, tid, TASKSTATS_CMD_GET, TASKSTATS_CMD_ATTR_PID, &tid, sizeof(tid));
|
2018-05-17 16:01:41 +00:00
|
|
|
|
2018-08-22 02:54:24 +00:00
|
|
|
for (const NetlinkMessage::Attribute * attr = &answer.payload.attribute;
|
|
|
|
attr < answer.end();
|
|
|
|
attr = attr->next())
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
if (attr->header.nla_type == TASKSTATS_TYPE_AGGR_TGID || attr->header.nla_type == TASKSTATS_TYPE_AGGR_PID)
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
for (const NetlinkMessage::Attribute * nested_attr = reinterpret_cast<const NetlinkMessage::Attribute *>(attr->payload);
|
|
|
|
nested_attr < attr->next();
|
|
|
|
nested_attr = nested_attr->next())
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
if (nested_attr->header.nla_type == TASKSTATS_TYPE_STATS)
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-22 02:54:24 +00:00
|
|
|
out_stats = unalignedLoad<::taskstats>(nested_attr->payload);
|
|
|
|
return;
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-22 02:54:24 +00:00
|
|
|
|
|
|
|
throw Exception("There is no TASKSTATS_TYPE_STATS attribute in the Netlink response", ErrorCodes::NETLINK_ERROR);
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
|
|
|
|
2018-08-21 21:05:30 +00:00
|
|
|
|
2018-08-22 00:05:06 +00:00
|
|
|
pid_t TaskStatsInfoGetter::getCurrentTID()
|
2018-05-17 16:01:41 +00:00
|
|
|
{
|
2018-08-17 18:57:07 +00:00
|
|
|
/// This call is always successful. - man gettid
|
2018-08-22 00:05:06 +00:00
|
|
|
return static_cast<pid_t>(syscall(SYS_gettid));
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
|
|
|
|
2018-08-21 23:52:03 +00:00
|
|
|
|
2018-08-21 21:05:30 +00:00
|
|
|
TaskStatsInfoGetter::~TaskStatsInfoGetter()
|
|
|
|
{
|
|
|
|
if (netlink_socket_fd >= 0)
|
|
|
|
close(netlink_socket_fd);
|
|
|
|
}
|
|
|
|
|
2018-05-17 16:01:41 +00:00
|
|
|
}
|
2018-08-22 05:56:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int NOT_IMPLEMENTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TaskStatsInfoGetter::checkPermissions()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TaskStatsInfoGetter::TaskStatsInfoGetter()
|
|
|
|
{
|
|
|
|
throw Exception("TaskStats are not implemented for this OS.", ErrorCodes::NOT_IMPLEMENTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TaskStatsInfoGetter::getStat(::taskstats &, pid_t)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
pid_t TaskStatsInfoGetter::getCurrentTID()
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
TaskStatsInfoGetter::~TaskStatsInfoGetter()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|