summaryrefslogtreecommitdiffstats
path: root/libs/common_time/diag_thread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/common_time/diag_thread.cpp')
-rw-r--r--libs/common_time/diag_thread.cpp323
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