From 5dcbc6c015fd56db9381cb7aff58506e8ebcc150 Mon Sep 17 00:00:00 2001 From: Joe Onorato Date: Tue, 29 Aug 2017 15:13:58 -0700 Subject: [PATCH] Add statsd. It doesn't start yet by default. When you start it manually, it sets itself up as a binder system service and starts a thread to read the event log. Test: Run statsd, observe output. also run stats_test Change-Id: If435d6a80fef3c1d957aedb61699bf5e9aae7e56 --- Android.mk | 1 + cmds/statsd/Android.mk | 106 +++++++++++++++++ cmds/statsd/src/LogEntryPrinter.cpp | 61 ++++++++++ cmds/statsd/src/LogEntryPrinter.h | 54 +++++++++ cmds/statsd/src/LogReader.cpp | 143 +++++++++++++++++++++++ cmds/statsd/src/LogReader.h | 81 +++++++++++++ cmds/statsd/src/StatsService.cpp | 76 +++++++++++++ cmds/statsd/src/StatsService.h | 44 ++++++++ cmds/statsd/src/main.cpp | 144 ++++++++++++++++++++++++ cmds/statsd/statsd.rc | 16 +++ cmds/statsd/tests/LogReader_test.cpp | 24 ++++ core/java/android/os/IStatsManager.aidl | 28 +++++ 12 files changed, 778 insertions(+) create mode 100644 cmds/statsd/Android.mk create mode 100644 cmds/statsd/src/LogEntryPrinter.cpp create mode 100644 cmds/statsd/src/LogEntryPrinter.h create mode 100644 cmds/statsd/src/LogReader.cpp create mode 100644 cmds/statsd/src/LogReader.h create mode 100644 cmds/statsd/src/StatsService.cpp create mode 100644 cmds/statsd/src/StatsService.h create mode 100644 cmds/statsd/src/main.cpp create mode 100644 cmds/statsd/statsd.rc create mode 100644 cmds/statsd/tests/LogReader_test.cpp create mode 100644 core/java/android/os/IStatsManager.aidl diff --git a/Android.mk b/Android.mk index 33936f361c245..e61d7c2a5c0fd 100644 --- a/Android.mk +++ b/Android.mk @@ -270,6 +270,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IRecoverySystemProgressListener.aidl \ core/java/android/os/IRemoteCallback.aidl \ core/java/android/os/ISchedulingPolicyService.aidl \ + core/java/android/os/IStatsManager.aidl \ core/java/android/os/IThermalEventListener.aidl \ core/java/android/os/IThermalService.aidl \ core/java/android/os/IUpdateLock.aidl \ diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk new file mode 100644 index 0000000000000..db8c89dafaba3 --- /dev/null +++ b/cmds/statsd/Android.mk @@ -0,0 +1,106 @@ +# Copyright (C) 2017 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. + +LOCAL_PATH:= $(call my-dir) + +# ========= +# statsd +# ========= + +include $(CLEAR_VARS) + +LOCAL_MODULE := statsd + +LOCAL_SRC_FILES := \ + ../../core/java/android/os/IStatsManager.aidl \ + src/StatsService.cpp \ + src/LogEntryPrinter.cpp \ + src/LogReader.cpp \ + src/main.cpp + +LOCAL_CFLAGS += \ + -Wall \ + -Werror \ + -Wno-missing-field-initializers \ + -Wno-unused-variable \ + -Wno-unused-function \ + -Wno-unused-parameter + +ifeq (debug,) + LOCAL_CFLAGS += \ + -g -O0 +else + # optimize for size (protobuf glop can get big) + LOCAL_CFLAGS += \ + -Os +endif + +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java +LOCAL_C_INCLUDES += $(LOCAL_PATH)/src + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + libincident \ + liblog \ + libselinux \ + libutils + +LOCAL_MODULE_CLASS := EXECUTABLES + +#LOCAL_INIT_RC := statsd.rc + +include $(BUILD_EXECUTABLE) + +# ============== +# statsd_test +# ============== + +include $(CLEAR_VARS) + +LOCAL_MODULE := statsd_test +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_MODULE_TAGS := tests + +LOCAL_CFLAGS += \ + -Wall \ + -Werror \ + -Wno-missing-field-initializers \ + -Wno-unused-variable \ + -Wno-unused-function \ + -Wno-unused-parameter + +LOCAL_C_INCLUDES += $(LOCAL_PATH)/src + +LOCAL_SRC_FILES := \ + ../../core/java/android/os/IStatsManager.aidl \ + src/StatsService.cpp \ + src/LogEntryPrinter.cpp \ + src/LogReader.cpp \ + tests/LogReader_test.cpp \ + +LOCAL_STATIC_LIBRARIES := \ + libgmock \ + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + liblog \ + libselinux \ + libutils + +include $(BUILD_NATIVE_TEST) + diff --git a/cmds/statsd/src/LogEntryPrinter.cpp b/cmds/statsd/src/LogEntryPrinter.cpp new file mode 100644 index 0000000000000..ba07308086ca2 --- /dev/null +++ b/cmds/statsd/src/LogEntryPrinter.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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. + */ + +#include + +#include +#include +#include + +using namespace android; + +LogEntryPrinter::LogEntryPrinter(int out) + :m_out(out) +{ + // Initialize the EventTagMap, which is how we know the names of the numeric event tags. + // If this fails, we can't print well, but something will print. + m_tags = android_openEventTagMap(NULL); + + // Printing format + m_format = android_log_format_new(); + android_log_setPrintFormat(m_format, FORMAT_THREADTIME); +} + +LogEntryPrinter::~LogEntryPrinter() +{ + if (m_tags != NULL) { + android_closeEventTagMap(m_tags); + } + android_log_format_free(m_format); +} + +void +LogEntryPrinter::OnLogEvent(const log_msg& msg) +{ + status_t err; + AndroidLogEntry entry; + char buf[1024]; + + err = android_log_processBinaryLogBuffer(&(const_cast(&msg)->entry_v1), + &entry, m_tags, buf, sizeof(buf)); + if (err == NO_ERROR) { + android_log_printLogLine(m_format, m_out, &entry); + } else { + printf("log entry: %s\n", buf); + fflush(stdout); + } +} + diff --git a/cmds/statsd/src/LogEntryPrinter.h b/cmds/statsd/src/LogEntryPrinter.h new file mode 100644 index 0000000000000..61ffddca09163 --- /dev/null +++ b/cmds/statsd/src/LogEntryPrinter.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef LOG_ENTRY_PRINTER_H +#define LOG_ENTRY_PRINTER_H + +#include "LogReader.h" + +#include + +#include + +/** + * Decodes the log entry and prints it to the supplied file descriptor. + */ +class LogEntryPrinter : public LogListener +{ +public: + LogEntryPrinter(int out); + virtual ~LogEntryPrinter(); + + virtual void OnLogEvent(const log_msg& msg); + +private: + /** + * Where to write to. + */ + int m_out; + + /** + * Numeric to string tag name mapping. + */ + EventTagMap* m_tags; + + /** + * Pretty printing format. + */ + AndroidLogFormat* m_format; +}; + +#endif // LOG_ENTRY_PRINTER_H diff --git a/cmds/statsd/src/LogReader.cpp b/cmds/statsd/src/LogReader.cpp new file mode 100644 index 0000000000000..e0ed6464f4dc7 --- /dev/null +++ b/cmds/statsd/src/LogReader.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "LogReader.h" + +#include + +#include + +#include +#include + +using namespace android; +using namespace std; + +#define SNOOZE_INITIAL_MS 100 +#define SNOOZE_MAX_MS (10 * 60 * 1000) // Ten minutes + + +// ================================================================================ +LogListener::LogListener() +{ +} + +LogListener::~LogListener() +{ +} + + +// ================================================================================ +LogReader::LogReader() +{ +} + +LogReader::~LogReader() +{ +} + +void +LogReader::AddListener(const sp& listener) +{ + m_listeners.push_back(listener); +} + +void +LogReader::Run() +{ + int nextSnoozeMs = SNOOZE_INITIAL_MS; + + // In an ideal world, this outer loop will only ever run one iteration, but it + // exists to handle crashes in logd. The inner loop inside connect_and_read() + // reads from logd forever, but if that read fails, we fall out to the outer + // loop, do the backoff (resetting the backoff timeout if we successfully read + // something), and then try again. + while (true) { + // Connect and read + int lineCount = connect_and_read(); + + // Figure out how long to sleep. + if (lineCount > 0) { + // If we managed to read at least one line, reset the backoff + nextSnoozeMs = SNOOZE_INITIAL_MS; + } else { + // Otherwise, expontial backoff + nextSnoozeMs *= 1.5f; + if (nextSnoozeMs > 10 * 60 * 1000) { + // Don't wait for toooo long. + nextSnoozeMs = SNOOZE_MAX_MS; + } + } + + // Sleep + timespec ts; + timespec rem; + ts.tv_sec = nextSnoozeMs / 1000; + ts.tv_nsec = (nextSnoozeMs % 1000) * 1000000L; + while (nanosleep(&ts, &rem) == -1) { + if (errno == EINTR) { + ts = rem; + } + // other errors are basically impossible + } + } +} + +int +LogReader::connect_and_read() +{ + int lineCount = 0; + status_t err; + logger_list* loggers; + logger* eventLogger; + + // Prepare the logging context + loggers = android_logger_list_alloc(ANDROID_LOG_RDONLY, + /* don't stop after N lines */ 0, + /* no pid restriction */ 0); + + // Open the buffer(s) + eventLogger = android_logger_open(loggers, LOG_ID_EVENTS); + + // Read forever + if (eventLogger) { + while (true) { + log_msg msg; + + // Read a message + err = android_logger_list_read(loggers, &msg); + if (err < 0) { + fprintf(stderr, "logcat read failure: %s\n", strerror(err)); + break; + } + + // Record that we read one (used above to know how to snooze). + lineCount++; + + // Call the listeners + for (vector >::iterator it = m_listeners.begin(); + it != m_listeners.end(); it++) { + (*it)->OnLogEvent(msg); + } + } + } + + // Free the logger list and close the individual loggers + android_logger_list_free(loggers); + + return lineCount; +} + diff --git a/cmds/statsd/src/LogReader.h b/cmds/statsd/src/LogReader.h new file mode 100644 index 0000000000000..08a17a31aa18b --- /dev/null +++ b/cmds/statsd/src/LogReader.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef LOGREADER_H +#define LOGREADER_H + +#include + +#include + +#include + +/** + * Callback for LogReader + */ +class LogListener : public virtual android::RefBase +{ +public: + LogListener(); + virtual ~LogListener(); + + // TODO: Rather than using log_msg, which doesn't have any real internal structure + // here, we should pull this out into our own LogEntry class. + virtual void OnLogEvent(const log_msg& msg) = 0; +}; + +/** + * Class to read logs from logd. + */ +class LogReader : public virtual android::RefBase +{ +public: + /** + * Construct the LogReader with a pointer back to the StatsService + */ + LogReader(); + + /** + * Destructor. + */ + virtual ~LogReader(); + + /** + * Add a LogListener class. + */ + void AddListener(const android::sp& listener); + + /** + * Run the main LogReader loop + */ + void Run(); + +private: + /** + * List of listeners to call back on when we do get an event. + */ + std::vector > m_listeners; + + /** + * Connect to a single instance of logd, and read until there's a read error. + * Logd can crash, exit, be killed etc. + * + * Returns the number of lines that were read. + */ + int connect_and_read(); +}; + +#endif // LOGREADER_H diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp new file mode 100644 index 0000000000000..13c6f67dd37d8 --- /dev/null +++ b/cmds/statsd/src/StatsService.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 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 "statsd" + +#include "StatsService.h" + +#include +#include +#include +#include +#include + +#include +#include + +using namespace android; + +// ================================================================================ +StatsService::StatsService(const sp& handlerLooper) +{ + ALOGD("stats service constructed"); +} + +StatsService::~StatsService() +{ +} + +status_t +StatsService::dump(int fd, const Vector& args) +{ + FILE* out = fdopen(fd, "w"); + if (out == NULL) { + return NO_MEMORY; // the fd is already open + } + + fprintf(out, "StatsService::dump:"); + ALOGD("StatsService::dump:"); + const int N = args.size(); + for (int i=0; igetCallingUid() != AID_SYSTEM) { + return Status::fromExceptionCode(Status::EX_SECURITY, + "Only system uid can call systemRunning"); + } + + // When system_server is up and running, schedule the dropbox task to run. + ALOGD("StatsService::systemRunning"); + + return Status::ok(); +} + diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h new file mode 100644 index 0000000000000..0f34882b4b60c --- /dev/null +++ b/cmds/statsd/src/StatsService.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef STATS_SERVICE_H +#define STATS_SERVICE_H + +#include +#include + +#include +#include + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; +using namespace std; + + +// ================================================================================ +class StatsService : public BnStatsManager { +public: + StatsService(const sp& handlerLooper); + virtual ~StatsService(); + + virtual status_t dump(int fd, const Vector& args); + virtual Status systemRunning(); + +}; + +#endif // STATS_SERVICE_H diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp new file mode 100644 index 0000000000000..93405cb6bf95f --- /dev/null +++ b/cmds/statsd/src/main.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2016 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 "statsd" + +#include "LogEntryPrinter.h" +#include "LogReader.h" +#include "StatsService.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace android; + +// ================================================================================ +/** + * Thread function data. + */ +struct log_reader_thread_data { + sp service; +}; + +/** + * Thread func for where the log reader runs. + */ +static void* +log_reader_thread_func(void* cookie) +{ + log_reader_thread_data* data = static_cast(cookie); + + sp reader = new LogReader(); + + // Put the printer one first, so it will print before the real ones. + if (true) { + reader->AddListener(new LogEntryPrinter(STDOUT_FILENO)); + } + + // TODO: Construct and add real LogListners here. + + reader->Run(); + + ALOGW("statsd LogReader.Run() is not supposed to return."); + + delete data; + return NULL; +} + +/** + * Creates and starts the thread to own the LogReader. + */ +static status_t +start_log_reader_thread(const sp& service) +{ + status_t err; + pthread_attr_t attr; + pthread_t thread; + + // Thread data. + log_reader_thread_data* data = new log_reader_thread_data(); + data->service = service; + + // Create the thread + err = pthread_attr_init(&attr); + if (err != NO_ERROR) { + return err; + } + // TODO: Do we need to tweak thread priority? + err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (err != NO_ERROR) { + pthread_attr_destroy(&attr); + return err; + } + err = pthread_create(&thread, &attr, log_reader_thread_func, static_cast(data)); + if (err != NO_ERROR) { + pthread_attr_destroy(&attr); + return err; + } + pthread_attr_destroy(&attr); + + return NO_ERROR; +} + +// ================================================================================ +int +main(int /*argc*/, char** /*argv*/) +{ + status_t err; + + // Set up the looper + sp looper(Looper::prepare(0 /* opts */)); + + // Set up the binder + sp ps(ProcessState::self()); + ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram + ps->startThreadPool(); + ps->giveThreadPoolName(); + IPCThreadState::self()->disableBackgroundScheduling(true); + + // Create the service + sp service = new StatsService(looper); + if (defaultServiceManager()->addService(String16("stats"), service) != 0) { + ALOGE("Failed to add service"); + return -1; + } + + // Start the log reader thread + err = start_log_reader_thread(service); + if (err != NO_ERROR) { + return 1; + } + + // Loop forever -- the reports run on this thread in a handler, and the + // binder calls remain responsive in their pool of one thread. + while (true) { + looper->pollAll(-1 /* timeoutMillis */); + } + ALOGW("statsd escaped from its loop."); + + return 1; +} diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc new file mode 100644 index 0000000000000..faccd610e2231 --- /dev/null +++ b/cmds/statsd/statsd.rc @@ -0,0 +1,16 @@ +# Copyright (C) 2017 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. + +service statsd /system/bin/statsd + class main diff --git a/cmds/statsd/tests/LogReader_test.cpp b/cmds/statsd/tests/LogReader_test.cpp new file mode 100644 index 0000000000000..ca538b082f89a --- /dev/null +++ b/cmds/statsd/tests/LogReader_test.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2017 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 "statsd_test" + +#include + +#include + +TEST(LogReaderTest, TestNothingAtAll) { + printf("yay!"); +} + diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl new file mode 100644 index 0000000000000..9b5139dc50928 --- /dev/null +++ b/core/java/android/os/IStatsManager.aidl @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2016, 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. + */ + +package android.os; + +/** + * Binder interface to communicate with the statistics collection service. + * {@hide} + */ +oneway interface IStatsManager { + /** + * Tell the incident daemon that the android system server is up and running. + */ + void systemRunning(); +}