Loading cmds/statsd/src/atom_field_options.proto +49 −20 Original line number Diff line number Diff line Loading @@ -24,46 +24,75 @@ option java_outer_classname = "AtomFieldOptions"; import "google/protobuf/descriptor.proto"; enum StateField { // Default value for fields that are not primary or exclusive state. // Default value for fields that are not a primary or exclusive state field. STATE_FIELD_UNSET = 0; // Fields that represent the key that the state belongs to. PRIMARY = 1; // Used on simple proto fields. Do not use on attribution chains. PRIMARY_FIELD = 1; // The field that represents the state. It's an exclusive state. EXCLUSIVE = 2; EXCLUSIVE_STATE = 2; // Used on an attribution chain field to indicate that the first uid is the // primary field. PRIMARY_FIELD_FIRST_UID = 3; } // Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE // exclusive state field, and any number of primary key fields. // For example, // message UidProcessStateChanged { // optional int32 uid = 1 [(state_field_option).option = PRIMARY]; // optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE]; // Used to annotate an atom that represents a state change. A state change atom must have exactly // ONE exclusive state field, and any number of primary key fields. For example, message // UidProcessStateChanged { // optional int32 uid = 1 [(state_field_option).option = PRIMARY_FIELD]; // optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = // EXCLUSIVE_STATE]; // } // Each of this UidProcessStateChanged atom represents a state change for a specific uid. // Each UidProcessStateChanged atom event represents a state change for a specific uid. // A new state automatically overrides the previous state. // // If the atom has 2 or more primary fields, it means the combination of the primary fields are // the primary key. // If the atom has 2 or more primary fields, it means the combination of the // primary fields are the primary key. // For example: // message ThreadStateChanged { // optional int32 pid = 1 [(state_field_option).option = PRIMARY]; // optional int32 tid = 2 [(state_field_option).option = PRIMARY]; // optional int32 state = 3 [(state_field_option).option = EXCLUSIVE]; // optional int32 pid = 1 [(state_field_option).option = PRIMARY_FIELD]; // optional int32 tid = 2 [(state_field_option).option = PRIMARY_FIELD]; // optional int32 state = 3 [(state_field_option).option = EXCLUSIVE_STATE]; // } // // Sometimes, there is no primary key field, when the state is GLOBAL. // For example, // // message ScreenStateChanged { // optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE]; // optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = // EXCLUSIVE_STATE]; // } // // Only fields of primary types can be annotated. AttributionNode cannot be primary keys (and they // usually are not). // For state atoms with attribution chain, sometimes the primary key is the first uid in the chain. // For example: // message AudioStateChanged { // repeated AttributionNode attribution_node = 1 // [(stateFieldOption).option = PRIMARY_KEY_FIRST_UID]; // // enum State { // OFF = 0; // ON = 1; // // RESET indicates all audio stopped. Used when it (re)starts (e.g. after it crashes). // RESET = 2; // } // optional State state = 2 [(stateFieldOption).option = EXCLUSIVE_STATE]; // } message StateAtomFieldOption { optional StateField option = 1 [default = STATE_FIELD_UNSET]; // Note: We cannot annotate directly on the enums because many enums are imported from other // proto files in the platform. proto-lite cc library does not support annotations unfortunately // Knowing the default state value allows state trackers to remove entries that become the // default state. If there is no default value specified, the default value is unknown, and all // states will be tracked in memory. optional int32 default_state_value = 2; // A reset state signals all states go to default value. For example, BLE reset means all active // BLE scans are to be turned off. optional int32 reset_state_value = 3; // If the state change needs to count nesting. optional bool nested = 4 [default = true]; } // Used to generate StatsLog.write APIs. Loading cmds/statsd/src/atoms.proto +34 −18 Original line number Diff line number Diff line Loading @@ -563,7 +563,8 @@ message ThermalThrottlingStateChanged { */ message ScreenStateChanged { // New screen state, from frameworks/base/core/proto/android/view/enums.proto. optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE]; optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE_STATE, (state_field_option).nested = false]; } /** Loading @@ -574,10 +575,11 @@ message ScreenStateChanged { * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java */ message UidProcessStateChanged { optional int32 uid = 1 [(state_field_option).option = PRIMARY, (is_uid) = true]; optional int32 uid = 1 [(state_field_option).option = PRIMARY_FIELD, (is_uid) = true]; // The state, from frameworks/base/core/proto/android/app/enums.proto. optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE]; optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE_STATE, (state_field_option).nested = false]; } /** Loading Loading @@ -609,7 +611,7 @@ message ActivityManagerSleepStateChanged { ASLEEP = 1; AWAKE = 2; } optional State state = 1 [(state_field_option).option = EXCLUSIVE]; optional State state = 1 [(state_field_option).option = EXCLUSIVE_STATE]; } /** Loading @@ -628,7 +630,7 @@ message MemoryFactorStateChanged { CRITICAL = 4; // critical memory. } optional State factor = 1 [(state_field_option).option = EXCLUSIVE]; optional State factor = 1 [(state_field_option).option = EXCLUSIVE_STATE]; } /** Loading Loading @@ -790,7 +792,8 @@ message ProcessLifeCycleStateChanged { * packages/apps/Bluetooth/src/com/android/bluetooth/gatt/AppScanStats.java */ message BleScanStateChanged { repeated AttributionNode attribution_node = 1; repeated AttributionNode attribution_node = 1 [(state_field_option).option = PRIMARY_FIELD_FIRST_UID]; enum State { OFF = 0; Loading @@ -798,14 +801,19 @@ message BleScanStateChanged { // RESET indicates all ble stopped. Used when it (re)starts (e.g. after it crashes). RESET = 2; } optional State state = 2; optional State state = 2 [ (state_field_option).option = EXCLUSIVE_STATE, (state_field_option).default_state_value = 0 /* State.OFF */, (state_field_option).reset_state_value = 2 /* State.RESET */, (state_field_option).nested = true ]; // Does the scan have a filter. optional bool is_filtered = 3; optional bool is_filtered = 3 [(state_field_option).option = PRIMARY_FIELD]; // Whether the scan is a CALLBACK_TYPE_FIRST_MATCH scan. Called 'background' scan internally. optional bool is_first_match = 4; optional bool is_first_match = 4 [(state_field_option).option = PRIMARY_FIELD]; // Whether the scan set to piggy-back off the results of other scans (SCAN_MODE_OPPORTUNISTIC). optional bool is_opportunistic = 5; optional bool is_opportunistic = 5 [(state_field_option).option = PRIMARY_FIELD]; } /** Loading Loading @@ -1029,11 +1037,11 @@ message WakelockStateChanged { // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock. // From frameworks/base/core/proto/android/os/enums.proto. optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY]; optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY_FIELD]; ; // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). optional string tag = 3 [(state_field_option).option = PRIMARY]; optional string tag = 3 [(state_field_option).option = PRIMARY_FIELD]; enum State { RELEASE = 0; Loading @@ -1041,7 +1049,11 @@ message WakelockStateChanged { CHANGE_RELEASE = 2; CHANGE_ACQUIRE = 3; } optional State state = 4 [(state_field_option).option = EXCLUSIVE]; optional State state = 4 [ (state_field_option).option = EXCLUSIVE_STATE, (state_field_option).default_state_value = 0, (state_field_option).nested = true ]; } /** Loading Loading @@ -3225,9 +3237,9 @@ message PictureInPictureStateChanged { * services/core/java/com/android/server/wm/Session.java */ message OverlayStateChanged { optional int32 uid = 1 [(state_field_option).option = PRIMARY, (is_uid) = true]; optional int32 uid = 1 [(state_field_option).option = PRIMARY_FIELD, (is_uid) = true]; optional string package_name = 2 [(state_field_option).option = PRIMARY]; optional string package_name = 2 [(state_field_option).option = PRIMARY_FIELD]; optional bool using_alert_window = 3; Loading @@ -3235,7 +3247,11 @@ message OverlayStateChanged { ENTERED = 1; EXITED = 2; } optional State state = 4 [(state_field_option).option = EXCLUSIVE]; optional State state = 4 [ (state_field_option).option = EXCLUSIVE_STATE, (state_field_option).nested = false, (state_field_option).default_state_value = 2 ]; } /* Loading Loading @@ -3401,7 +3417,7 @@ message LmkKillOccurred { */ message AppDied { // timestamp(elapsedRealtime) of record creation optional uint64 timestamp_millis = 1 [(state_field_option).option = EXCLUSIVE]; optional uint64 timestamp_millis = 1 [(state_field_option).option = EXCLUSIVE_STATE]; } /** Loading Loading @@ -3921,7 +3937,7 @@ message PrivacyIndicatorsInteracted { DIALOG_LINE_ITEM = 5; } optional Type type = 1 [(state_field_option).option = EXCLUSIVE]; optional Type type = 1 [(state_field_option).option = EXCLUSIVE_STATE]; // Used if the type is LINE_ITEM optional string package_name = 2; Loading cmds/statsd/src/state/StateTracker.cpp +62 −18 Original line number Diff line number Diff line Loading @@ -26,7 +26,9 @@ namespace os { namespace statsd { StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo) : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) { : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)), mNested(stateAtomInfo.nested) { // create matcher for each primary field for (const auto& primaryField : stateAtomInfo.primaryFields) { if (primaryField == util::FIRST_UID_IN_CHAIN) { Loading @@ -38,7 +40,13 @@ StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptio } } // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting if (stateAtomInfo.defaultState != util::UNSET_VALUE) { mDefaultState = stateAtomInfo.defaultState; } if (stateAtomInfo.resetState != util::UNSET_VALUE) { mResetState = stateAtomInfo.resetState; } } void StateTracker::onLogEvent(const LogEvent& event) { Loading @@ -60,7 +68,6 @@ void StateTracker::onLogEvent(const LogEvent& event) { // Parse event for state value. FieldValue stateValue; int32_t state; if (!filterValues(mStateField, event.getValues(), &stateValue) || stateValue.mValue.getType() != INT) { ALOGE("StateTracker error extracting state from log event. Type: %d", Loading @@ -68,11 +75,12 @@ void StateTracker::onLogEvent(const LogEvent& event) { handlePartialReset(eventTimeNs, primaryKey); return; } state = stateValue.mValue.int_value; int32_t state = stateValue.mValue.int_value; if (state == mResetState) { VLOG("StateTracker Reset state: %s", stateValue.mValue.toString().c_str()); handleReset(eventTimeNs); return; } // Track and update state. Loading Loading @@ -113,15 +121,17 @@ bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValu return true; } } else if (queryKey.getValues().size() > mPrimaryFields.size()) { ALOGE("StateTracker query key size > primary key size is illegal"); ALOGE("StateTracker query key size %zu > primary key size %zu is illegal", queryKey.getValues().size(), mPrimaryFields.size()); } else { ALOGE("StateTracker query key size < primary key size is not supported"); ALOGE("StateTracker query key size %zu < primary key size %zu is not supported", queryKey.getValues().size(), mPrimaryFields.size()); } // Set the state value to unknown if: // Set the state value to default state if: // - query key size is incorrect // - query key is not found in state map output->mValue = StateTracker::kStateUnknown; output->mValue = mDefaultState; return false; } Loading Loading @@ -164,7 +174,9 @@ void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int *oldState = mDefaultState; } // update state map // Update state map for non-nested counting case. // Every state event triggers a state overwrite. if (!mNested) { if (eventState == mDefaultState) { // remove (key, state) pair if state returns to default state VLOG("\t StateTracker changed to default state") Loading @@ -174,8 +186,40 @@ void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int mStateMap[primaryKey].count = 1; } *newState = eventState; return; } // TODO(tsaichristine): support atoms with nested counting // Update state map for nested counting case. // // Nested counting is only allowed for binary state events such as ON/OFF or // ACQUIRE/RELEASE. For example, WakelockStateChanged might have the state // events: ON, ON, OFF. The state will still be ON until we see the same // number of OFF events as ON events. // // In atoms.proto, a state atom with nested counting enabled // must only have 2 states and one of the states must be the default state. it = mStateMap.find(primaryKey); if (it != mStateMap.end()) { *newState = it->second.state; if (eventState == it->second.state) { it->second.count++; } else if (eventState == mDefaultState) { if ((--it->second.count) == 0) { mStateMap.erase(primaryKey); *newState = mDefaultState; } } else { ALOGE("StateTracker Nest counting state has a third state instead of the binary state " "limit."); return; } } else { if (eventState != mDefaultState) { mStateMap[primaryKey].state = eventState; mStateMap[primaryKey].count = 1; } *newState = eventState; } } } // namespace statsd Loading cmds/statsd/src/state/StateTracker.h +2 −0 Original line number Diff line number Diff line Loading @@ -74,6 +74,8 @@ private: int32_t mResetState = kStateUnknown; const bool mNested; // Maps primary key to state value info std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap; Loading cmds/statsd/tests/state/StateTracker_test.cpp +98 −5 Original line number Diff line number Diff line Loading @@ -127,6 +127,23 @@ std::shared_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::stri event->init(); return event; } std::shared_ptr<LogEvent> buildBleScanEvent(int uid, bool acquire, bool reset) { std::vector<AttributionNodeInternal> chain; chain.push_back(AttributionNodeInternal()); AttributionNodeInternal& attr = chain.back(); attr.set_uid(uid); std::shared_ptr<LogEvent> event = std::make_shared<LogEvent>(android::util::BLE_SCAN_STATE_CHANGED, 1000); event->write(chain); event->write(reset ? 2 : acquire ? 1 : 0); // PARTIAL_WAKE_LOCK event->write(0); // filtered event->write(0); // first match event->write(0); // opportunistic event->init(); return event; } // END: build event functions. // START: get primary key functions Loading Loading @@ -276,6 +293,80 @@ TEST(StateTrackerTest, TestUnregisterListener) { EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED)); } /** * Test a binary state atom with nested counting. * * To go from an "ON" state to an "OFF" state with nested counting, we must see * an equal number of "OFF" events as "ON" events. * For example, ACQUIRE, ACQUIRE, RELEASE will still be in the ACQUIRE state. * ACQUIRE, ACQUIRE, RELEASE, RELEASE will be in the RELEASE state. */ TEST(StateTrackerTest, TestStateChangeNested) { sp<TestStateListener> listener = new TestStateListener(); StateManager mgr; mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener); std::shared_ptr<LogEvent> event1 = buildPartialWakelockEvent(1000 /* uid */, "tag", true /*acquire*/); mgr.onLogEvent(*event1); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(1, listener->updates[0].mState); listener->updates.clear(); std::shared_ptr<LogEvent> event2 = buildPartialWakelockEvent(1000 /* uid */, "tag", true /*acquire*/); mgr.onLogEvent(*event2); EXPECT_EQ(0, listener->updates.size()); std::shared_ptr<LogEvent> event3 = buildPartialWakelockEvent(1000 /* uid */, "tag", false /*release*/); mgr.onLogEvent(*event3); EXPECT_EQ(0, listener->updates.size()); std::shared_ptr<LogEvent> event4 = buildPartialWakelockEvent(1000 /* uid */, "tag", false /*release*/); mgr.onLogEvent(*event4); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(0, listener->updates[0].mState); } /** * Test a state atom with a reset state. * * If the reset state value is seen, every state in the map is set to the default * state and every listener is notified. */ TEST(StateTrackerTest, TestStateChangeReset) { sp<TestStateListener> listener = new TestStateListener(); StateManager mgr; mgr.registerListener(android::util::BLE_SCAN_STATE_CHANGED, listener); std::shared_ptr<LogEvent> event1 = buildBleScanEvent(1000 /* uid */, true /*acquire*/, false /*reset*/); mgr.onLogEvent(*event1); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); listener->updates.clear(); std::shared_ptr<LogEvent> event2 = buildBleScanEvent(2000 /* uid */, true /*acquire*/, false /*reset*/); mgr.onLogEvent(*event2); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(2000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); listener->updates.clear(); std::shared_ptr<LogEvent> event3 = buildBleScanEvent(2000 /* uid */, false /*acquire*/, true /*reset*/); mgr.onLogEvent(*event3); EXPECT_EQ(2, listener->updates.size()); EXPECT_EQ(BleScanStateChanged::OFF, listener->updates[0].mState); EXPECT_EQ(BleScanStateChanged::OFF, listener->updates[1].mState); } /** * Test StateManager's onLogEvent and StateListener's onStateChanged correctly * updates listener for states without primary keys. Loading Loading @@ -334,7 +425,7 @@ TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { // Log event. std::shared_ptr<LogEvent> event = buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */); buildPartialWakelockEvent(1001 /* uid */, "tag1", true /* acquire */); mgr.onLogEvent(*event); EXPECT_EQ(1, mgr.getStateTrackersCount()); Loading @@ -346,23 +437,25 @@ TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value); EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value); EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState); EXPECT_EQ(WakelockStateChanged::ACQUIRE, listener1->updates[0].mState); // Check StateTracker was updated by querying for state. HashableDimensionKey queryKey; getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey); EXPECT_EQ(WakelockStateChanged::RELEASE, EXPECT_EQ(WakelockStateChanged::ACQUIRE, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey)); // No state stored for this query key. HashableDimensionKey queryKey2; getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2)); EXPECT_EQ(WakelockStateChanged::RELEASE, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2)); // Partial query fails. HashableDimensionKey queryKey3; getPartialWakelockKey(1001 /* uid */, &queryKey3); EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3)); EXPECT_EQ(WakelockStateChanged::RELEASE, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3)); } /** Loading Loading
cmds/statsd/src/atom_field_options.proto +49 −20 Original line number Diff line number Diff line Loading @@ -24,46 +24,75 @@ option java_outer_classname = "AtomFieldOptions"; import "google/protobuf/descriptor.proto"; enum StateField { // Default value for fields that are not primary or exclusive state. // Default value for fields that are not a primary or exclusive state field. STATE_FIELD_UNSET = 0; // Fields that represent the key that the state belongs to. PRIMARY = 1; // Used on simple proto fields. Do not use on attribution chains. PRIMARY_FIELD = 1; // The field that represents the state. It's an exclusive state. EXCLUSIVE = 2; EXCLUSIVE_STATE = 2; // Used on an attribution chain field to indicate that the first uid is the // primary field. PRIMARY_FIELD_FIRST_UID = 3; } // Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE // exclusive state field, and any number of primary key fields. // For example, // message UidProcessStateChanged { // optional int32 uid = 1 [(state_field_option).option = PRIMARY]; // optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE]; // Used to annotate an atom that represents a state change. A state change atom must have exactly // ONE exclusive state field, and any number of primary key fields. For example, message // UidProcessStateChanged { // optional int32 uid = 1 [(state_field_option).option = PRIMARY_FIELD]; // optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = // EXCLUSIVE_STATE]; // } // Each of this UidProcessStateChanged atom represents a state change for a specific uid. // Each UidProcessStateChanged atom event represents a state change for a specific uid. // A new state automatically overrides the previous state. // // If the atom has 2 or more primary fields, it means the combination of the primary fields are // the primary key. // If the atom has 2 or more primary fields, it means the combination of the // primary fields are the primary key. // For example: // message ThreadStateChanged { // optional int32 pid = 1 [(state_field_option).option = PRIMARY]; // optional int32 tid = 2 [(state_field_option).option = PRIMARY]; // optional int32 state = 3 [(state_field_option).option = EXCLUSIVE]; // optional int32 pid = 1 [(state_field_option).option = PRIMARY_FIELD]; // optional int32 tid = 2 [(state_field_option).option = PRIMARY_FIELD]; // optional int32 state = 3 [(state_field_option).option = EXCLUSIVE_STATE]; // } // // Sometimes, there is no primary key field, when the state is GLOBAL. // For example, // // message ScreenStateChanged { // optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE]; // optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = // EXCLUSIVE_STATE]; // } // // Only fields of primary types can be annotated. AttributionNode cannot be primary keys (and they // usually are not). // For state atoms with attribution chain, sometimes the primary key is the first uid in the chain. // For example: // message AudioStateChanged { // repeated AttributionNode attribution_node = 1 // [(stateFieldOption).option = PRIMARY_KEY_FIRST_UID]; // // enum State { // OFF = 0; // ON = 1; // // RESET indicates all audio stopped. Used when it (re)starts (e.g. after it crashes). // RESET = 2; // } // optional State state = 2 [(stateFieldOption).option = EXCLUSIVE_STATE]; // } message StateAtomFieldOption { optional StateField option = 1 [default = STATE_FIELD_UNSET]; // Note: We cannot annotate directly on the enums because many enums are imported from other // proto files in the platform. proto-lite cc library does not support annotations unfortunately // Knowing the default state value allows state trackers to remove entries that become the // default state. If there is no default value specified, the default value is unknown, and all // states will be tracked in memory. optional int32 default_state_value = 2; // A reset state signals all states go to default value. For example, BLE reset means all active // BLE scans are to be turned off. optional int32 reset_state_value = 3; // If the state change needs to count nesting. optional bool nested = 4 [default = true]; } // Used to generate StatsLog.write APIs. Loading
cmds/statsd/src/atoms.proto +34 −18 Original line number Diff line number Diff line Loading @@ -563,7 +563,8 @@ message ThermalThrottlingStateChanged { */ message ScreenStateChanged { // New screen state, from frameworks/base/core/proto/android/view/enums.proto. optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE]; optional android.view.DisplayStateEnum state = 1 [(state_field_option).option = EXCLUSIVE_STATE, (state_field_option).nested = false]; } /** Loading @@ -574,10 +575,11 @@ message ScreenStateChanged { * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java */ message UidProcessStateChanged { optional int32 uid = 1 [(state_field_option).option = PRIMARY, (is_uid) = true]; optional int32 uid = 1 [(state_field_option).option = PRIMARY_FIELD, (is_uid) = true]; // The state, from frameworks/base/core/proto/android/app/enums.proto. optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE]; optional android.app.ProcessStateEnum state = 2 [(state_field_option).option = EXCLUSIVE_STATE, (state_field_option).nested = false]; } /** Loading Loading @@ -609,7 +611,7 @@ message ActivityManagerSleepStateChanged { ASLEEP = 1; AWAKE = 2; } optional State state = 1 [(state_field_option).option = EXCLUSIVE]; optional State state = 1 [(state_field_option).option = EXCLUSIVE_STATE]; } /** Loading @@ -628,7 +630,7 @@ message MemoryFactorStateChanged { CRITICAL = 4; // critical memory. } optional State factor = 1 [(state_field_option).option = EXCLUSIVE]; optional State factor = 1 [(state_field_option).option = EXCLUSIVE_STATE]; } /** Loading Loading @@ -790,7 +792,8 @@ message ProcessLifeCycleStateChanged { * packages/apps/Bluetooth/src/com/android/bluetooth/gatt/AppScanStats.java */ message BleScanStateChanged { repeated AttributionNode attribution_node = 1; repeated AttributionNode attribution_node = 1 [(state_field_option).option = PRIMARY_FIELD_FIRST_UID]; enum State { OFF = 0; Loading @@ -798,14 +801,19 @@ message BleScanStateChanged { // RESET indicates all ble stopped. Used when it (re)starts (e.g. after it crashes). RESET = 2; } optional State state = 2; optional State state = 2 [ (state_field_option).option = EXCLUSIVE_STATE, (state_field_option).default_state_value = 0 /* State.OFF */, (state_field_option).reset_state_value = 2 /* State.RESET */, (state_field_option).nested = true ]; // Does the scan have a filter. optional bool is_filtered = 3; optional bool is_filtered = 3 [(state_field_option).option = PRIMARY_FIELD]; // Whether the scan is a CALLBACK_TYPE_FIRST_MATCH scan. Called 'background' scan internally. optional bool is_first_match = 4; optional bool is_first_match = 4 [(state_field_option).option = PRIMARY_FIELD]; // Whether the scan set to piggy-back off the results of other scans (SCAN_MODE_OPPORTUNISTIC). optional bool is_opportunistic = 5; optional bool is_opportunistic = 5 [(state_field_option).option = PRIMARY_FIELD]; } /** Loading Loading @@ -1029,11 +1037,11 @@ message WakelockStateChanged { // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock. // From frameworks/base/core/proto/android/os/enums.proto. optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY]; optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY_FIELD]; ; // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). optional string tag = 3 [(state_field_option).option = PRIMARY]; optional string tag = 3 [(state_field_option).option = PRIMARY_FIELD]; enum State { RELEASE = 0; Loading @@ -1041,7 +1049,11 @@ message WakelockStateChanged { CHANGE_RELEASE = 2; CHANGE_ACQUIRE = 3; } optional State state = 4 [(state_field_option).option = EXCLUSIVE]; optional State state = 4 [ (state_field_option).option = EXCLUSIVE_STATE, (state_field_option).default_state_value = 0, (state_field_option).nested = true ]; } /** Loading Loading @@ -3225,9 +3237,9 @@ message PictureInPictureStateChanged { * services/core/java/com/android/server/wm/Session.java */ message OverlayStateChanged { optional int32 uid = 1 [(state_field_option).option = PRIMARY, (is_uid) = true]; optional int32 uid = 1 [(state_field_option).option = PRIMARY_FIELD, (is_uid) = true]; optional string package_name = 2 [(state_field_option).option = PRIMARY]; optional string package_name = 2 [(state_field_option).option = PRIMARY_FIELD]; optional bool using_alert_window = 3; Loading @@ -3235,7 +3247,11 @@ message OverlayStateChanged { ENTERED = 1; EXITED = 2; } optional State state = 4 [(state_field_option).option = EXCLUSIVE]; optional State state = 4 [ (state_field_option).option = EXCLUSIVE_STATE, (state_field_option).nested = false, (state_field_option).default_state_value = 2 ]; } /* Loading Loading @@ -3401,7 +3417,7 @@ message LmkKillOccurred { */ message AppDied { // timestamp(elapsedRealtime) of record creation optional uint64 timestamp_millis = 1 [(state_field_option).option = EXCLUSIVE]; optional uint64 timestamp_millis = 1 [(state_field_option).option = EXCLUSIVE_STATE]; } /** Loading Loading @@ -3921,7 +3937,7 @@ message PrivacyIndicatorsInteracted { DIALOG_LINE_ITEM = 5; } optional Type type = 1 [(state_field_option).option = EXCLUSIVE]; optional Type type = 1 [(state_field_option).option = EXCLUSIVE_STATE]; // Used if the type is LINE_ITEM optional string package_name = 2; Loading
cmds/statsd/src/state/StateTracker.cpp +62 −18 Original line number Diff line number Diff line Loading @@ -26,7 +26,9 @@ namespace os { namespace statsd { StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo) : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) { : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)), mNested(stateAtomInfo.nested) { // create matcher for each primary field for (const auto& primaryField : stateAtomInfo.primaryFields) { if (primaryField == util::FIRST_UID_IN_CHAIN) { Loading @@ -38,7 +40,13 @@ StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptio } } // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting if (stateAtomInfo.defaultState != util::UNSET_VALUE) { mDefaultState = stateAtomInfo.defaultState; } if (stateAtomInfo.resetState != util::UNSET_VALUE) { mResetState = stateAtomInfo.resetState; } } void StateTracker::onLogEvent(const LogEvent& event) { Loading @@ -60,7 +68,6 @@ void StateTracker::onLogEvent(const LogEvent& event) { // Parse event for state value. FieldValue stateValue; int32_t state; if (!filterValues(mStateField, event.getValues(), &stateValue) || stateValue.mValue.getType() != INT) { ALOGE("StateTracker error extracting state from log event. Type: %d", Loading @@ -68,11 +75,12 @@ void StateTracker::onLogEvent(const LogEvent& event) { handlePartialReset(eventTimeNs, primaryKey); return; } state = stateValue.mValue.int_value; int32_t state = stateValue.mValue.int_value; if (state == mResetState) { VLOG("StateTracker Reset state: %s", stateValue.mValue.toString().c_str()); handleReset(eventTimeNs); return; } // Track and update state. Loading Loading @@ -113,15 +121,17 @@ bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValu return true; } } else if (queryKey.getValues().size() > mPrimaryFields.size()) { ALOGE("StateTracker query key size > primary key size is illegal"); ALOGE("StateTracker query key size %zu > primary key size %zu is illegal", queryKey.getValues().size(), mPrimaryFields.size()); } else { ALOGE("StateTracker query key size < primary key size is not supported"); ALOGE("StateTracker query key size %zu < primary key size %zu is not supported", queryKey.getValues().size(), mPrimaryFields.size()); } // Set the state value to unknown if: // Set the state value to default state if: // - query key size is incorrect // - query key is not found in state map output->mValue = StateTracker::kStateUnknown; output->mValue = mDefaultState; return false; } Loading Loading @@ -164,7 +174,9 @@ void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int *oldState = mDefaultState; } // update state map // Update state map for non-nested counting case. // Every state event triggers a state overwrite. if (!mNested) { if (eventState == mDefaultState) { // remove (key, state) pair if state returns to default state VLOG("\t StateTracker changed to default state") Loading @@ -174,8 +186,40 @@ void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int mStateMap[primaryKey].count = 1; } *newState = eventState; return; } // TODO(tsaichristine): support atoms with nested counting // Update state map for nested counting case. // // Nested counting is only allowed for binary state events such as ON/OFF or // ACQUIRE/RELEASE. For example, WakelockStateChanged might have the state // events: ON, ON, OFF. The state will still be ON until we see the same // number of OFF events as ON events. // // In atoms.proto, a state atom with nested counting enabled // must only have 2 states and one of the states must be the default state. it = mStateMap.find(primaryKey); if (it != mStateMap.end()) { *newState = it->second.state; if (eventState == it->second.state) { it->second.count++; } else if (eventState == mDefaultState) { if ((--it->second.count) == 0) { mStateMap.erase(primaryKey); *newState = mDefaultState; } } else { ALOGE("StateTracker Nest counting state has a third state instead of the binary state " "limit."); return; } } else { if (eventState != mDefaultState) { mStateMap[primaryKey].state = eventState; mStateMap[primaryKey].count = 1; } *newState = eventState; } } } // namespace statsd Loading
cmds/statsd/src/state/StateTracker.h +2 −0 Original line number Diff line number Diff line Loading @@ -74,6 +74,8 @@ private: int32_t mResetState = kStateUnknown; const bool mNested; // Maps primary key to state value info std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap; Loading
cmds/statsd/tests/state/StateTracker_test.cpp +98 −5 Original line number Diff line number Diff line Loading @@ -127,6 +127,23 @@ std::shared_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::stri event->init(); return event; } std::shared_ptr<LogEvent> buildBleScanEvent(int uid, bool acquire, bool reset) { std::vector<AttributionNodeInternal> chain; chain.push_back(AttributionNodeInternal()); AttributionNodeInternal& attr = chain.back(); attr.set_uid(uid); std::shared_ptr<LogEvent> event = std::make_shared<LogEvent>(android::util::BLE_SCAN_STATE_CHANGED, 1000); event->write(chain); event->write(reset ? 2 : acquire ? 1 : 0); // PARTIAL_WAKE_LOCK event->write(0); // filtered event->write(0); // first match event->write(0); // opportunistic event->init(); return event; } // END: build event functions. // START: get primary key functions Loading Loading @@ -276,6 +293,80 @@ TEST(StateTrackerTest, TestUnregisterListener) { EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED)); } /** * Test a binary state atom with nested counting. * * To go from an "ON" state to an "OFF" state with nested counting, we must see * an equal number of "OFF" events as "ON" events. * For example, ACQUIRE, ACQUIRE, RELEASE will still be in the ACQUIRE state. * ACQUIRE, ACQUIRE, RELEASE, RELEASE will be in the RELEASE state. */ TEST(StateTrackerTest, TestStateChangeNested) { sp<TestStateListener> listener = new TestStateListener(); StateManager mgr; mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener); std::shared_ptr<LogEvent> event1 = buildPartialWakelockEvent(1000 /* uid */, "tag", true /*acquire*/); mgr.onLogEvent(*event1); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(1, listener->updates[0].mState); listener->updates.clear(); std::shared_ptr<LogEvent> event2 = buildPartialWakelockEvent(1000 /* uid */, "tag", true /*acquire*/); mgr.onLogEvent(*event2); EXPECT_EQ(0, listener->updates.size()); std::shared_ptr<LogEvent> event3 = buildPartialWakelockEvent(1000 /* uid */, "tag", false /*release*/); mgr.onLogEvent(*event3); EXPECT_EQ(0, listener->updates.size()); std::shared_ptr<LogEvent> event4 = buildPartialWakelockEvent(1000 /* uid */, "tag", false /*release*/); mgr.onLogEvent(*event4); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(0, listener->updates[0].mState); } /** * Test a state atom with a reset state. * * If the reset state value is seen, every state in the map is set to the default * state and every listener is notified. */ TEST(StateTrackerTest, TestStateChangeReset) { sp<TestStateListener> listener = new TestStateListener(); StateManager mgr; mgr.registerListener(android::util::BLE_SCAN_STATE_CHANGED, listener); std::shared_ptr<LogEvent> event1 = buildBleScanEvent(1000 /* uid */, true /*acquire*/, false /*reset*/); mgr.onLogEvent(*event1); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(1000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); listener->updates.clear(); std::shared_ptr<LogEvent> event2 = buildBleScanEvent(2000 /* uid */, true /*acquire*/, false /*reset*/); mgr.onLogEvent(*event2); EXPECT_EQ(1, listener->updates.size()); EXPECT_EQ(2000, listener->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(BleScanStateChanged::ON, listener->updates[0].mState); listener->updates.clear(); std::shared_ptr<LogEvent> event3 = buildBleScanEvent(2000 /* uid */, false /*acquire*/, true /*reset*/); mgr.onLogEvent(*event3); EXPECT_EQ(2, listener->updates.size()); EXPECT_EQ(BleScanStateChanged::OFF, listener->updates[0].mState); EXPECT_EQ(BleScanStateChanged::OFF, listener->updates[1].mState); } /** * Test StateManager's onLogEvent and StateListener's onStateChanged correctly * updates listener for states without primary keys. Loading Loading @@ -334,7 +425,7 @@ TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { // Log event. std::shared_ptr<LogEvent> event = buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */); buildPartialWakelockEvent(1001 /* uid */, "tag1", true /* acquire */); mgr.onLogEvent(*event); EXPECT_EQ(1, mgr.getStateTrackersCount()); Loading @@ -346,23 +437,25 @@ TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value); EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value); EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value); EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState); EXPECT_EQ(WakelockStateChanged::ACQUIRE, listener1->updates[0].mState); // Check StateTracker was updated by querying for state. HashableDimensionKey queryKey; getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey); EXPECT_EQ(WakelockStateChanged::RELEASE, EXPECT_EQ(WakelockStateChanged::ACQUIRE, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey)); // No state stored for this query key. HashableDimensionKey queryKey2; getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2)); EXPECT_EQ(WakelockStateChanged::RELEASE, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2)); // Partial query fails. HashableDimensionKey queryKey3; getPartialWakelockKey(1001 /* uid */, &queryKey3); EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3)); EXPECT_EQ(WakelockStateChanged::RELEASE, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3)); } /** Loading