Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #pragma once |
| 18 | |
Yao Chen | 8a8d16c | 2018-02-08 14:50:40 -0800 | [diff] [blame] | 19 | #include "FieldValue.h" |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 20 | |
Yao Chen | 5110bed | 2017-10-23 12:50:02 -0700 | [diff] [blame] | 21 | #include <android/util/ProtoOutputStream.h> |
Yao Chen | 8023540 | 2017-11-13 20:42:25 -0800 | [diff] [blame] | 22 | #include <private/android_logger.h> |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 23 | |
| 24 | #include <string> |
| 25 | #include <vector> |
| 26 | |
| 27 | namespace android { |
| 28 | namespace os { |
| 29 | namespace statsd { |
| 30 | |
Chenjie Yu | 97dbb20 | 2019-02-13 16:42:04 -0800 | [diff] [blame] | 31 | struct InstallTrainInfo { |
| 32 | int64_t trainVersionCode; |
Muhammad Qureshi | f4ca824 | 2019-03-01 09:20:15 -0800 | [diff] [blame] | 33 | std::string trainName; |
| 34 | int32_t status; |
Jeff Hamilton | fa2f91c | 2019-03-22 00:25:02 -0400 | [diff] [blame] | 35 | std::vector<int64_t> experimentIds; |
Jonathan Nguyen | 703c42f | 2020-02-04 15:54:26 -0800 | [diff] [blame] | 36 | bool requiresStaging; |
| 37 | bool rollbackEnabled; |
| 38 | bool requiresLowLatencyMonitor; |
Chenjie Yu | 97dbb20 | 2019-02-13 16:42:04 -0800 | [diff] [blame] | 39 | }; |
Jeff Hamilton | fa2f91c | 2019-03-22 00:25:02 -0400 | [diff] [blame] | 40 | |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 41 | /** |
Ruchir Rastogi | dfd63d4 | 2020-02-20 17:54:13 -0800 | [diff] [blame] | 42 | * This class decodes the structured, serialized encoding of an atom into a |
| 43 | * vector of FieldValues. |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 44 | */ |
| 45 | class LogEvent { |
| 46 | public: |
| 47 | /** |
Ruchir Rastogi | dfd63d4 | 2020-02-20 17:54:13 -0800 | [diff] [blame] | 48 | * \param uid user id of the logging caller |
| 49 | * \param pid process id of the logging caller |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 50 | */ |
Ruchir Rastogi | dfd63d4 | 2020-02-20 17:54:13 -0800 | [diff] [blame] | 51 | explicit LogEvent(int32_t uid, int32_t pid); |
| 52 | |
| 53 | /** |
| 54 | * Parses the atomId, timestamp, and vector of values from a buffer |
| 55 | * containing the StatsEvent/AStatsEvent encoding of an atom. |
| 56 | * |
| 57 | * \param buf a buffer that begins at the start of the serialized atom (it |
| 58 | * should not include the android_log_header_t or the StatsEventTag) |
| 59 | * \param len size of the buffer |
| 60 | * |
| 61 | * \return success of the initialization |
| 62 | */ |
| 63 | bool parseBuffer(uint8_t* buf, size_t len); |
| 64 | |
Chenjie Yu | 6b1667c | 2019-01-18 10:09:33 -0800 | [diff] [blame] | 65 | // Constructs a BinaryPushStateChanged LogEvent from API call. |
| 66 | explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, |
| 67 | bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, |
| 68 | const std::vector<uint8_t>& experimentIds, int32_t userId); |
| 69 | |
Howard Ro | a46b658 | 2018-09-18 16:45:02 -0700 | [diff] [blame] | 70 | explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, |
Chenjie Yu | 97dbb20 | 2019-02-13 16:42:04 -0800 | [diff] [blame] | 71 | const InstallTrainInfo& installTrainInfo); |
| 72 | |
Ruchir Rastogi | 48dbf83 | 2020-04-20 16:50:46 -0700 | [diff] [blame] | 73 | ~LogEvent() {} |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 74 | |
| 75 | /** |
| 76 | * Get the timestamp associated with this event. |
| 77 | */ |
Yangster-mac | 330af58 | 2018-02-08 15:24:38 -0800 | [diff] [blame] | 78 | inline int64_t GetLogdTimestampNs() const { return mLogdTimestampNs; } |
| 79 | inline int64_t GetElapsedTimestampNs() const { return mElapsedTimestampNs; } |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 80 | |
| 81 | /** |
| 82 | * Get the tag for this event. |
| 83 | */ |
Yangster-mac | 6898580 | 2018-01-21 10:05:09 -0800 | [diff] [blame] | 84 | inline int GetTagId() const { return mTagId; } |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 85 | |
Ruchir Rastogi | ab71ef0 | 2020-01-30 01:24:50 -0800 | [diff] [blame] | 86 | /** |
| 87 | * Get the uid of the logging client. |
| 88 | * Returns -1 if the uid is unknown/has not been set. |
| 89 | */ |
| 90 | inline int32_t GetUid() const { return mLogUid; } |
| 91 | |
| 92 | /** |
| 93 | * Get the pid of the logging client. |
| 94 | * Returns -1 if the pid is unknown/has not been set. |
| 95 | */ |
| 96 | inline int32_t GetPid() const { return mLogPid; } |
Yao Chen | d10f7b1 | 2017-12-18 12:53:50 -0800 | [diff] [blame] | 97 | |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 98 | /** |
| 99 | * Get the nth value, starting at 1. |
| 100 | * |
| 101 | * Returns BAD_INDEX if the index is larger than the number of elements. |
| 102 | * Returns BAD_TYPE if the index is available but the data is the wrong type. |
| 103 | */ |
| 104 | int64_t GetLong(size_t key, status_t* err) const; |
Chenjie Yu | 80f9112 | 2018-01-31 20:24:50 -0800 | [diff] [blame] | 105 | int GetInt(size_t key, status_t* err) const; |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 106 | const char* GetString(size_t key, status_t* err) const; |
| 107 | bool GetBool(size_t key, status_t* err) const; |
| 108 | float GetFloat(size_t key, status_t* err) const; |
Jonathan Nguyen | a0e6de1 | 2020-01-28 18:33:55 -0800 | [diff] [blame] | 109 | std::vector<uint8_t> GetStorage(size_t key, status_t* err) const; |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 110 | |
| 111 | /** |
| 112 | * Return a string representation of this event. |
| 113 | */ |
Yao Chen | 9c1debe | 2018-02-19 14:39:19 -0800 | [diff] [blame] | 114 | std::string ToString() const; |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 115 | |
| 116 | /** |
Yao Chen | 5110bed | 2017-10-23 12:50:02 -0700 | [diff] [blame] | 117 | * Write this object to a ProtoOutputStream. |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 118 | */ |
Yao Chen | 5110bed | 2017-10-23 12:50:02 -0700 | [diff] [blame] | 119 | void ToProto(android::util::ProtoOutputStream& out) const; |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 120 | |
David Chen | 1481fe1 | 2017-10-16 13:16:34 -0700 | [diff] [blame] | 121 | /** |
Yangster-mac | 330af58 | 2018-02-08 15:24:38 -0800 | [diff] [blame] | 122 | * Set elapsed timestamp if the original timestamp is missing. |
Chenjie Yu | a7259ab | 2017-12-10 08:31:05 -0800 | [diff] [blame] | 123 | */ |
Yangster-mac | 330af58 | 2018-02-08 15:24:38 -0800 | [diff] [blame] | 124 | void setElapsedTimestampNs(int64_t timestampNs) { |
| 125 | mElapsedTimestampNs = timestampNs; |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * Set the timestamp if the original logd timestamp is missing. |
| 130 | */ |
| 131 | void setLogdWallClockTimestampNs(int64_t timestampNs) { |
| 132 | mLogdTimestampNs = timestampNs; |
| 133 | } |
Chenjie Yu | a7259ab | 2017-12-10 08:31:05 -0800 | [diff] [blame] | 134 | |
Yangster-mac | 2087716 | 2017-12-22 17:19:39 -0800 | [diff] [blame] | 135 | inline int size() const { |
Yao Chen | 8a8d16c | 2018-02-08 14:50:40 -0800 | [diff] [blame] | 136 | return mValues.size(); |
Chenjie Yu | d9dfda7 | 2017-12-11 17:41:20 -0800 | [diff] [blame] | 137 | } |
| 138 | |
Yao Chen | 8a8d16c | 2018-02-08 14:50:40 -0800 | [diff] [blame] | 139 | const std::vector<FieldValue>& getValues() const { |
| 140 | return mValues; |
| 141 | } |
Yangster-mac | d40053e | 2018-01-09 16:29:22 -0800 | [diff] [blame] | 142 | |
Yao Chen | 8a8d16c | 2018-02-08 14:50:40 -0800 | [diff] [blame] | 143 | std::vector<FieldValue>* getMutableValues() { |
| 144 | return &mValues; |
| 145 | } |
Yangster-mac | 2087716 | 2017-12-22 17:19:39 -0800 | [diff] [blame] | 146 | |
Ruchir Rastogi | 1329651 | 2020-03-24 10:59:49 -0700 | [diff] [blame] | 147 | // Default value = false |
Muhammad Qureshi | a7de000 | 2020-04-15 11:34:35 -0700 | [diff] [blame] | 148 | inline bool shouldTruncateTimestamp() const { |
Ruchir Rastogi | 1329651 | 2020-03-24 10:59:49 -0700 | [diff] [blame] | 149 | return mTruncateTimestamp; |
| 150 | } |
| 151 | |
| 152 | // Returns the index of the uid field within the FieldValues vector if the |
| 153 | // uid exists. If there is no uid field, returns -1. |
| 154 | // |
| 155 | // If the index within the atom definition is desired, do the following: |
| 156 | // int vectorIndex = LogEvent.getUidFieldIndex(); |
| 157 | // if (vectorIndex != -1) { |
| 158 | // FieldValue& v = LogEvent.getValues()[vectorIndex]; |
| 159 | // int atomIndex = v.mField.getPosAtDepth(0); |
| 160 | // } |
| 161 | // Note that atomIndex is 1-indexed. |
| 162 | inline int getUidFieldIndex() { |
Ruchir Rastogi | 1e8c57f | 2020-04-27 17:41:58 -0700 | [diff] [blame] | 163 | return static_cast<int>(mUidFieldIndex); |
Ruchir Rastogi | 1329651 | 2020-03-24 10:59:49 -0700 | [diff] [blame] | 164 | } |
| 165 | |
Muhammad Qureshi | 83aaa66 | 2020-04-21 12:51:36 -0700 | [diff] [blame] | 166 | // Returns whether this LogEvent has an AttributionChain. |
| 167 | // If it does and indexRange is not a nullptr, populate indexRange with the start and end index |
| 168 | // of the AttributionChain within mValues. |
| 169 | bool hasAttributionChain(std::pair<int, int>* indexRange = nullptr) const; |
Ruchir Rastogi | 1329651 | 2020-03-24 10:59:49 -0700 | [diff] [blame] | 170 | |
Muhammad Qureshi | bfc4bdb | 2020-04-08 06:26:49 -0700 | [diff] [blame] | 171 | // Returns the index of the exclusive state field within the FieldValues vector if |
| 172 | // an exclusive state exists. If there is no exclusive state field, returns -1. |
| 173 | // |
| 174 | // If the index within the atom definition is desired, do the following: |
| 175 | // int vectorIndex = LogEvent.getExclusiveStateFieldIndex(); |
| 176 | // if (vectorIndex != -1) { |
| 177 | // FieldValue& v = LogEvent.getValues()[vectorIndex]; |
| 178 | // int atomIndex = v.mField.getPosAtDepth(0); |
| 179 | // } |
| 180 | // Note that atomIndex is 1-indexed. |
| 181 | inline int getExclusiveStateFieldIndex() const { |
Ruchir Rastogi | 1e8c57f | 2020-04-27 17:41:58 -0700 | [diff] [blame] | 182 | return static_cast<int>(mExclusiveStateFieldIndex); |
Muhammad Qureshi | bfc4bdb | 2020-04-08 06:26:49 -0700 | [diff] [blame] | 183 | } |
| 184 | |
Ruchir Rastogi | 48dbf83 | 2020-04-20 16:50:46 -0700 | [diff] [blame] | 185 | // If a reset state is not sent in the StatsEvent, returns -1. Note that a |
| 186 | // reset state is sent if and only if a reset should be triggered. |
| 187 | inline int getResetState() const { |
| 188 | return mResetState; |
| 189 | } |
| 190 | |
Chenjie Yu | 0bd73db | 2018-12-16 07:37:04 -0800 | [diff] [blame] | 191 | inline LogEvent makeCopy() { |
| 192 | return LogEvent(*this); |
| 193 | } |
| 194 | |
Jonathan Nguyen | a0e6de1 | 2020-01-28 18:33:55 -0800 | [diff] [blame] | 195 | template <class T> |
| 196 | status_t updateValue(size_t key, T& value, Type type) { |
| 197 | int field = getSimpleField(key); |
| 198 | for (auto& fieldValue : mValues) { |
| 199 | if (fieldValue.mField.getField() == field) { |
| 200 | if (fieldValue.mValue.getType() == type) { |
| 201 | fieldValue.mValue = Value(value); |
| 202 | return OK; |
| 203 | } else { |
| 204 | return BAD_TYPE; |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | return BAD_INDEX; |
| 209 | } |
| 210 | |
Ruchir Rastogi | 07f7adb | 2020-04-16 17:46:12 -0700 | [diff] [blame] | 211 | bool isValid() const { |
| 212 | return mValid; |
| 213 | } |
| 214 | |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 215 | private: |
| 216 | /** |
Chenjie Yu | 0bd73db | 2018-12-16 07:37:04 -0800 | [diff] [blame] | 217 | * Only use this if copy is absolutely needed. |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 218 | */ |
Chih-Hung Hsieh | 3227aab | 2018-12-20 13:42:28 -0800 | [diff] [blame] | 219 | LogEvent(const LogEvent&); |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 220 | |
Ruchir Rastogi | 84eb44b | 2020-03-12 18:48:48 -0700 | [diff] [blame] | 221 | void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
| 222 | void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
| 223 | void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
| 224 | void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
| 225 | void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
| 226 | void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
| 227 | void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
| 228 | void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); |
Ruchir Rastogi | 1329651 | 2020-03-24 10:59:49 -0700 | [diff] [blame] | 229 | |
| 230 | void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1); |
| 231 | void parseIsUidAnnotation(uint8_t annotationType); |
| 232 | void parseTruncateTimestampAnnotation(uint8_t annotationType); |
Muhammad Qureshi | 3f9c330 | 2020-04-01 16:11:53 -0700 | [diff] [blame] | 233 | void parsePrimaryFieldAnnotation(uint8_t annotationType); |
| 234 | void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex); |
| 235 | void parseExclusiveStateAnnotation(uint8_t annotationType); |
| 236 | void parseTriggerStateResetAnnotation(uint8_t annotationType); |
Ruchir Rastogi | 1329651 | 2020-03-24 10:59:49 -0700 | [diff] [blame] | 237 | void parseStateNestedAnnotation(uint8_t annotationType); |
Ruchir Rastogi | ffa34f0 | 2020-04-04 15:30:11 -0700 | [diff] [blame] | 238 | bool checkPreviousValueType(Type expected); |
Ruchir Rastogi | 1736ba4 | 2019-11-04 14:37:13 -0800 | [diff] [blame] | 239 | |
| 240 | /** |
Ruchir Rastogi | 07f7adb | 2020-04-16 17:46:12 -0700 | [diff] [blame] | 241 | * The below two variables are only valid during the execution of |
Ruchir Rastogi | dfd63d4 | 2020-02-20 17:54:13 -0800 | [diff] [blame] | 242 | * parseBuffer. There are no guarantees about the state of these variables |
| 243 | * before/after. |
Ruchir Rastogi | 1736ba4 | 2019-11-04 14:37:13 -0800 | [diff] [blame] | 244 | */ |
| 245 | uint8_t* mBuf; |
Ruchir Rastogi | 1736ba4 | 2019-11-04 14:37:13 -0800 | [diff] [blame] | 246 | uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed |
Ruchir Rastogi | 07f7adb | 2020-04-16 17:46:12 -0700 | [diff] [blame] | 247 | |
Ruchir Rastogi | 1736ba4 | 2019-11-04 14:37:13 -0800 | [diff] [blame] | 248 | bool mValid = true; // stores whether the event we received from the socket is valid |
| 249 | |
| 250 | /** |
| 251 | * Side-effects: |
| 252 | * If there is enough space in buffer to read value of type T |
| 253 | * - move mBuf past the value that was just read |
| 254 | * - decrement mRemainingLen by size of T |
| 255 | * Else |
| 256 | * - set mValid to false |
| 257 | */ |
| 258 | template <class T> |
| 259 | T readNextValue() { |
| 260 | T value; |
| 261 | if (mRemainingLen < sizeof(T)) { |
| 262 | mValid = false; |
| 263 | value = 0; // all primitive types can successfully cast 0 |
| 264 | } else { |
Greg Kaiser | 19a8866 | 2020-01-29 13:48:09 -0800 | [diff] [blame] | 265 | // When alignof(T) == 1, hopefully the compiler can optimize away |
| 266 | // this conditional as always true. |
| 267 | if ((reinterpret_cast<uintptr_t>(mBuf) % alignof(T)) == 0) { |
| 268 | // We're properly aligned, and can safely make this assignment. |
| 269 | value = *((T*)mBuf); |
| 270 | } else { |
| 271 | // We need to use memcpy. It's slower, but safe. |
| 272 | memcpy(&value, mBuf, sizeof(T)); |
| 273 | } |
Ruchir Rastogi | 1736ba4 | 2019-11-04 14:37:13 -0800 | [diff] [blame] | 274 | mBuf += sizeof(T); |
| 275 | mRemainingLen -= sizeof(T); |
| 276 | } |
| 277 | return value; |
| 278 | } |
| 279 | |
| 280 | template <class T> |
| 281 | void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) { |
| 282 | Field f = Field(mTagId, pos, depth); |
| 283 | // do not decorate last position at depth 0 |
| 284 | for (int i = 1; i < depth; i++) { |
| 285 | if (last[i]) f.decorateLastPos(i); |
| 286 | } |
| 287 | |
| 288 | Value v = Value(value); |
| 289 | mValues.push_back(FieldValue(f, v)); |
| 290 | } |
| 291 | |
| 292 | uint8_t getTypeId(uint8_t typeInfo); |
| 293 | uint8_t getNumAnnotations(uint8_t typeInfo); |
| 294 | |
Yao Chen | 8a8d16c | 2018-02-08 14:50:40 -0800 | [diff] [blame] | 295 | // The items are naturally sorted in DFS order as we read them. this allows us to do fast |
| 296 | // matching. |
| 297 | std::vector<FieldValue> mValues; |
Yao Chen | 8023540 | 2017-11-13 20:42:25 -0800 | [diff] [blame] | 298 | |
Yangster-mac | 330af58 | 2018-02-08 15:24:38 -0800 | [diff] [blame] | 299 | // The timestamp set by the logd. |
| 300 | int64_t mLogdTimestampNs; |
| 301 | |
| 302 | // The elapsed timestamp set by statsd log writer. |
| 303 | int64_t mElapsedTimestampNs; |
Yao Chen | 93fe3a3 | 2017-11-02 13:52:59 -0700 | [diff] [blame] | 304 | |
Ruchir Rastogi | 07f7adb | 2020-04-16 17:46:12 -0700 | [diff] [blame] | 305 | // The atom tag of the event (defaults to 0 if client does not |
| 306 | // appropriately set the atom id). |
| 307 | int mTagId = 0; |
Yao Chen | d10f7b1 | 2017-12-18 12:53:50 -0800 | [diff] [blame] | 308 | |
Ruchir Rastogi | ab71ef0 | 2020-01-30 01:24:50 -0800 | [diff] [blame] | 309 | // The uid of the logging client (defaults to -1). |
| 310 | int32_t mLogUid = -1; |
| 311 | |
| 312 | // The pid of the logging client (defaults to -1). |
| 313 | int32_t mLogPid = -1; |
Ruchir Rastogi | 1329651 | 2020-03-24 10:59:49 -0700 | [diff] [blame] | 314 | |
| 315 | // Annotations |
| 316 | bool mTruncateTimestamp = false; |
Ruchir Rastogi | 48dbf83 | 2020-04-20 16:50:46 -0700 | [diff] [blame] | 317 | int mResetState = -1; |
Ruchir Rastogi | 1e8c57f | 2020-04-27 17:41:58 -0700 | [diff] [blame] | 318 | |
| 319 | // Indexes within the FieldValue vector can be stored in 7 bits because |
| 320 | // that's the assumption enforced by the encoding used in FieldValue. |
| 321 | int8_t mUidFieldIndex = -1; |
| 322 | int8_t mAttributionChainStartIndex = -1; |
| 323 | int8_t mAttributionChainEndIndex = -1; |
| 324 | int8_t mExclusiveStateFieldIndex = -1; |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 325 | }; |
| 326 | |
Jeff Hamilton | fa2f91c | 2019-03-22 00:25:02 -0400 | [diff] [blame] | 327 | void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); |
| 328 | |
Joe Onorato | c4dfae5 | 2017-10-17 23:38:21 -0700 | [diff] [blame] | 329 | } // namespace statsd |
| 330 | } // namespace os |
| 331 | } // namespace android |