diff options
Diffstat (limited to 'libs/common_time/diag_thread.cpp')
-rw-r--r-- | libs/common_time/diag_thread.cpp | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/libs/common_time/diag_thread.cpp b/libs/common_time/diag_thread.cpp new file mode 100644 index 0000000..4cb9551 --- /dev/null +++ b/libs/common_time/diag_thread.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed 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. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <fcntl.h> +#include <linux/in.h> +#include <linux/tcp.h> +#include <poll.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <utils/Errors.h> +#include <utils/misc.h> + +#include <common_time/local_clock.h> + +#include "common_clock.h" +#include "diag_thread.h" + +#define kMaxEvents 16 +#define kListenPort 9876 + +static bool setNonblocking(int fd) { + int flags = fcntl(fd, F_GETFL); + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)", + fd, errno); + return false; + } + + return true; +} + +static bool setNodelay(int fd) { + int tmp = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) { + ALOGE("Failed to set socket (%d) to no-delay mode (errno %d)", + fd, errno); + return false; + } + + return true; +} + +namespace android { + +DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) { + common_clock_ = common_clock; + local_clock_ = local_clock; + listen_fd_ = -1; + data_fd_ = -1; + kernel_logID_basis_known_ = false; + discipline_log_ID_ = 0; +} + +DiagThread::~DiagThread() { +} + +status_t DiagThread::startWorkThread() { + status_t res; + stopWorkThread(); + res = run("Diag"); + + if (res != OK) + ALOGE("Failed to start work thread (res = %d)", res); + + return res; +} + +void DiagThread::stopWorkThread() { + status_t res; + res = requestExitAndWait(); // block until thread exit. + if (res != OK) + ALOGE("Failed to stop work thread (res = %d)", res); +} + +bool DiagThread::openListenSocket() { + bool ret = false; + int flags; + cleanupListenSocket(); + + if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + ALOGE("Socket failed."); + goto bailout; + } + + // Set non-blocking operation + if (!setNonblocking(listen_fd_)) + goto bailout; + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(kListenPort); + + if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + ALOGE("Bind failed."); + goto bailout; + } + + if (listen(listen_fd_, 1) < 0) { + ALOGE("Listen failed."); + goto bailout; + } + + ret = true; +bailout: + if (!ret) + cleanupListenSocket(); + + return ret; +} + +void DiagThread::cleanupListenSocket() { + if (listen_fd_ >= 0) { + int res; + + struct linger l; + l.l_onoff = 1; + l.l_linger = 0; + + setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + shutdown(listen_fd_, SHUT_RDWR); + close(listen_fd_); + listen_fd_ = -1; + } +} + +void DiagThread::cleanupDataSocket() { + if (data_fd_ >= 0) { + int res; + + struct linger l; + l.l_onoff = 1; + l.l_linger = 0; + + setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + shutdown(data_fd_, SHUT_RDWR); + close(data_fd_); + data_fd_ = -1; + } +} + +void DiagThread::resetLogIDs() { + // Drain and discard all of the events from the kernel + struct local_time_debug_event events[kMaxEvents]; + while(local_clock_->getDebugLog(events, kMaxEvents) > 0) + ; + + { + Mutex::Autolock lock(&discipline_log_lock_); + discipline_log_.clear(); + discipline_log_ID_ = 0; + } + + kernel_logID_basis_known_ = false; +} + +void DiagThread::pushDisciplineEvent(int64_t observed_local_time, + int64_t observed_common_time, + int64_t nominal_common_time, + int32_t total_correction, + int32_t rtt) { + Mutex::Autolock lock(&discipline_log_lock_); + + DisciplineEventRecord evt; + + evt.event_id = discipline_log_ID_++; + + evt.action_local_time = local_clock_->getLocalTime(); + common_clock_->localToCommon(evt.action_local_time, + &evt.action_common_time); + + evt.observed_local_time = observed_local_time; + evt.observed_common_time = observed_common_time; + evt.nominal_common_time = nominal_common_time; + evt.total_correction = total_correction; + evt.rtt = rtt; + + discipline_log_.push_back(evt); + while (discipline_log_.size() > kMaxDisciplineLogSize) + discipline_log_.erase(discipline_log_.begin()); +} + +bool DiagThread::threadLoop() { + struct pollfd poll_fds[1]; + + if (!openListenSocket()) { + ALOGE("Failed to open listen socket"); + goto bailout; + } + + while (!exitPending()) { + memset(&poll_fds, 0, sizeof(poll_fds)); + + if (data_fd_ < 0) { + poll_fds[0].fd = listen_fd_; + poll_fds[0].events = POLLIN; + } else { + poll_fds[0].fd = data_fd_; + poll_fds[0].events = POLLRDHUP | POLLIN; + } + + int poll_res = poll(poll_fds, NELEM(poll_fds), 50); + if (poll_res < 0) { + ALOGE("Fatal error (%d,%d) while waiting on events", + poll_res, errno); + goto bailout; + } + + if (exitPending()) + break; + + if (poll_fds[0].revents) { + if (poll_fds[0].fd == listen_fd_) { + data_fd_ = accept(listen_fd_, NULL, NULL); + + if (data_fd_ < 0) { + ALOGW("Failed accept on socket %d with err %d", + listen_fd_, errno); + } else { + if (!setNonblocking(data_fd_)) + cleanupDataSocket(); + if (!setNodelay(data_fd_)) + cleanupDataSocket(); + } + } else + if (poll_fds[0].fd == data_fd_) { + if (poll_fds[0].revents & POLLRDHUP) { + // Connection hung up; time to clean up. + cleanupDataSocket(); + } else + if (poll_fds[0].revents & POLLIN) { + uint8_t cmd; + if (read(data_fd_, &cmd, sizeof(cmd)) > 0) { + switch(cmd) { + case 'r': + case 'R': + resetLogIDs(); + break; + } + } + } + } + } + + struct local_time_debug_event events[kMaxEvents]; + int amt = local_clock_->getDebugLog(events, kMaxEvents); + + if (amt > 0) { + for (int i = 0; i < amt; i++) { + struct local_time_debug_event& e = events[i]; + + if (!kernel_logID_basis_known_) { + kernel_logID_basis_ = e.local_timesync_event_id; + kernel_logID_basis_known_ = true; + } + + char buf[1024]; + int64_t common_time; + status_t res = common_clock_->localToCommon(e.local_time, + &common_time); + snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n", + e.local_timesync_event_id - kernel_logID_basis_, + e.local_time, + common_time, + (OK == res) ? 1 : 0); + buf[sizeof(buf) - 1] = 0; + + if (data_fd_ >= 0) + write(data_fd_, buf, strlen(buf)); + } + } + + { // scope for autolock pattern + Mutex::Autolock lock(&discipline_log_lock_); + + while (discipline_log_.size() > 0) { + char buf[1024]; + DisciplineEventRecord& e = *discipline_log_.begin(); + snprintf(buf, sizeof(buf), + "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d\n", + e.event_id, + e.action_local_time, + e.action_common_time, + e.observed_local_time, + e.observed_common_time, + e.nominal_common_time, + e.total_correction, + e.rtt); + buf[sizeof(buf) - 1] = 0; + + if (data_fd_ >= 0) + write(data_fd_, buf, strlen(buf)); + + discipline_log_.erase(discipline_log_.begin()); + } + } + } + +bailout: + cleanupDataSocket(); + cleanupListenSocket(); + return false; +} + +} // namespace android |