diff options
22 files changed, 1688 insertions, 568 deletions
diff --git a/Android.mk b/Android.mk index 4a57020ab8d3..b5efd4752e0d 100644 --- a/Android.mk +++ b/Android.mk @@ -587,6 +587,17 @@ LOCAL_SRC_FILES += \ lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl \ lowpan/java/android/net/lowpan/ILowpanManager.aidl +# StatsLog generated functions +statslog_src_dir := $(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON)/statslog +gen := $(statslog_src_dir)/android/util/StatsLog.java +$(gen): PRIVATE_PATH := $(LOCAL_PATH) +$(gen): PRIVATE_CUSTOM_TOOL = $(HOST_OUT_EXECUTABLES)/stats-log-api-gen --java $@ +$(gen): $(HOST_OUT_EXECUTABLES)/stats-log-api-gen + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(gen) +statslog_src_dir:= +gen:= + # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk LOCAL_AIDL_INCLUDES += \ $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \ diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp new file mode 100644 index 000000000000..5586057a2e70 --- /dev/null +++ b/cmds/statsd/Android.bp @@ -0,0 +1,37 @@ +// +// Copyright (C) 2015 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. +// + +// ========================================================== +// Build the library for use on the host +// ========================================================== +cc_library_host_shared { + name: "libstats_proto_host", + srcs: [ + "src/stats_events.proto", + "src/stats_log.proto", + "src/statsd_config.proto", + ], + + shared_libs: [ + "libplatformprotos", + ], + + proto: { + type: "full", + export_proto_headers: true, + }, +} + diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 4c95007b0c44..d9c37ef0c9b8 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -14,22 +14,51 @@ LOCAL_PATH:= $(call my-dir) -# ================ -# proto static lib -# ================ -include $(CLEAR_VARS) - -LOCAL_MODULE := statsd_proto -LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := $(call all-proto-files-under, src) +statsd_common_src := \ + ../../core/java/android/os/IStatsCompanionService.aidl \ + ../../core/java/android/os/IStatsManager.aidl \ + src/stats_log.proto \ + src/statsd_config.proto \ + src/stats_events.proto \ + src/condition/CombinationConditionTracker.cpp \ + src/condition/condition_util.cpp \ + src/condition/SimpleConditionTracker.cpp \ + src/matchers/CombinationLogMatchingTracker.cpp \ + src/matchers/matcher_util.cpp \ + src/matchers/SimpleLogMatchingTracker.cpp \ + src/metrics/CountAnomalyTracker.cpp \ + src/metrics/CountMetricProducer.cpp \ + src/metrics/MetricsManager.cpp \ + src/metrics/metrics_manager_util.cpp \ + src/AnomalyMonitor.cpp \ + src/DropboxReader.cpp \ + src/DropboxWriter.cpp \ + src/KernelWakelockPuller.cpp \ + src/LogEntryPrinter.cpp \ + src/LogReader.cpp \ + src/StatsLogProcessor.cpp \ + src/StatsPullerManager.cpp \ + src/StatsService.cpp \ + src/stats_util.cpp \ + src/UidMap.cpp -LOCAL_PROTOC_FLAGS := -LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static +statsd_common_c_includes := \ + $(LOCAL_PATH)/src -include $(BUILD_STATIC_LIBRARY) +statsd_common_aidl_includes := \ + $(LOCAL_PATH)/../../core/java -STATSD_PROTO_INCLUDES := $(local-generated-sources-dir)/src/$(LOCAL_PATH) +statsd_common_shared_libraries := \ + libbase \ + libbinder \ + libcutils \ + libincident \ + liblog \ + libselinux \ + libutils \ + libservices \ + libandroidfw # ========= # statsd @@ -40,9 +69,8 @@ include $(CLEAR_VARS) LOCAL_MODULE := statsd LOCAL_SRC_FILES := \ - ../../core/java/android/os/IStatsCompanionService.aidl \ - ../../core/java/android/os/IStatsManager.aidl \ - $(call all-cpp-files-under,src) \ + $(statsd_common_src) \ + src/main.cpp LOCAL_CFLAGS += \ -Wall \ @@ -60,24 +88,12 @@ else LOCAL_CFLAGS += \ -Os endif +LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static -LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java -LOCAL_C_INCLUDES += $(LOCAL_PATH)/src \ - STATSD_PROTO_INCLUDES - -LOCAL_STATIC_LIBRARIES := statsd_proto +LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes) +LOCAL_C_INCLUDES += $(statsd_common_c_includes) -LOCAL_SHARED_LIBRARIES := \ - libbase \ - libbinder \ - libcutils \ - libincident \ - liblog \ - libselinux \ - libutils \ - libservices \ - libandroidfw \ - libprotobuf-cpp-lite \ +LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) LOCAL_MODULE_CLASS := EXECUTABLES @@ -85,6 +101,7 @@ LOCAL_MODULE_CLASS := EXECUTABLES include $(BUILD_EXECUTABLE) + # ============== # statsd_test # ============== @@ -95,8 +112,8 @@ LOCAL_MODULE := statsd_test LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_MODULE_TAGS := tests -LOCAL_C_INCLUDES += $(LOCAL_PATH)/src \ - STATSD_PROTO_INCLUDES +LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes) +LOCAL_C_INCLUDES += $(statsd_common_c_includes) LOCAL_CFLAGS += \ -Wall \ @@ -107,38 +124,25 @@ LOCAL_CFLAGS += \ -Wno-unused-parameter LOCAL_SRC_FILES := \ - src/stats_log.proto \ - src/statsd_config.proto \ - ../../core/java/android/os/IStatsCompanionService.aidl \ - ../../core/java/android/os/IStatsManager.aidl \ - src/StatsService.cpp \ - src/AnomalyMonitor.cpp \ - src/stats_util.cpp \ - src/LogEntryPrinter.cpp \ - src/LogReader.cpp \ - src/matchers/matcher_util.cpp \ - src/condition/SimpleConditionTracker.cpp \ - src/condition/CombinationConditionTracker.cpp \ - src/matchers/SimpleLogMatchingTracker.cpp \ - src/matchers/CombinationLogMatchingTracker.cpp \ - src/metrics/metrics_manager_util.cpp \ - src/metrics/CountMetricProducer.cpp \ - src/metrics/CountAnomalyTracker.cpp \ - src/condition/condition_util.cpp \ - src/UidMap.cpp \ - $(call all-cpp-files-under, tests) \ + $(statsd_common_src) \ + tests/indexed_priority_queue_test.cpp \ + tests/LogReader_test.cpp \ + tests/MetricsManager_test.cpp \ + tests/UidMap_test.cpp \ + tests/LogEntryMatcher_test.cpp \ + tests/AnomalyMonitor_test.cpp \ + tests/ConditionTracker_test.cpp LOCAL_STATIC_LIBRARIES := \ - libgmock \ - statsd_proto \ + libgmock -LOCAL_SHARED_LIBRARIES := \ - libbase \ - libbinder \ - libcutils \ - liblog \ - libselinux \ - libutils \ - libprotobuf-cpp-lite \ +LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite + +statsd_common_src:= +statsd_common_aidl_includes:= +statsd_common_c_includes:= include $(BUILD_NATIVE_TEST) + diff --git a/cmds/statsd/src/LogEntryPrinter.cpp b/cmds/statsd/src/LogEntryPrinter.cpp index 63465b094da2..3b6f6791d7f4 100644 --- a/cmds/statsd/src/LogEntryPrinter.cpp +++ b/cmds/statsd/src/LogEntryPrinter.cpp @@ -20,6 +20,11 @@ #include <log/logprint.h> #include <utils/Errors.h> +#include "matchers/matcher_util.h" + +#define PRINT_WITH_LIBLOG 0 +#define PRINT_WITH_LOG_EVENT_WRAPPER 1 + using namespace android; namespace android { @@ -44,16 +49,24 @@ LogEntryPrinter::~LogEntryPrinter() { } void LogEntryPrinter::OnLogEvent(const log_msg& msg) { - status_t err; - AndroidLogEntry entry; - char buf[1024]; - - err = android_log_processBinaryLogBuffer(&(const_cast<log_msg*>(&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); + if (PRINT_WITH_LIBLOG) { + status_t err; + AndroidLogEntry entry; + char buf[1024]; + + err = android_log_processBinaryLogBuffer(&(const_cast<log_msg*>(&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); + } + } + + if (PRINT_WITH_LOG_EVENT_WRAPPER) { + LogEventWrapper event = parseLogEvent(msg); + printf("event: %s\n", event.toString().c_str()); fflush(stdout); } } diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp index 557c03228436..3308f3aeb318 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -26,8 +26,11 @@ #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "stats_util.h" +#include <sstream> + using std::set; using std::string; +using std::ostringstream; using std::unordered_map; using std::vector; @@ -35,6 +38,42 @@ namespace android { namespace os { namespace statsd { +string LogEventWrapper::toString() const { + std::ostringstream result; + result << "{ " << timestamp_ns << " (" << tagId << ")"; + for (int index = 1; ; index++) { + auto intVal = intMap.find(index); + auto strVal = strMap.find(index); + auto boolVal = boolMap.find(index); + auto floatVal = floatMap.find(index); + if (intVal != intMap.end()) { + result << " "; + result << std::to_string(index); + result << "->"; + result << std::to_string(intVal->second); + } else if (strVal != strMap.end()) { + result << " "; + result << std::to_string(index); + result << "->"; + result << strVal->second; + } else if (boolVal != boolMap.end()) { + result << " "; + result << std::to_string(index); + result << "->"; + result << std::to_string(boolVal->second); + } else if (floatVal != floatMap.end()) { + result << " "; + result << std::to_string(index); + result << "->"; + result << std::to_string(floatVal->second); + } else { + break; + } + } + result << " }"; + return result.str(); +} + LogEventWrapper parseLogEvent(log_msg msg) { LogEventWrapper wrapper; wrapper.timestamp_ns = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec; @@ -48,38 +87,32 @@ LogEventWrapper parseLogEvent(log_msg msg) { if (context) { memset(&elem, 0, sizeof(elem)); - size_t index = 0; - int32_t key = -1; + // TODO: The log is actually structured inside one list. This is convenient + // because we'll be able to use it to put the attribution (WorkSource) block first + // without doing our own tagging scheme. Until that change is in, just drop the + // list-related log elements and the order we get there is our index-keyed data + // structure. + int32_t key = 1; do { elem = android_log_read_next(context); switch ((int)elem.type) { case EVENT_TYPE_INT: - if (index % 2 == 0) { - key = elem.data.int32; - } else { - wrapper.intMap[key] = elem.data.int32; - } - index++; + wrapper.intMap[key] = elem.data.int32; + key++; break; case EVENT_TYPE_FLOAT: - if (index % 2 == 1) { - wrapper.floatMap[key] = elem.data.float32; - } - index++; + wrapper.floatMap[key] = elem.data.float32; + key++; break; case EVENT_TYPE_STRING: - if (index % 2 == 1) { - // without explicit calling string() constructor, there will be an - // additional 0 in the end of the string. - wrapper.strMap[key] = string(elem.data.string); - } - index++; + // without explicit calling string() constructor, there will be an + // additional 0 in the end of the string. + wrapper.strMap[key] = string(elem.data.string); + key++; break; case EVENT_TYPE_LONG: - if (index % 2 == 1) { - wrapper.intMap[key] = elem.data.int64; - } - index++; + wrapper.intMap[key] = elem.data.int64; + key++; break; case EVENT_TYPE_LIST: break; @@ -91,10 +124,6 @@ LogEventWrapper parseLogEvent(log_msg msg) { elem.complete = true; break; } - - if (elem.complete) { - break; - } } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete); android_log_destroy(&context); diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h index 6d8e762382f0..ac17bbe6e6e1 100644 --- a/cmds/statsd/src/matchers/matcher_util.h +++ b/cmds/statsd/src/matchers/matcher_util.h @@ -20,6 +20,7 @@ #include <log/log_read.h> #include <log/logprint.h> #include <set> +#include <string> #include <unordered_map> #include <vector> #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" @@ -36,6 +37,8 @@ typedef struct { std::unordered_map<int, std::string> strMap; std::unordered_map<int, bool> boolMap; std::unordered_map<int, float> floatMap; + + std::string toString() const; } LogEventWrapper; enum MatchingState { diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto index 1e1789574676..cd00ba8e8898 100644 --- a/cmds/statsd/src/stats_events.proto +++ b/cmds/statsd/src/stats_events.proto @@ -15,49 +15,116 @@ */ syntax = "proto2"; -option optimize_for = LITE_RUNTIME; +// TODO: Not the right package and class name package android.os.statsd; - option java_package = "com.android.os"; option java_outer_classname = "StatsEventProto"; +/** + * The master event class. This message defines all of the available + * raw stats log events from the Android system, also known as "atoms." + * + * This field contains a single oneof with all of the available messages. + * The stats-log-api-gen tool runs as part of the Android build and + * generates the android.util.StatsLog class, which contains the constants + * and methods that Android uses to log. + * + * This StatsEvent class is not actually built into the Android system. + * Instead, statsd on Android constructs these messages synthetically, + * in the format defined here and in stats_log.proto. + */ message StatsEvent { - oneof event { - // Screen state change. - ScreenStateChange screen_state_change = 2; - // Process state change. - ProcessStateChange process_state_change = 1112; - } + oneof event { + ScreenStateChanged screen_state_changed = 1; + ProcessStateChanged process_state_changed = 2; + WakeLockChanged wakelock_changed = 3; + } +} + +/** + * A WorkSource represents the chained attribution of applications that + * resulted in a particular bit of work being done. + */ +message WorkSource { + // TODO +} + +/* + * ***************************************************************************** + * Below are all of the individual atoms that are logged by Android via statsd + * and Westworld. + * + * RULES: + * - The field ids for each atom must start at 1, and count upwards by 1. + * Skipping field ids is not allowed. + * - These form an API, so renaming, renumbering or removing fields is + * not allowed between android releases. (This is not currently enforced, + * but there will be a tool to enforce this restriction). + * - The types must be built-in protocol buffer types, namely, no sub-messages + * are allowed (yet). The bytes type is also not allowed. + * - The CamelCase name of the message type should match the + * underscore_separated name as defined in StatsEvent. + * - If an atom represents work that can be attributed to an app, there can + * be exactly one WorkSource field. It must be field number 1. + * - A field that is a uid should be a string field, tagged with the [xxx] + * annotation. The generated code on android will be represented by UIDs, + * and those UIDs will be translated in xxx to those strings. + * + * CONVENTIONS: + * - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange + * - If there is a UID, it goes first. Think in an object-oriented fashion. + * ***************************************************************************** + */ + +/** + * Logs when the screen state changes. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java + */ +message ScreenStateChanged { + // TODO: Use the real screen state. + enum State { + STATE_UNKNOWN = 0; + STATE_OFF = 1; + STATE_ON = 2; + STATE_DOZE = 3; + STATE_DOZE_SUSPEND = 4; + STATE_VR = 5; + } + // New screen state. + optional State display_state = 1; } -// Logs changes in screen state. This event is logged in -// frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java -message ScreenStateChange { - // Screen state enums follow the values defined in below file. - // frameworks/base/core/java/android/view/Display.java - enum State { - STATE_UNKNOWN = 0; - STATE_OFF = 1; - STATE_ON = 2; - STATE_DOZE = 3; - STATE_DOZE_SUSPEND = 4; - STATE_VR = 5; - } - // New screen state. - optional State display_state = 1; +/** + * Logs that the state of a process state, as per the activity manager has changed. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java + */ +message ProcessStateChanged { + // TODO: Use the real (mapped) process states. + optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation + + // The state. + optional int32 state = 2; } -// Logs changes in process state. This event is logged in -// frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java -message ProcessStateChange { - // Type of process event. - enum State { - START = 1; - CRASH = 2; - } - optional State state = 1; - - // UID associated with the package. - optional int32 uid = 2; +/** + * Logs that the state of a wakelock has changed. + * + * Logged from: + * TODO + */ +message WakeLockChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). + optional string tag = 2; + + // TODO: Use a constant instead of boolean? + optional bool state = 3; } + diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java deleted file mode 100644 index 0be1a8cfabae..000000000000 --- a/core/java/android/util/StatsLog.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2007 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.util; - -/** - * Logging access for platform metrics. - * - * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})! - * These diagnostic stats are for system integrators, not application authors. - * - * <p>Stats use integer tag codes. - * They carry a payload of one or more int, long, or String values. - * @hide - */ -public class StatsLog { - /** @hide */ public StatsLog() {} - - private static final String TAG = "StatsLog"; - - // We assume that the native methods deal with any concurrency issues. - - /** - * Records an stats log message. - * @param tag The stats type tag code - * @param value A value to log - * @return The number of bytes written - */ - public static native int writeInt(int tag, int value); - - /** - * Records an stats log message. - * @param tag The stats type tag code - * @param value A value to log - * @return The number of bytes written - */ - public static native int writeLong(int tag, long value); - - /** - * Records an stats log message. - * @param tag The stats type tag code - * @param value A value to log - * @return The number of bytes written - */ - public static native int writeFloat(int tag, float value); - - /** - * Records an stats log message. - * @param tag The stats type tag code - * @param str A value to log - * @return The number of bytes written - */ - public static native int writeString(int tag, String str); - - /** - * Records an stats log message. - * @param tag The stats type tag code - * @param list A list of values to log. All values should - * be of type int, long, float or String. - * @return The number of bytes written - */ - public static native int writeArray(int tag, Object... list); -} diff --git a/core/java/android/util/StatsLogKey.java b/core/java/android/util/StatsLogKey.java deleted file mode 100644 index 9ad0a23d00d6..000000000000 --- a/core/java/android/util/StatsLogKey.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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. - */ - -// THIS FILE IS AUTO-GENERATED. -// DO NOT MODIFY. - -package android.util; - -/** @hide */ -public class StatsLogKey { - private StatsLogKey() {} - - /** Constants for android.os.statsd.ScreenStateChange. */ - - /** display_state */ - public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE = 1; - - /** Constants for android.os.statsd.ProcessStateChange. */ - - /** state */ - public static final int PROCESS_STATE_CHANGE__STATE = 1; - - /** uid */ - public static final int PROCESS_STATE_CHANGE__UID = 2; - - /** package_name */ - public static final int PROCESS_STATE_CHANGE__PACKAGE_NAME = 1002; - - /** package_version */ - public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION = 3; - - /** package_version_string */ - public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION_STRING = 4; - -} diff --git a/core/java/android/util/StatsLogTag.java b/core/java/android/util/StatsLogTag.java deleted file mode 100644 index 5e5a82870aa0..000000000000 --- a/core/java/android/util/StatsLogTag.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -// THIS FILE IS AUTO-GENERATED. -// DO NOT MODIFY. - -package android.util; - -/** @hide */ -public class StatsLogTag { - private StatsLogTag() {} - - /** android.os.statsd.ScreenStateChange. */ - public static final int SCREEN_STATE_CHANGE = 2; - - /** android.os.statsd.ProcessStateChange. */ - public static final int PROCESS_STATE_CHANGE = 1112; - -} diff --git a/core/java/android/util/StatsLogValue.java b/core/java/android/util/StatsLogValue.java deleted file mode 100644 index 05b9d9333bef..000000000000 --- a/core/java/android/util/StatsLogValue.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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. - */ - -// THIS FILE IS AUTO-GENERATED. -// DO NOT MODIFY. - -package android.util; - -/** @hide */ -public class StatsLogValue { - private StatsLogValue() {} - - /** Constants for android.os.statsd.ScreenStateChange. */ - - /** display_state: STATE_UNKNOWN */ - public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_UNKNOWN = 0; - - /** display_state: STATE_OFF */ - public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF = 1; - - /** display_state: STATE_ON */ - public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON = 2; - - /** display_state: STATE_DOZE */ - public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE = 3; - - /** display_state: STATE_DOZE_SUSPEND */ - public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE_SUSPEND = 4; - - /** display_state: STATE_VR */ - public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_VR = 5; - - /** Constants for android.os.statsd.ProcessStateChange. */ - - /** state: START */ - public static final int PROCESS_STATE_CHANGE__STATE__START = 1; - - /** state: CRASH */ - public static final int PROCESS_STATE_CHANGE__STATE__CRASH = 2; - -} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 256b920ee34a..928626b21475 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -1,3 +1,13 @@ + +genrule { + name: "android_util_StatsLog.cpp", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --jni $(genDir)/android_util_StatsLog.cpp", + out: [ + "android_util_StatsLog.cpp", + ], +} + cc_library_shared { name: "libandroid_runtime", @@ -104,7 +114,6 @@ cc_library_shared { "android_nio_utils.cpp", "android_util_AssetManager.cpp", "android_util_Binder.cpp", - "android_util_StatsLog.cpp", "android_util_EventLog.cpp", "android_util_MemoryIntArray.cpp", "android_util_Log.cpp", @@ -271,11 +280,13 @@ cc_library_shared { "libhwbinder", "libvintf", "libnativewindow", - "libhwui", "libdl", + "libstatslog", ], + generated_sources: ["android_util_StatsLog.cpp"], + local_include_dirs: ["android/graphics"], export_include_dirs: [ ".", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 820933b02e82..da6d5aa88d47 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1324,10 +1324,10 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit), REG_JNI(register_android_os_SystemClock), REG_JNI(register_android_util_EventLog), - REG_JNI(register_android_util_StatsLog), REG_JNI(register_android_util_Log), REG_JNI(register_android_util_MemoryIntArray), REG_JNI(register_android_util_PathParser), + REG_JNI(register_android_util_StatsLog), REG_JNI(register_android_app_admin_SecurityLog), REG_JNI(register_android_content_AssetManager), REG_JNI(register_android_content_StringBlock), diff --git a/core/jni/android_util_StatsLog.cpp b/core/jni/android_util_StatsLog.cpp deleted file mode 100644 index c992365094f7..000000000000 --- a/core/jni/android_util_StatsLog.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2007-2014 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 <fcntl.h> -#include <log/log_event_list.h> - -#include <log/log.h> - -#include <nativehelper/JNIHelp.h> -#include "core_jni_helpers.h" -#include "jni.h" - -#define UNUSED __attribute__((__unused__)) - -namespace android { - -static jclass gCollectionClass; -static jmethodID gCollectionAddID; - -static jclass gIntegerClass; -static jfieldID gIntegerValueID; - -static jclass gLongClass; -static jfieldID gLongValueID; - -static jclass gFloatClass; -static jfieldID gFloatValueID; - -static jclass gStringClass; - -/* - * In class android.util.StatsLog: - * static native int writeInt(int tag, int value) - */ -static jint android_util_StatsLog_write_Integer(JNIEnv* env UNUSED, - jobject clazz UNUSED, - jint tag, jint value) -{ - android_log_event_list ctx(tag); - ctx << (int32_t)value; - return ctx.write(LOG_ID_STATS); -} - -/* - * In class android.util.StatsLog: - * static native int writeLong(long tag, long value) - */ -static jint android_util_StatsLog_write_Long(JNIEnv* env UNUSED, - jobject clazz UNUSED, - jint tag, jlong value) -{ - android_log_event_list ctx(tag); - ctx << (int64_t)value; - return ctx.write(LOG_ID_STATS); -} - -/* - * In class android.util.StatsLog: - * static native int writeFloat(long tag, float value) - */ -static jint android_util_StatsLog_write_Float(JNIEnv* env UNUSED, - jobject clazz UNUSED, - jint tag, jfloat value) -{ - android_log_event_list ctx(tag); - ctx << (float)value; - return ctx.write(LOG_ID_STATS); -} - -/* - * In class android.util.StatsLog: - * static native int writeString(int tag, String value) - */ -static jint android_util_StatsLog_write_String(JNIEnv* env, - jobject clazz UNUSED, - jint tag, jstring value) { - android_log_event_list ctx(tag); - // Don't throw NPE -- I feel like it's sort of mean for a logging function - // to be all crashy if you pass in NULL -- but make the NULL value explicit. - if (value != NULL) { - const char *str = env->GetStringUTFChars(value, NULL); - ctx << str; - env->ReleaseStringUTFChars(value, str); - } else { - ctx << "NULL"; - } - return ctx.write(LOG_ID_STATS); -} - -/* - * In class android.util.StatsLog: - * static native int writeArray(long tag, Object... value) - */ -static jint android_util_StatsLog_write_Array(JNIEnv* env, jobject clazz, - jint tag, jobjectArray value) { - android_log_event_list ctx(tag); - - if (value == NULL) { - ctx << "[NULL]"; - return ctx.write(LOG_ID_STATS); - } - - jsize copied = 0, num = env->GetArrayLength(value); - for (; copied < num && copied < 255; ++copied) { - if (ctx.status()) break; - jobject item = env->GetObjectArrayElement(value, copied); - if (item == NULL) { - ctx << "NULL"; - } else if (env->IsInstanceOf(item, gStringClass)) { - const char *str = env->GetStringUTFChars((jstring) item, NULL); - ctx << str; - env->ReleaseStringUTFChars((jstring) item, str); - } else if (env->IsInstanceOf(item, gIntegerClass)) { - ctx << (int32_t)env->GetIntField(item, gIntegerValueID); - } else if (env->IsInstanceOf(item, gLongClass)) { - ctx << (int64_t)env->GetLongField(item, gLongValueID); - } else if (env->IsInstanceOf(item, gFloatClass)) { - ctx << (float)env->GetFloatField(item, gFloatValueID); - } else { - jniThrowException(env, - "java/lang/IllegalArgumentException", - "Invalid payload item type"); - return -1; - } - env->DeleteLocalRef(item); - } - return ctx.write(LOG_ID_STATS); -} - -/* - * JNI registration. - */ -static const JNINativeMethod gRegisterMethods[] = { - /* name, signature, funcPtr */ - { "writeInt", "(II)I", (void*) android_util_StatsLog_write_Integer }, - { "writeLong", "(IJ)I", (void*) android_util_StatsLog_write_Long }, - { "writeFloat", "(IF)I", (void*) android_util_StatsLog_write_Float }, - { "writeString", - "(ILjava/lang/String;)I", - (void*) android_util_StatsLog_write_String - }, - { "writeArray", - "(I[Ljava/lang/Object;)I", - (void*) android_util_StatsLog_write_Array - }, -}; - -static struct { const char *name; jclass *clazz; } gClasses[] = { - { "java/lang/Integer", &gIntegerClass }, - { "java/lang/Long", &gLongClass }, - { "java/lang/Float", &gFloatClass }, - { "java/lang/String", &gStringClass }, - { "java/util/Collection", &gCollectionClass }, -}; - -static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = { - { &gIntegerClass, "value", "I", &gIntegerValueID }, - { &gLongClass, "value", "J", &gLongValueID }, - { &gFloatClass, "value", "F", &gFloatValueID }, -}; - -static struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = { - { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID }, -}; - -int register_android_util_StatsLog(JNIEnv* env) { - for (int i = 0; i < NELEM(gClasses); ++i) { - jclass clazz = FindClassOrDie(env, gClasses[i].name); - *gClasses[i].clazz = MakeGlobalRefOrDie(env, clazz); - } - - for (int i = 0; i < NELEM(gFields); ++i) { - *gFields[i].id = GetFieldIDOrDie(env, - *gFields[i].c, gFields[i].name, gFields[i].ft); - } - - for (int i = 0; i < NELEM(gMethods); ++i) { - *gMethods[i].id = GetMethodIDOrDie(env, - *gMethods[i].c, gMethods[i].name, gMethods[i].mt); - } - - return RegisterMethodsOrDie( - env, - "android/util/StatsLog", - gRegisterMethods, NELEM(gRegisterMethods)); -} - -}; // namespace android diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 7c9cd00e9c41..68ed9aecf31b 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -297,26 +297,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub void noteProcessStart(String name, int uid) { synchronized (mStats) { mStats.noteProcessStartLocked(name, uid); - - // TODO: remove this once we figure out properly where and how - // PROCESS_EVENT = 1112 - // KEY_STATE = 1 - // KEY_PACKAGE_NAME: 1002 - // KEY_UID: 2 - StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid); } } void noteProcessCrash(String name, int uid) { synchronized (mStats) { mStats.noteProcessCrashLocked(name, uid); - - // TODO: remove this once we figure out properly where and how - // PROCESS_EVENT = 1112 - // KEY_STATE = 1 - // KEY_PACKAGE_NAME: 1002 - // KEY_UID: 2 - StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid); } } @@ -334,6 +320,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub void noteUidProcessState(int uid, int state) { synchronized (mStats) { + // TODO: remove this once we figure out properly where and how + StatsLog.write(StatsLog.PROCESS_STATE_CHANGED, uid, state); + mStats.noteUidProcessStateLocked(uid, state); } } @@ -548,12 +537,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub enforceCallingPermission(); if (DBG) Slog.d(TAG, "begin noteScreenState"); synchronized (mStats) { - mStats.noteScreenStateLocked(state); // TODO: remove this once we figure out properly where and how - // SCREEN_EVENT = 2 - // KEY_STATE: 1 - // State value: state. We can change this to our own def later. - StatsLog.writeArray(2, 1, state); + StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, state); + + mStats.noteScreenStateLocked(state); } if (DBG) Slog.d(TAG, "end noteScreenState"); } diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp index a8002417b916..b2ee99c2c74c 100644 --- a/tools/bit/make.cpp +++ b/tools/bit/make.cpp @@ -182,7 +182,7 @@ read_modules(const string& buildOut, const string& device, map<string,Module>* r for (ssize_t i = module.classes.size() - 1; i >= 0; i--) { string cl = module.classes[i]; if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES" - || cl == "APPS")) { + || cl == "APPS" || cl == "NATIVE_TESTS")) { module.classes.erase(module.classes.begin() + i); } } diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp new file mode 100644 index 000000000000..a910c628a9e6 --- /dev/null +++ b/tools/stats_log_api_gen/Android.bp @@ -0,0 +1,98 @@ +// +// 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. +// + +// ========================================================== +// Build the host executable: stats-log-api-gen +// ========================================================== +cc_binary_host { + name: "stats-log-api-gen", + srcs: [ + "Collation.cpp", + "main.cpp", + ], + + shared_libs: [ + "libstats_proto_host", + "libprotobuf-cpp-full", + ], + + proto: { + type: "full", + }, +} + +// ========================================================== +// Build the host test executable: stats-log-api-gen +// ========================================================== +cc_test_host { + name: "stats-log-api-gen-test", + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-g", + "-DUNIT_TEST", + ], + srcs: [ + "Collation.cpp", + "test_collation.cpp", + "test.proto", + ], + + static_libs: [ + "libgmock_host", + ], + + shared_libs: [ + "libstats_proto_host", + ], + + proto: { + type: "full", + }, +} + +// ========================================================== +// Native library +// ========================================================== +genrule { + name: "statslog.h", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog.h", + out: [ + "statslog.h", + ], +} + +genrule { + name: "statslog.cpp", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog.cpp", + out: [ + "statslog.cpp", + ], +} + +cc_library_shared { + name: "libstatslog", + generated_sources: ["statslog.cpp"], + generated_headers: ["statslog.h"], + export_generated_headers: ["statslog.h"], + shared_libs: [ + "liblog", + ], +} + diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp new file mode 100644 index 000000000000..5d2926821164 --- /dev/null +++ b/tools/stats_log_api_gen/Collation.cpp @@ -0,0 +1,265 @@ +/* + * 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 "Collation.h" + +#include <stdio.h> +#include <map> + +namespace android { +namespace stats_log_api_gen { + +using google::protobuf::FieldDescriptor; +using google::protobuf::FileDescriptor; +using google::protobuf::SourceLocation; +using std::map; + + +// +// AtomDecl class +// + +AtomDecl::AtomDecl() + :code(0), + name() +{ +} + +AtomDecl::AtomDecl(const AtomDecl& that) + :code(that.code), + name(that.name), + message(that.message), + fields(that.fields) +{ +} + +AtomDecl::AtomDecl(int c, const string& n, const string& m) + :code(c), + name(n), + message(m) +{ +} + +AtomDecl::~AtomDecl() +{ +} + + +/** + * Print an error message for a FieldDescriptor, including the file name and line number. + */ +static void +print_error(const FieldDescriptor* field, const char* format, ...) +{ + const Descriptor* message = field->containing_type(); + const FileDescriptor* file = message->file(); + + SourceLocation loc; + if (field->GetSourceLocation(&loc)) { + // TODO: this will work if we can figure out how to pass --include_source_info to protoc + fprintf(stderr, "%s:%d: ", file->name().c_str(), loc.start_line); + } else { + fprintf(stderr, "%s: ", file->name().c_str()); + } + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end (args); +} + +/** + * Convert a protobuf type into a java type. + */ +static java_type_t +java_type(const FieldDescriptor* field) +{ + int protoType = field->type(); + switch (protoType) { + case FieldDescriptor::TYPE_DOUBLE: + return JAVA_TYPE_DOUBLE; + case FieldDescriptor::TYPE_FLOAT: + return JAVA_TYPE_FLOAT; + case FieldDescriptor::TYPE_INT64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_UINT64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_INT32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_FIXED64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_FIXED32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_BOOL: + return JAVA_TYPE_BOOLEAN; + case FieldDescriptor::TYPE_STRING: + return JAVA_TYPE_STRING; + case FieldDescriptor::TYPE_GROUP: + return JAVA_TYPE_UNKNOWN; + case FieldDescriptor::TYPE_MESSAGE: + // TODO: not the final package name + if (field->message_type()->full_name() == "android.os.statsd.WorkSource") { + return JAVA_TYPE_WORK_SOURCE; + } else { + return JAVA_TYPE_OBJECT; + } + case FieldDescriptor::TYPE_BYTES: + return JAVA_TYPE_BYTE_ARRAY; + case FieldDescriptor::TYPE_UINT32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_ENUM: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_SFIXED32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_SFIXED64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_SINT32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_SINT64: + return JAVA_TYPE_LONG; + default: + return JAVA_TYPE_UNKNOWN; + } +} + +/** + * Gather the info about the atoms. + */ +int +collate_atoms(const Descriptor* descriptor, Atoms* atoms) +{ + int errorCount = 0; + const bool dbg = false; + + for (int i=0; i<descriptor->field_count(); i++) { + const FieldDescriptor* atomField = descriptor->field(i); + + if (dbg) { + printf(" %s (%d)\n", atomField->name().c_str(), atomField->number()); + } + + // StatsEvent only has one oneof, which contains only messages. Don't allow other types. + if (atomField->type() != FieldDescriptor::TYPE_MESSAGE) { + print_error(atomField, + "Bad type for atom. StatsEvent can only have message type fields: %s\n", + atomField->name().c_str()); + errorCount++; + continue; + } + + const Descriptor* atom = atomField->message_type(); + + // Build a sorted list of the fields. Descriptor has them in source file order. + map<int,const FieldDescriptor*> fields; + for (int j=0; j<atom->field_count(); j++) { + const FieldDescriptor* field = atom->field(j); + fields[field->number()] = field; + } + + // Check that the parameters start at 1 and go up sequentially. + int expectedNumber = 1; + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const int number = it->first; + const FieldDescriptor* field = it->second; + if (number != expectedNumber) { + print_error(field, "Fields must be numbered consecutively starting at 1:" + " '%s' is %d but should be %d\n", + field->name().c_str(), number, expectedNumber); + errorCount++; + expectedNumber = number; + continue; + } + expectedNumber++; + } + + // Check that only allowed types are present. Remove any invalid ones. + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const FieldDescriptor* field = it->second; + + java_type_t javaType = java_type(field); + + if (javaType == JAVA_TYPE_UNKNOWN) { + print_error(field, "Unkown type for field: %s\n", field->name().c_str()); + errorCount++; + continue; + } else if (javaType == JAVA_TYPE_OBJECT) { + // Allow WorkSources, but only at position 1. + print_error(field, "Message type not allowed for field: %s\n", + field->name().c_str()); + errorCount++; + continue; + } else if (javaType == JAVA_TYPE_BYTE_ARRAY) { + print_error(field, "Raw bytes type not allowed for field: %s\n", + field->name().c_str()); + errorCount++; + continue; + } + + } + + // Check that if there's a WorkSource, it's at position 1. + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + int number = it->first; + if (number != 1) { + const FieldDescriptor* field = it->second; + java_type_t javaType = java_type(field); + if (javaType == JAVA_TYPE_WORK_SOURCE) { + print_error(field, "WorkSource fields must have field id 1, in message: '%s'\n", + atom->name().c_str()); + errorCount++; + } + } + } + + AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name()); + + // Build the type signature + vector<java_type_t> signature; + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const FieldDescriptor* field = it->second; + java_type_t javaType = java_type(field); + + atomDecl.fields.push_back(AtomField(field->name(), javaType)); + signature.push_back(javaType); + } + + atoms->signatures.insert(signature); + atoms->decls.insert(atomDecl); + } + + if (dbg) { + printf("signatures = [\n"); + for (set<vector<java_type_t>>::const_iterator it = atoms->signatures.begin(); + it != atoms->signatures.end(); it++) { + printf(" "); + for (vector<java_type_t>::const_iterator jt = it->begin(); jt != it->end(); jt++) { + printf(" %d", (int)*jt); + } + printf("\n"); + } + printf("]\n"); + } + + return errorCount; +} + +} // namespace stats_log_api_gen +} // namespace android + + diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h new file mode 100644 index 000000000000..50af7ea2d335 --- /dev/null +++ b/tools/stats_log_api_gen/Collation.h @@ -0,0 +1,100 @@ +/* + * 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 ANDROID_STATS_LOG_API_GEN_COLLATION_H +#define ANDROID_STATS_LOG_API_GEN_COLLATION_H + + +#include <google/protobuf/descriptor.h> + +#include <set> +#include <vector> + +namespace android { +namespace stats_log_api_gen { + +using std::set; +using std::string; +using std::vector; +using google::protobuf::Descriptor; + +/** + * The types for atom parameters. + */ +typedef enum { + JAVA_TYPE_UNKNOWN = 0, + + JAVA_TYPE_WORK_SOURCE = 1, + JAVA_TYPE_BOOLEAN = 2, + JAVA_TYPE_INT = 3, + JAVA_TYPE_LONG = 4, + JAVA_TYPE_FLOAT = 5, + JAVA_TYPE_DOUBLE = 6, + JAVA_TYPE_STRING = 7, + + JAVA_TYPE_OBJECT = -1, + JAVA_TYPE_BYTE_ARRAY = -2, +} java_type_t; + + +/** + * The name and type for an atom field. + */ +struct AtomField { + string name; + java_type_t javaType; + + inline AtomField() :name(), javaType(JAVA_TYPE_UNKNOWN) {} + inline AtomField(const AtomField& that) :name(that.name), javaType(that.javaType) {} + inline AtomField(string n, java_type_t jt) :name(n), javaType(jt) {} + inline ~AtomField() {} +}; + +/** + * The name and code for an atom. + */ +struct AtomDecl { + int code; + string name; + + string message; + vector<AtomField> fields; + + AtomDecl(); + AtomDecl(const AtomDecl& that); + AtomDecl(int code, const string& name, const string& message); + ~AtomDecl(); + + inline bool operator<(const AtomDecl& that) const { + return (code == that.code) ? (name < that.name) : (code < that.code); + } +}; + +struct Atoms { + set<vector<java_type_t>> signatures; + set<AtomDecl> decls; +}; + +/** + * Gather the information about the atoms. Returns the number of errors. + */ +int collate_atoms(const Descriptor* descriptor, Atoms* atoms); + +} // namespace stats_log_api_gen +} // namespace android + + +#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp new file mode 100644 index 000000000000..aaea4f6fe749 --- /dev/null +++ b/tools/stats_log_api_gen/main.cpp @@ -0,0 +1,623 @@ + + +#include "Collation.h" + +#include "frameworks/base/cmds/statsd/src/stats_events.pb.h" + +#include <set> +#include <vector> + +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +using namespace google::protobuf; +using namespace std; + +namespace android { +namespace stats_log_api_gen { + +using android::os::statsd::StatsEvent; + +// TODO: Support WorkSources + +/** + * Turn lower and camel case into upper case with underscores. + */ +static string +make_constant_name(const string& str) +{ + string result; + const int N = str.size(); + bool underscore_next = false; + for (int i=0; i<N; i++) { + char c = str[i]; + if (c >= 'A' && c <= 'Z') { + if (underscore_next) { + result += '_'; + underscore_next = false; + } + } else if (c >= 'a' && c <= 'z') { + c = 'A' + c - 'a'; + underscore_next = true; + } else if (c == '_') { + underscore_next = false; + } + result += c; + } + return result; +} + +static const char* +cpp_type_name(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "bool"; + case JAVA_TYPE_INT: + return "int32_t"; + case JAVA_TYPE_LONG: + return "int64_t"; + case JAVA_TYPE_FLOAT: + return "float"; + case JAVA_TYPE_DOUBLE: + return "double"; + case JAVA_TYPE_STRING: + return "char const*"; + default: + return "UNKNOWN"; + } +} + +static const char* +java_type_name(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "boolean"; + case JAVA_TYPE_INT: + return "int"; + case JAVA_TYPE_LONG: + return "long"; + case JAVA_TYPE_FLOAT: + return "float"; + case JAVA_TYPE_DOUBLE: + return "double"; + case JAVA_TYPE_STRING: + return "java.lang.String"; + default: + return "UNKNOWN"; + } +} + +static int +write_stats_log_cpp(FILE* out, const Atoms& atoms) +{ + int errorCount; + + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + + fprintf(out, "#include <log/log_event_list.h>\n"); + fprintf(out, "#include <log/log.h>\n"); + fprintf(out, "#include <statslog.h>\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "namespace util {\n"); + + // Print write methods + fprintf(out, "\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + int argIndex; + + fprintf(out, "void\n"); + fprintf(out, "stats_write(int code"); + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ")\n"); + + fprintf(out, "{\n"); + argIndex = 1; + fprintf(out, " android_log_event_list event(code);\n"); + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " if (arg%d == NULL) {\n", argIndex); + fprintf(out, " arg%d = \"\";\n", argIndex); + fprintf(out, " }\n"); + } + fprintf(out, " event << arg%d;\n", argIndex); + argIndex++; + } + + fprintf(out, " event.write(LOG_ID_STATS);\n"); + fprintf(out, "}\n"); + fprintf(out, "\n"); + } + + // Print footer + fprintf(out, "\n"); + fprintf(out, "} // namespace util\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + + +static int +write_stats_log_header(FILE* out, const Atoms& atoms) +{ + int errorCount; + + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + fprintf(out, "#pragma once\n"); + fprintf(out, "\n"); + fprintf(out, "#include <stdint.h>\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "namespace util {\n"); + fprintf(out, "\n"); + fprintf(out, "/*\n"); + fprintf(out, " * API For logging statistics events.\n"); + fprintf(out, " */\n"); + fprintf(out, "\n"); + fprintf(out, "/**\n"); + fprintf(out, " * Constants for event codes.\n"); + fprintf(out, " */\n"); + fprintf(out, "enum {\n"); + + size_t i = 0; + // Print constants + for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); + atom != atoms.decls.end(); atom++) { + string constant = make_constant_name(atom->name); + fprintf(out, "\n"); + fprintf(out, " /**\n"); + fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); + fprintf(out, " * Usage: stats_write(StatsLog.%s", constant.c_str()); + for (vector<AtomField>::const_iterator field = atom->fields.begin(); + field != atom->fields.end(); field++) { + fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); + } + fprintf(out, ");\n"); + fprintf(out, " */\n"); + char const* const comma = (i == atoms.decls.size() - 1) ? "" : ","; + fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma); + i++; + } + fprintf(out, "\n"); + fprintf(out, "};\n"); + fprintf(out, "\n"); + + // Print write methods + fprintf(out, "//\n"); + fprintf(out, "// Write methods\n"); + fprintf(out, "//\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + + fprintf(out, "void stats_write(int code"); + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ");\n"); + } + + fprintf(out, "\n"); + fprintf(out, "} // namespace util\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + +static int +write_stats_log_java(FILE* out, const Atoms& atoms) +{ + int errorCount; + + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + fprintf(out, "package android.util;\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "/**\n"); + fprintf(out, " * API For logging statistics events.\n"); + fprintf(out, " * @hide\n"); + fprintf(out, " */\n"); + fprintf(out, "public final class StatsLog {\n"); + fprintf(out, " // Constants for event codes.\n"); + + // Print constants + for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); + atom != atoms.decls.end(); atom++) { + string constant = make_constant_name(atom->name); + fprintf(out, "\n"); + fprintf(out, " /**\n"); + fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); + fprintf(out, " * Usage: StatsLog.write(StatsLog.%s", constant.c_str()); + for (vector<AtomField>::const_iterator field = atom->fields.begin(); + field != atom->fields.end(); field++) { + fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); + } + fprintf(out, ");\n"); + fprintf(out, " */\n"); + fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code); + } + fprintf(out, "\n"); + + // Print write methods + fprintf(out, " // Write methods\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + fprintf(out, " public static native void write(int code"); + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ");\n"); + } + + fprintf(out, "}\n"); + + return 0; +} + +static const char* +jni_type_name(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "jboolean"; + case JAVA_TYPE_INT: + return "jint"; + case JAVA_TYPE_LONG: + return "jlong"; + case JAVA_TYPE_FLOAT: + return "jfloat"; + case JAVA_TYPE_DOUBLE: + return "jdouble"; + case JAVA_TYPE_STRING: + return "jstring"; + default: + return "UNKNOWN"; + } +} + +static string +jni_function_name(const vector<java_type_t>& signature) +{ + string result("StatsLog_write"); + for (vector<java_type_t>::const_iterator arg = signature.begin(); + arg != signature.end(); arg++) { + switch (*arg) { + case JAVA_TYPE_BOOLEAN: + result += "_boolean"; + break; + case JAVA_TYPE_INT: + result += "_int"; + break; + case JAVA_TYPE_LONG: + result += "_long"; + break; + case JAVA_TYPE_FLOAT: + result += "_float"; + break; + case JAVA_TYPE_DOUBLE: + result += "_double"; + break; + case JAVA_TYPE_STRING: + result += "_String"; + break; + default: + result += "_UNKNOWN"; + break; + } + } + return result; +} + +static const char* +java_type_signature(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "Z"; + case JAVA_TYPE_INT: + return "I"; + case JAVA_TYPE_LONG: + return "J"; + case JAVA_TYPE_FLOAT: + return "F"; + case JAVA_TYPE_DOUBLE: + return "D"; + case JAVA_TYPE_STRING: + return "Ljava/lang/String;"; + default: + return "UNKNOWN"; + } +} + +static string +jni_function_signature(const vector<java_type_t>& signature) +{ + string result("(I"); + for (vector<java_type_t>::const_iterator arg = signature.begin(); + arg != signature.end(); arg++) { + result += java_type_signature(*arg); + } + result += ")V"; + return result; +} + +static int +write_stats_log_jni(FILE* out, const Atoms& atoms) +{ + int errorCount; + + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + + fprintf(out, "#include <statslog.h>\n"); + fprintf(out, "\n"); + fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); + fprintf(out, "#include \"core_jni_helpers.h\"\n"); + fprintf(out, "#include \"jni.h\"\n"); + fprintf(out, "\n"); + fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "\n"); + + // Print write methods + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + int argIndex; + + fprintf(out, "static void\n"); + fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code", + jni_function_name(*signature).c_str()); + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", jni_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ")\n"); + + fprintf(out, "{\n"); + + // Prepare strings + argIndex = 1; + bool hadString = false; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " const char* str%d;\n", argIndex); + fprintf(out, " if (arg%d != NULL) {\n", argIndex); + fprintf(out, " str%d = env->GetStringUTFChars(arg%d, NULL);\n", + argIndex, argIndex); + fprintf(out, " } else {\n"); + fprintf(out, " str%d = NULL;\n", argIndex); + fprintf(out, " }\n"); + hadString = true; + } + argIndex++; + } + + // Emit this to quiet the unused parameter warning if there were no strings. + if (!hadString) { + fprintf(out, " (void)env;\n"); + } + + // stats_write call + argIndex = 1; + fprintf(out, " android::util::stats_write(code"); + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + const char* argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg"; + fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex); + argIndex++; + } + fprintf(out, ");\n"); + + // Clean up strings + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " if (str%d != NULL) {\n", argIndex); + fprintf(out, " env->ReleaseStringUTFChars(arg%d, str%d);\n", + argIndex, argIndex); + fprintf(out, " }\n"); + } + argIndex++; + } + + fprintf(out, "}\n"); + fprintf(out, "\n"); + } + + // Print registration function table + fprintf(out, "/*\n"); + fprintf(out, " * JNI registration.\n"); + fprintf(out, " */\n"); + fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + fprintf(out, " { \"write\", \"%s\", (void*)%s },\n", + jni_function_signature(*signature).c_str(), + jni_function_name(*signature).c_str()); + } + fprintf(out, "};\n"); + fprintf(out, "\n"); + + // Print registration function + fprintf(out, "int register_android_util_StatsLog(JNIEnv* env) {\n"); + fprintf(out, " return RegisterMethodsOrDie(\n"); + fprintf(out, " env,\n"); + fprintf(out, " \"android/util/StatsLog\",\n"); + fprintf(out, " gRegisterMethods, NELEM(gRegisterMethods));\n"); + fprintf(out, "}\n"); + + fprintf(out, "\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + + +static void +print_usage() +{ + fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "OPTIONS\n"); + fprintf(stderr, " --cpp FILENAME the header file to output\n"); + fprintf(stderr, " --header FILENAME the cpp file to output\n"); + fprintf(stderr, " --help this message\n"); + fprintf(stderr, " --java FILENAME the java file to output\n"); + fprintf(stderr, " --jni FILENAME the jni file to output\n"); +} + +/** + * Do the argument parsing and execute the tasks. + */ +static int +run(int argc, char const*const* argv) +{ + string cppFilename; + string headerFilename; + string javaFilename; + string jniFilename; + + int index = 1; + while (index < argc) { + if (0 == strcmp("--help", argv[index])) { + print_usage(); + return 0; + } else if (0 == strcmp("--cpp", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + cppFilename = argv[index]; + } else if (0 == strcmp("--header", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + headerFilename = argv[index]; + } else if (0 == strcmp("--java", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + javaFilename = argv[index]; + } else if (0 == strcmp("--jni", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + jniFilename = argv[index]; + } + index++; + } + + if (cppFilename.size() == 0 + && headerFilename.size() == 0 + && javaFilename.size() == 0 + && jniFilename.size() == 0) { + print_usage(); + return 1; + } + + // Collate the parameters + Atoms atoms; + int errorCount = collate_atoms(StatsEvent::descriptor(), &atoms); + if (errorCount != 0) { + return 1; + } + + // Write the .cpp file + if (cppFilename.size() != 0) { + FILE* out = fopen(cppFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", cppFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_cpp(out, atoms); + fclose(out); + } + + // Write the .h file + if (headerFilename.size() != 0) { + FILE* out = fopen(headerFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", headerFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_header(out, atoms); + fclose(out); + } + + // Write the .java file + if (javaFilename.size() != 0) { + FILE* out = fopen(javaFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_java(out, atoms); + fclose(out); + } + + // Write the jni file + if (jniFilename.size() != 0) { + FILE* out = fopen(jniFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", jniFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_jni(out, atoms); + fclose(out); + } + + return 0; +} + +} +} + +/** + * Main. + */ +int +main(int argc, char const*const* argv) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + return android::stats_log_api_gen::run(argc, argv); +} diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto new file mode 100644 index 000000000000..2311a1192c95 --- /dev/null +++ b/tools/stats_log_api_gen/test.proto @@ -0,0 +1,112 @@ +/* + * 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. + */ + +syntax = "proto2"; + +import "frameworks/base/cmds/statsd/src/stats_events.proto"; + +package android.stats_log_api_gen; + +message IntAtom { + optional int32 field1 = 1; +} + +message AnotherIntAtom { + optional int32 field1 = 1; +} + +message OutOfOrderAtom { + optional int32 field2 = 2; + optional int32 field1 = 1; +} + +enum AnEnum { + VALUE0 = 0; + VALUE1 = 1; +} + +message AllTypesAtom { + optional android.os.statsd.WorkSource attribution = 1; + optional double double_field = 2; + optional float float_field = 3; + optional int64 int64_field = 4; + optional uint64 uint64_field = 5; + optional int32 int32_field = 6; + optional fixed64 fixed64_field = 7; + optional fixed32 fixed32_field = 8; + optional bool bool_field = 9; + optional string string_field = 10; + optional uint32 uint32_field = 11; + optional AnEnum enum_field = 12; + optional sfixed32 sfixed32_field = 13; + optional sfixed64 sfixed64_field = 14; + optional sint32 sint32_field = 15; + optional sint64 sint64_field = 16; +} + +message Event { + oneof event { + OutOfOrderAtom out_of_order_atom = 2; + IntAtom int_atom = 1; + AnotherIntAtom another_int_atom = 3; + AllTypesAtom all_types_atom = 4; + } +} + +message BadTypesAtom { + optional IntAtom bad_int_atom = 1; + optional bytes bad_bytes = 2; +} + +message BadTypesEvent { + oneof event { + BadTypesAtom bad_types_atom = 1; + } +} + +message BadSkippedFieldSingleAtom { + optional int32 field2 = 2; +} + +message BadSkippedFieldSingle { + oneof event { + BadSkippedFieldSingleAtom bad = 1; + } +} + +message BadSkippedFieldMultipleAtom { + optional int32 field1 = 1; + optional int32 field3 = 3; + optional int32 field5 = 5; +} + +message BadSkippedFieldMultiple { + oneof event { + BadSkippedFieldMultipleAtom bad = 1; + } +} + +message BadWorkSourcePositionAtom { + optional int32 field1 = 1; + optional android.os.statsd.WorkSource attribution = 2; +} + +message BadWorkSourcePosition { + oneof event { + BadWorkSourcePositionAtom bad = 1; + } +} + diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp new file mode 100644 index 000000000000..1bd2e3de332e --- /dev/null +++ b/tools/stats_log_api_gen/test_collation.cpp @@ -0,0 +1,171 @@ +/* + * 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 <gtest/gtest.h> + +#include "frameworks/base/tools/stats_log_api_gen/test.pb.h" +#include "Collation.h" + +#include <stdio.h> + +namespace android { +namespace stats_log_api_gen { + +using std::set; +using std::vector; + +/** + * Return whether the set contains a vector of the elements provided. + */ +static bool +set_contains_vector(const set<vector<java_type_t>>& s, int count, ...) +{ + va_list args; + vector<java_type_t> v; + + va_start(args, count); + for (int i=0; i<count; i++) { + v.push_back((java_type_t)va_arg(args, int)); + } + va_end(args); + + return s.find(v) != s.end(); +} + +/** + * Expect that the provided set contains the elements provided. + */ +#define EXPECT_SET_CONTAINS_SIGNATURE(s, ...) \ + do { \ + int count = sizeof((int[]){__VA_ARGS__})/sizeof(int); \ + EXPECT_TRUE(set_contains_vector(s, count, __VA_ARGS__)); \ + } while(0) + +/** + * Test a correct collation, with all the types. + */ +TEST(CollationTest, CollateStats) { + Atoms atoms; + int errorCount = collate_atoms(Event::descriptor(), &atoms); + + EXPECT_EQ(0, errorCount); + EXPECT_EQ(3ul, atoms.signatures.size()); + + // IntAtom, AnotherIntAtom + EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT); + + // OutOfOrderAtom + EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT, JAVA_TYPE_INT); + + // AllTypesAtom + EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, + JAVA_TYPE_WORK_SOURCE, // WorkSource + JAVA_TYPE_DOUBLE, // double + JAVA_TYPE_FLOAT, // float + JAVA_TYPE_LONG, // int64 + JAVA_TYPE_LONG, // uint64 + JAVA_TYPE_INT, // int32 + JAVA_TYPE_LONG, // fixed64 + JAVA_TYPE_INT, // fixed32 + JAVA_TYPE_BOOLEAN, // bool + JAVA_TYPE_STRING, // string + JAVA_TYPE_INT, // uint32 + JAVA_TYPE_INT, // AnEnum + JAVA_TYPE_INT, // sfixed32 + JAVA_TYPE_LONG, // sfixed64 + JAVA_TYPE_INT, // sint32 + JAVA_TYPE_LONG // sint64 + ); + + set<AtomDecl>::const_iterator atom = atoms.decls.begin(); + EXPECT_EQ(1, atom->code); + EXPECT_EQ("int_atom", atom->name); + EXPECT_EQ("IntAtom", atom->message); + atom++; + + EXPECT_EQ(2, atom->code); + EXPECT_EQ("out_of_order_atom", atom->name); + EXPECT_EQ("OutOfOrderAtom", atom->message); + atom++; + + EXPECT_EQ(3, atom->code); + EXPECT_EQ("another_int_atom", atom->name); + EXPECT_EQ("AnotherIntAtom", atom->message); + atom++; + + EXPECT_EQ(4, atom->code); + EXPECT_EQ("all_types_atom", atom->name); + EXPECT_EQ("AllTypesAtom", atom->message); + atom++; + + EXPECT_TRUE(atom == atoms.decls.end()); +} + +/** + * Test that event class that contains stuff other than the atoms is rejected. + */ +TEST(CollationTest, NonMessageTypeFails) { + Atoms atoms; + int errorCount = collate_atoms(IntAtom::descriptor(), &atoms); + + EXPECT_EQ(1, errorCount); +} + +/** + * Test that atoms that have non-primitve types are rejected. + */ +TEST(CollationTest, FailOnBadTypes) { + Atoms atoms; + int errorCount = collate_atoms(BadTypesEvent::descriptor(), &atoms); + + EXPECT_EQ(2, errorCount); +} + +/** + * Test that atoms that skip field numbers (in the first position) are rejected. + */ +TEST(CollationTest, FailOnSkippedFieldsSingle) { + Atoms atoms; + int errorCount = collate_atoms(BadSkippedFieldSingle::descriptor(), &atoms); + + EXPECT_EQ(1, errorCount); +} + +/** + * Test that atoms that skip field numbers (not in the first position, and multiple + * times) are rejected. + */ +TEST(CollationTest, FailOnSkippedFieldsMultiple) { + Atoms atoms; + int errorCount = collate_atoms(BadSkippedFieldMultiple::descriptor(), &atoms); + + EXPECT_EQ(2, errorCount); +} + +/** + * Test that atoms that have a WorkSource not in the first position are rejected. + */ +TEST(CollationTest, FailBadWorkSourcePosition) { + Atoms atoms; + int errorCount = collate_atoms(BadWorkSourcePosition::descriptor(), &atoms); + + EXPECT_EQ(1, errorCount); +} + + +} // namespace stats_log_api_gen +} // namespace android + |