From 876f64cb2f100a078b7ddfd9cc57103a0e456bc6 Mon Sep 17 00:00:00 2001 From: Muhammad Qureshi Date: Mon, 2 Dec 2019 20:27:46 -0800 Subject: [PATCH] Create libstatssocket_q Break up libstatssocket into libstatssocket_q and libstatssocket. libstatssocket_q is for Q Mainline modules. Bug: 145569088 Test: m -j Test: bit statsd_test:* Test: adb shell cmd stats print-logs && adb logcat "*:S statsd:*" Change-Id: I9d113b37640345ebad6aa269ab710db0d2179671 --- libstats/Android.bp | 44 +-- libstats/socket/Android.bp | 40 +++ libstats/{ => socket}/include/stats_event.h | 0 .../{ => socket}/include/stats_event_list.h | 0 libstats/{ => socket}/stats_event.c | 0 libstats/{ => socket}/stats_event_list.c | 0 libstats/{ => socket}/statsd_writer.c | 2 +- libstats/{ => socket}/statsd_writer.h | 0 libstats/socket_q/Android.bp | 40 +++ libstats/socket_q/include/stats_event_list.h | 250 +++++++++++++++ libstats/socket_q/stats_event_list.c | 239 +++++++++++++++ libstats/socket_q/statsd_writer.c | 285 ++++++++++++++++++ libstats/socket_q/statsd_writer.h | 45 +++ 13 files changed, 904 insertions(+), 41 deletions(-) create mode 100644 libstats/socket/Android.bp rename libstats/{ => socket}/include/stats_event.h (100%) rename libstats/{ => socket}/include/stats_event_list.h (100%) rename libstats/{ => socket}/stats_event.c (100%) rename libstats/{ => socket}/stats_event_list.c (100%) rename libstats/{ => socket}/statsd_writer.c (98%) rename libstats/{ => socket}/statsd_writer.h (100%) create mode 100644 libstats/socket_q/Android.bp create mode 100644 libstats/socket_q/include/stats_event_list.h create mode 100644 libstats/socket_q/stats_event_list.c create mode 100644 libstats/socket_q/statsd_writer.c create mode 100644 libstats/socket_q/statsd_writer.h diff --git a/libstats/Android.bp b/libstats/Android.bp index 0440087b2..89c4048b5 100644 --- a/libstats/Android.bp +++ b/libstats/Android.bp @@ -1,40 +1,4 @@ -// -// 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 { - name: "libstatssocket", - srcs: [ - "stats_event_list.c", - "statsd_writer.c", - "stats_event.c", - ], - host_supported: true, - cflags: [ - "-Wall", - "-Werror", - "-DLIBLOG_LOG_TAG=1006", - "-DWRITE_TO_STATSD=1", - "-DWRITE_TO_LOGD=0", - ], - export_include_dirs: ["include"], - shared_libs: [ - "libcutils", - "liblog", - ], -} +subdirs = [ + "socket", + "socket_q", +] diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp new file mode 100644 index 000000000..b7c07b624 --- /dev/null +++ b/libstats/socket/Android.bp @@ -0,0 +1,40 @@ +// +// 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 on Android R and later +// ========================================================================= +cc_library { + name: "libstatssocket", + srcs: [ + "stats_event.c", + "stats_event_list.c", + "statsd_writer.c", + ], + host_supported: true, + cflags: [ + "-Wall", + "-Werror", + "-DLIBLOG_LOG_TAG=1006", + "-DWRITE_TO_STATSD=1", + "-DWRITE_TO_LOGD=0", + ], + export_include_dirs: ["include"], + shared_libs: [ + "libcutils", + "liblog", + ], +} diff --git a/libstats/include/stats_event.h b/libstats/socket/include/stats_event.h similarity index 100% rename from libstats/include/stats_event.h rename to libstats/socket/include/stats_event.h diff --git a/libstats/include/stats_event_list.h b/libstats/socket/include/stats_event_list.h similarity index 100% rename from libstats/include/stats_event_list.h rename to libstats/socket/include/stats_event_list.h diff --git a/libstats/stats_event.c b/libstats/socket/stats_event.c similarity index 100% rename from libstats/stats_event.c rename to libstats/socket/stats_event.c diff --git a/libstats/stats_event_list.c b/libstats/socket/stats_event_list.c similarity index 100% rename from libstats/stats_event_list.c rename to libstats/socket/stats_event_list.c diff --git a/libstats/statsd_writer.c b/libstats/socket/statsd_writer.c similarity index 98% rename from libstats/statsd_writer.c rename to libstats/socket/statsd_writer.c index 073b67fa0..04d3b4644 100644 --- a/libstats/statsd_writer.c +++ b/libstats/socket/statsd_writer.c @@ -101,7 +101,7 @@ static int statsdOpen() { strcpy(un.sun_path, "/dev/socket/statsdw"); if (TEMP_FAILURE_RETRY( - connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) { + connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) { ret = -errno; switch (ret) { case -ENOTCONN: diff --git a/libstats/statsd_writer.h b/libstats/socket/statsd_writer.h similarity index 100% rename from libstats/statsd_writer.h rename to libstats/socket/statsd_writer.h diff --git a/libstats/socket_q/Android.bp b/libstats/socket_q/Android.bp new file mode 100644 index 000000000..6c0c65c23 --- /dev/null +++ b/libstats/socket_q/Android.bp @@ -0,0 +1,40 @@ +// +// 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 on Android Q and earlier. +// This library is only meant to be used by libstatssocket_compat. +// ============================================================================ +cc_library { + name: "libstatssocket_q", + srcs: [ + "stats_event_list.c", + "statsd_writer.c", + ], + host_supported: true, + cflags: [ + "-Wall", + "-Werror", + "-DLIBLOG_LOG_TAG=1006", + "-DWRITE_TO_STATSD=1", + "-DWRITE_TO_LOGD=0", + ], + export_include_dirs: ["include"], + shared_libs: [ + "libcutils", + "liblog", + ], +} diff --git a/libstats/socket_q/include/stats_event_list.h b/libstats/socket_q/include/stats_event_list.h new file mode 100644 index 000000000..b7ada0cfd --- /dev/null +++ b/libstats/socket_q/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. + */ + +#pragma once + +#include +#include + +#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); +void note_log_drop(int error, int atom_tag); +void stats_log_close(); +int android_log_write_char_array(android_log_context ctx, const char* value, size_t len); +extern int (*write_to_statsd)(struct iovec* vec, size_t nr); + +#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(tag)); + } + ~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(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(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; + } + + 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; + } + + 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 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; + } + + 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; + } + + bool AppendFloat(float value) { + int retval = android_log_write_float32(ctx, value); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } + + template + 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; + } + + bool AppendCharArray(const char* value, size_t len) { + int retval = android_log_write_char_array(ctx, value, len); + if (retval < 0) { + ret = retval; + } + return ret >= 0; + } +}; + +#endif diff --git a/libstats/socket_q/stats_event_list.c b/libstats/socket_q/stats_event_list.c new file mode 100644 index 000000000..ae12cbeaa --- /dev/null +++ b/libstats/socket_q/stats_event_list.c @@ -0,0 +1,239 @@ +/* + * 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 +#include +#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); +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; +} + +void note_log_drop(int error, int tag) { + statsdLoggerWrite.noteDrop(error, tag); +} + +void stats_log_close() { + statsd_writer_init_lock(); + write_to_statsd = __write_to_statsd_init; + if (statsdLoggerWrite.close) { + (*statsdLoggerWrite.close)(); + } + statsd_writer_init_unlock(); +} + +/* 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 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; +#if defined(__ANDROID__) + clock_gettime(CLOCK_REALTIME, &ts); +#else + struct timeval tv; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; +#endif + + int ret = (int)(*statsdLoggerWrite.write)(&ts, vec, nr); + 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; +} + +static inline void copy4LE(uint8_t* buf, uint32_t val) { + buf[0] = val & 0xFF; + buf[1] = (val >> 8) & 0xFF; + buf[2] = (val >> 16) & 0xFF; + buf[3] = (val >> 24) & 0xFF; +} + +// Note: this function differs from android_log_write_string8_len in that the length passed in +// should be treated as actual length and not max length. +int android_log_write_char_array(android_log_context ctx, const char* value, size_t actual_len) { + size_t needed; + ssize_t len = actual_len; + android_log_context_internal* context; + + context = (android_log_context_internal*)ctx; + if (!context || (kAndroidLoggerWrite != context->read_write_flag)) { + return -EBADF; + } + if (context->overflow) { + return -EIO; + } + if (!value) { + value = ""; + len = 0; + } + needed = sizeof(uint8_t) + sizeof(int32_t) + len; + if ((context->pos + needed) > MAX_EVENT_PAYLOAD) { + /* Truncate string for delivery */ + len = MAX_EVENT_PAYLOAD - context->pos - 1 - sizeof(int32_t); + if (len <= 0) { + context->overflow = true; + return -EIO; + } + } + context->count[context->list_nest_depth]++; + context->storage[context->pos + 0] = EVENT_TYPE_STRING; + copy4LE(&context->storage[context->pos + 1], len); + if (len) { + memcpy(&context->storage[context->pos + 5], value, len); + } + context->pos += needed; + return len; +} diff --git a/libstats/socket_q/statsd_writer.c b/libstats/socket_q/statsd_writer.c new file mode 100644 index 000000000..04d3b4644 --- /dev/null +++ b/libstats/socket_q/statsd_writer.c @@ -0,0 +1,285 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER; +static atomic_int dropped = 0; +static atomic_int log_error = 0; +static atomic_int atom_tag = 0; + +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); +static void statsdNoteDrop(); + +struct android_log_transport_write statsdLoggerWrite = { + .name = "statsd", + .sock = -EBADF, + .available = statsdAvailable, + .open = statsdOpen, + .close = statsdClose, + .write = statsdWrite, + .noteDrop = statsdNoteDrop, +}; + +/* log_init_lock assumed */ +static int statsdOpen() { + int i, ret = 0; + + i = atomic_load(&statsdLoggerWrite.sock); + if (i < 0) { + int flags = SOCK_DGRAM; +#ifdef SOCK_CLOEXEC + flags |= SOCK_CLOEXEC; +#endif +#ifdef SOCK_NONBLOCK + flags |= SOCK_NONBLOCK; +#endif + int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, flags, 0)); + if (sock < 0) { + ret = -errno; + } else { + int sndbuf = 1 * 1024 * 1024; // set max send buffer size 1MB + socklen_t bufLen = sizeof(sndbuf); + // SO_RCVBUF does not have an effect on unix domain socket, but SO_SNDBUF does. + // Proceed to connect even setsockopt fails. + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sndbuf, bufLen); + 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 void statsdNoteDrop(int error, int tag) { + atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed); + atomic_exchange_explicit(&log_error, error, memory_order_relaxed); + atomic_exchange_explicit(&atom_tag, tag, memory_order_relaxed); +} + +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; + + 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_long_t buffer; + header.id = LOG_ID_STATS; + // store the last log error in the tag field. This tag field is not used by statsd. + buffer.header.tag = atomic_load(&log_error); + buffer.payload.type = EVENT_TYPE_LONG; + // format: + // |atom_tag|dropped_count| + int64_t composed_long = atomic_load(&atom_tag); + // Send 2 int32's via an int64. + composed_long = ((composed_long << 32) | ((int64_t)snapshot)); + buffer.payload.data = composed_long; + + 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); + } + + return ret; +} diff --git a/libstats/socket_q/statsd_writer.h b/libstats/socket_q/statsd_writer.h new file mode 100644 index 000000000..fe2d37cbc --- /dev/null +++ b/libstats/socket_q/statsd_writer.h @@ -0,0 +1,45 @@ +/* + * 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 +#include +#include + +/** + * 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); + /* note one log drop */ + void (*noteDrop)(int error, int tag); +}; + +#endif // ANDROID_STATS_LOG_STATS_WRITER_H