diff options
author | Yao Chen <yaochen@google.com> | 2018-05-07 16:57:13 -0700 |
---|---|---|
committer | Yao Chen <yaochen@google.com> | 2018-05-21 10:06:25 -0700 |
commit | b3be9eaff57dfd344784fb7b803d1cc56d771294 (patch) | |
tree | c76f06e25136200c464dd2a86540a5556c647ffa /libstats | |
parent | da6ab73420760da9fe39ba3f26068c6d1ad46537 (diff) | |
download | system_core-b3be9eaff57dfd344784fb7b803d1cc56d771294.tar.gz system_core-b3be9eaff57dfd344784fb7b803d1cc56d771294.tar.bz2 system_core-b3be9eaff57dfd344784fb7b803d1cc56d771294.zip |
Move libstatssocket from frameworks/base to system/core/
So that lmkd can build on PDK.
Bug: 79349329
Test: builds locally
Merged-In: I981e6ef9f9769b873640e5f169a9495ccea2f25c
Change-Id: I981e6ef9f9769b873640e5f169a9495ccea2f25c
(cherry picked from commit b13a102c0afec2c0a1fbdafb9a00e3d55450b28b)
Diffstat (limited to 'libstats')
-rw-r--r-- | libstats/Android.bp | 37 | ||||
-rw-r--r-- | libstats/include/stats_event_list.h | 250 | ||||
-rw-r--r-- | libstats/stats_event_list.c | 181 | ||||
-rw-r--r-- | libstats/statsd_writer.c | 260 | ||||
-rw-r--r-- | libstats/statsd_writer.h | 43 |
5 files changed, 771 insertions, 0 deletions
diff --git a/libstats/Android.bp b/libstats/Android.bp new file mode 100644 index 000000000..d58f29417 --- /dev/null +++ b/libstats/Android.bp @@ -0,0 +1,37 @@ +// +// Copyright (C) 2018 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. +// + +// ========================================================== +// Native library to write stats log to statsd socket +// ========================================================== +cc_library_static { + name: "libstatssocket", + srcs: [ + "stats_event_list.c", + "statsd_writer.c", + ], + cflags: [ + "-Wall", + "-Werror", + "-DLIBLOG_LOG_TAG=1006", + "-DWRITE_TO_STATSD=1", + "-DWRITE_TO_LOGD=0", + ], + export_include_dirs: ["include"], + shared_libs: [ + "liblog", + ], +} diff --git a/libstats/include/stats_event_list.h b/libstats/include/stats_event_list.h new file mode 100644 index 000000000..5d174ae03 --- /dev/null +++ b/libstats/include/stats_event_list.h @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2018, 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 ANDROID_STATS_LOG_STATS_EVENT_LIST_H +#define ANDROID_STATS_LOG_STATS_EVENT_LIST_H + +#include <log/log_event_list.h> + +#ifdef __cplusplus +extern "C" { +#endif +void reset_log_context(android_log_context ctx); +int write_to_logger(android_log_context context, log_id_t id); + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +/** + * A copy of android_log_event_list class. + * + * android_log_event_list is going to be deprecated soon, so copy it here to + * avoid creating dependency on upstream code. TODO(b/78304629): Rewrite this + * code. + */ +class stats_event_list { + private: + android_log_context ctx; + int ret; + + stats_event_list(const stats_event_list&) = delete; + void operator=(const stats_event_list&) = delete; + + public: + explicit stats_event_list(int tag) : ret(0) { + ctx = create_android_logger(static_cast<uint32_t>(tag)); + } + explicit stats_event_list(log_msg& log_msg) : ret(0) { + ctx = create_android_log_parser(log_msg.msg() + sizeof(uint32_t), + log_msg.entry.len - sizeof(uint32_t)); + } + ~stats_event_list() { android_log_destroy(&ctx); } + + int close() { + int retval = android_log_destroy(&ctx); + if (retval < 0) { + ret = retval; + } + return retval; + } + + /* To allow above C calls to use this class as parameter */ + operator android_log_context() const { return ctx; } + + /* return errors or transmit status */ + int status() const { return ret; } + + int begin() { + int retval = android_log_write_list_begin(ctx); + if (retval < 0) { + ret = retval; + } + return ret; + } + int end() { + int retval = android_log_write_list_end(ctx); + if (retval < 0) { + ret = retval; + } + return ret; + } + + stats_event_list& operator<<(int32_t value) { + int retval = android_log_write_int32(ctx, value); + if (retval < 0) { + ret = retval; + } + return *this; + } + + stats_event_list& operator<<(uint32_t value) { + int retval = android_log_write_int32(ctx, static_cast<int32_t>(value)); + if (retval < 0) { + ret = retval; + } + return *this; + } + + stats_event_list& operator<<(bool value) { + int retval = android_log_write_int32(ctx, value ? 1 : 0); + if (retval < 0) { + ret = retval; + } + return *this; + } + + stats_event_list& operator<<(int64_t value) { + int retval = android_log_write_int64(ctx, value); + if (retval < 0) { + ret = retval; + } + return *this; + } + + stats_event_list& operator<<(uint64_t value) { + int retval = android_log_write_int64(ctx, static_cast<int64_t>(value)); + if (retval < 0) { + ret = retval; + } + return *this; + } + + stats_event_list& operator<<(const char* value) { + int retval = android_log_write_string8(ctx, value); + if (retval < 0) { + ret = retval; + } + return *this; + } + +#if defined(_USING_LIBCXX) + stats_event_list& operator<<(const std::string& value) { + int retval = android_log_write_string8_len(ctx, value.data(), value.length()); + if (retval < 0) { + ret = retval; + } + return *this; + } +#endif + + stats_event_list& operator<<(float value) { + int retval = android_log_write_float32(ctx, value); + if (retval < 0) { + ret = retval; + } + return *this; + } + + int write(log_id_t id = LOG_ID_EVENTS) { + /* facilitate -EBUSY retry */ + if ((ret == -EBUSY) || (ret > 0)) { + ret = 0; + } + int retval = write_to_logger(ctx, id); + /* existing errors trump transmission errors */ + if (!ret) { + ret = retval; + } + return ret; + } + + /* + * Append<Type> methods removes any integer promotion + * confusion, and adds access to string with length. + * Append methods are also added for all types for + * convenience. + */ + + bool AppendInt(int32_t value) { + int retval = android_log_write_int32(ctx, value); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } + + bool AppendLong(int64_t value) { + int retval = android_log_write_int64(ctx, value); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } + + bool AppendString(const char* value) { + int retval = android_log_write_string8(ctx, value); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } + + bool AppendString(const char* value, size_t len) { + int retval = android_log_write_string8_len(ctx, value, len); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } + +#if defined(_USING_LIBCXX) + bool AppendString(const std::string& value) { + int retval = android_log_write_string8_len(ctx, value.data(), value.length()); + if (retval < 0) { + ret = retval; + } + return ret; + } + + bool Append(const std::string& value) { + int retval = android_log_write_string8_len(ctx, value.data(), value.length()); + if (retval < 0) { + ret = retval; + } + return ret; + } +#endif + + bool AppendFloat(float value) { + int retval = android_log_write_float32(ctx, value); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } + + template <typename Tvalue> + bool Append(Tvalue value) { + *this << value; + return ret >= 0; + } + + bool Append(const char* value, size_t len) { + int retval = android_log_write_string8_len(ctx, value, len); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } + + android_log_list_element read() { return android_log_read_next(ctx); } + android_log_list_element peek() { return android_log_peek_next(ctx); } +}; + +#endif +#endif // ANDROID_STATS_LOG_STATS_EVENT_LIST_H diff --git a/libstats/stats_event_list.c b/libstats/stats_event_list.c new file mode 100644 index 000000000..966bb08a2 --- /dev/null +++ b/libstats/stats_event_list.c @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2018, 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/stats_event_list.h" + +#include <string.h> +#include "statsd_writer.h" + +#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t)) + +typedef struct { + uint32_t tag; + unsigned pos; /* Read/write position into buffer */ + unsigned count[ANDROID_MAX_LIST_NEST_DEPTH + 1]; /* Number of elements */ + unsigned list[ANDROID_MAX_LIST_NEST_DEPTH + 1]; /* pos for list counter */ + unsigned list_nest_depth; + unsigned len; /* Length or raw buffer. */ + bool overflow; + bool list_stop; /* next call decrement list_nest_depth and issue a stop */ + enum { + kAndroidLoggerRead = 1, + kAndroidLoggerWrite = 2, + } read_write_flag; + uint8_t storage[LOGGER_ENTRY_MAX_PAYLOAD]; +} android_log_context_internal; + +extern struct android_log_transport_write statsdLoggerWrite; + +static int __write_to_statsd_init(struct iovec* vec, size_t nr); +static int (*write_to_statsd)(struct iovec* vec, size_t nr) = __write_to_statsd_init; + +// Similar to create_android_logger(), but instead of allocation a new buffer, +// this function resets the buffer for resuse. +void reset_log_context(android_log_context ctx) { + if (!ctx) { + return; + } + android_log_context_internal* context = (android_log_context_internal*)(ctx); + uint32_t tag = context->tag; + memset(context, 0, sizeof(android_log_context_internal)); + + context->tag = tag; + context->read_write_flag = kAndroidLoggerWrite; + size_t needed = sizeof(uint8_t) + sizeof(uint8_t); + if ((context->pos + needed) > MAX_EVENT_PAYLOAD) { + context->overflow = true; + } + /* Everything is a list */ + context->storage[context->pos + 0] = EVENT_TYPE_LIST; + context->list[0] = context->pos + 1; + context->pos += needed; +} + +int stats_write_list(android_log_context ctx) { + android_log_context_internal* context; + const char* msg; + ssize_t len; + + context = (android_log_context_internal*)(ctx); + if (!context || (kAndroidLoggerWrite != context->read_write_flag)) { + return -EBADF; + } + + if (context->list_nest_depth) { + return -EIO; + } + + /* NB: if there was overflow, then log is truncated. Nothing reported */ + context->storage[1] = context->count[0]; + len = context->len = context->pos; + msg = (const char*)context->storage; + /* it's not a list */ + if (context->count[0] <= 1) { + len -= sizeof(uint8_t) + sizeof(uint8_t); + if (len < 0) { + len = 0; + } + msg += sizeof(uint8_t) + sizeof(uint8_t); + } + + struct iovec vec[2]; + vec[0].iov_base = &context->tag; + vec[0].iov_len = sizeof(context->tag); + vec[1].iov_base = (void*)msg; + vec[1].iov_len = len; + return write_to_statsd(vec, 2); +} + +int write_to_logger(android_log_context ctx, log_id_t id) { + int retValue = 0; + + if (WRITE_TO_LOGD) { + retValue = android_log_write_list(ctx, id); + } + + if (WRITE_TO_STATSD) { + // log_event_list's cast operator is overloaded. + int ret = stats_write_list(ctx); + // In debugging phase, we may write to both logd and statsd. Prefer to + // return statsd socket write error code here. + if (ret < 0) { + retValue = ret; + } + } + + return retValue; +} + +/* log_init_lock assumed */ +static int __write_to_statsd_initialize_locked() { + if (!statsdLoggerWrite.open || ((*statsdLoggerWrite.open)() < 0)) { + if (statsdLoggerWrite.close) { + (*statsdLoggerWrite.close)(); + return -ENODEV; + } + } + return 1; +} + +static int __write_to_stats_daemon(struct iovec* vec, size_t nr) { + int ret, save_errno; + struct timespec ts; + size_t len, i; + + for (len = i = 0; i < nr; ++i) { + len += vec[i].iov_len; + } + if (!len) { + return -EINVAL; + } + + save_errno = errno; + clock_gettime(CLOCK_REALTIME, &ts); + + ret = 0; + + ssize_t retval; + retval = (*statsdLoggerWrite.write)(&ts, vec, nr); + if (ret >= 0) { + ret = retval; + } + + errno = save_errno; + return ret; +} + +static int __write_to_statsd_init(struct iovec* vec, size_t nr) { + int ret, save_errno = errno; + + statsd_writer_init_lock(); + + if (write_to_statsd == __write_to_statsd_init) { + ret = __write_to_statsd_initialize_locked(); + if (ret < 0) { + statsd_writer_init_unlock(); + errno = save_errno; + return ret; + } + + write_to_statsd = __write_to_stats_daemon; + } + + statsd_writer_init_unlock(); + + ret = write_to_statsd(vec, nr); + errno = save_errno; + return ret; +}
\ No newline at end of file diff --git a/libstats/statsd_writer.c b/libstats/statsd_writer.c new file mode 100644 index 000000000..9953bba2e --- /dev/null +++ b/libstats/statsd_writer.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2018, 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 "statsd_writer.h" + +#include <cutils/sockets.h> +#include <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <poll.h> +#include <private/android_filesystem_config.h> +#include <private/android_logger.h> +#include <stdarg.h> +#include <stdatomic.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <time.h> +#include <unistd.h> + +/* branchless on many architectures. */ +#define min(x, y) ((y) ^ (((x) ^ (y)) & -((x) < (y)))) + +static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER; + +void statsd_writer_init_lock() { + /* + * If we trigger a signal handler in the middle of locked activity and the + * signal handler logs a message, we could get into a deadlock state. + */ + pthread_mutex_lock(&log_init_lock); +} + +int statd_writer_trylock() { + return pthread_mutex_trylock(&log_init_lock); +} + +void statsd_writer_init_unlock() { + pthread_mutex_unlock(&log_init_lock); +} + +static int statsdAvailable(); +static int statsdOpen(); +static void statsdClose(); +static int statsdWrite(struct timespec* ts, struct iovec* vec, size_t nr); + +struct android_log_transport_write statsdLoggerWrite = { + .name = "statsd", + .sock = -EBADF, + .available = statsdAvailable, + .open = statsdOpen, + .close = statsdClose, + .write = statsdWrite, +}; + +/* log_init_lock assumed */ +static int statsdOpen() { + int i, ret = 0; + + i = atomic_load(&statsdLoggerWrite.sock); + if (i < 0) { + int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (sock < 0) { + ret = -errno; + } else { + struct sockaddr_un un; + memset(&un, 0, sizeof(struct sockaddr_un)); + un.sun_family = AF_UNIX; + strcpy(un.sun_path, "/dev/socket/statsdw"); + + if (TEMP_FAILURE_RETRY( + connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) { + ret = -errno; + switch (ret) { + case -ENOTCONN: + case -ECONNREFUSED: + case -ENOENT: + i = atomic_exchange(&statsdLoggerWrite.sock, ret); + /* FALLTHRU */ + default: + break; + } + close(sock); + } else { + ret = atomic_exchange(&statsdLoggerWrite.sock, sock); + if ((ret >= 0) && (ret != sock)) { + close(ret); + } + ret = 0; + } + } + } + + return ret; +} + +static void __statsdClose(int negative_errno) { + int sock = atomic_exchange(&statsdLoggerWrite.sock, negative_errno); + if (sock >= 0) { + close(sock); + } +} + +static void statsdClose() { + __statsdClose(-EBADF); +} + +static int statsdAvailable() { + if (atomic_load(&statsdLoggerWrite.sock) < 0) { + if (access("/dev/socket/statsdw", W_OK) == 0) { + return 0; + } + return -EBADF; + } + return 1; +} + +static int statsdWrite(struct timespec* ts, struct iovec* vec, size_t nr) { + ssize_t ret; + int sock; + static const unsigned headerLength = 1; + struct iovec newVec[nr + headerLength]; + android_log_header_t header; + size_t i, payloadSize; + static atomic_int dropped; + + sock = atomic_load(&statsdLoggerWrite.sock); + if (sock < 0) switch (sock) { + case -ENOTCONN: + case -ECONNREFUSED: + case -ENOENT: + break; + default: + return -EBADF; + } + /* + * struct { + * // what we provide to socket + * android_log_header_t header; + * // caller provides + * union { + * struct { + * char prio; + * char payload[]; + * } string; + * struct { + * uint32_t tag + * char payload[]; + * } binary; + * }; + * }; + */ + + header.tid = gettid(); + header.realtime.tv_sec = ts->tv_sec; + header.realtime.tv_nsec = ts->tv_nsec; + + newVec[0].iov_base = (unsigned char*)&header; + newVec[0].iov_len = sizeof(header); + + // If we dropped events before, try to tell statsd. + if (sock >= 0) { + int32_t snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed); + if (snapshot) { + android_log_event_int_t buffer; + header.id = LOG_ID_STATS; + buffer.header.tag = htole32(LIBLOG_LOG_TAG); + buffer.payload.type = EVENT_TYPE_INT; + buffer.payload.data = htole32(snapshot); + + newVec[headerLength].iov_base = &buffer; + newVec[headerLength].iov_len = sizeof(buffer); + + ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2)); + if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) { + atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed); + } + } + } + + header.id = LOG_ID_STATS; + + for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) { + newVec[i].iov_base = vec[i - headerLength].iov_base; + payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len; + + if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) { + newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD; + if (newVec[i].iov_len) { + ++i; + } + break; + } + } + + /* + * The write below could be lost, but will never block. + * + * ENOTCONN occurs if statsd has died. + * ENOENT occurs if statsd is not running and socket is missing. + * ECONNREFUSED occurs if we can not reconnect to statsd. + * EAGAIN occurs if statsd is overloaded. + */ + if (sock < 0) { + ret = sock; + } else { + ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i)); + if (ret < 0) { + ret = -errno; + } + } + switch (ret) { + case -ENOTCONN: + case -ECONNREFUSED: + case -ENOENT: + if (statd_writer_trylock()) { + return ret; /* in a signal handler? try again when less stressed + */ + } + __statsdClose(ret); + ret = statsdOpen(); + statsd_writer_init_unlock(); + + if (ret < 0) { + return ret; + } + + ret = TEMP_FAILURE_RETRY(writev(atomic_load(&statsdLoggerWrite.sock), newVec, i)); + if (ret < 0) { + ret = -errno; + } + /* FALLTHRU */ + default: + break; + } + + if (ret > (ssize_t)sizeof(header)) { + ret -= sizeof(header); + } else if (ret == -EAGAIN) { + atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed); + } + + return ret; +} diff --git a/libstats/statsd_writer.h b/libstats/statsd_writer.h new file mode 100644 index 000000000..82e14e04f --- /dev/null +++ b/libstats/statsd_writer.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018, 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 ANDROID_STATS_LOG_STATS_WRITER_H +#define ANDROID_STATS_LOG_STATS_WRITER_H + +#include <pthread.h> +#include <stdatomic.h> +#include <sys/socket.h> + +/** + * Internal lock should not be exposed. This is bad design. + * TODO: rewrite it in c++ code and encapsulate the functionality in a + * StatsdWriter class. + */ +void statsd_writer_init_lock(); +int statsd_writer_init_trylock(); +void statsd_writer_init_unlock(); + +struct android_log_transport_write { + const char* name; /* human name to describe the transport */ + atomic_int sock; + int (*available)(); /* Does not cause resources to be taken */ + int (*open)(); /* can be called multiple times, reusing current resources */ + void (*close)(); /* free up resources */ + /* write log to transport, returns number of bytes propagated, or -errno */ + int (*write)(struct timespec* ts, struct iovec* vec, size_t nr); +}; + +#endif // ANDROID_STATS_LOG_STATS_WRITER_H |