diff options
author | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
commit | 628590d7ec80e10a3fc24b1c18a1afb55cca10a8 (patch) | |
tree | 4b1c3f52d86d7fb53afbe9e9438468588fa489f8 /cmds | |
parent | b11b8ec3aec8bb42f2c07e1c5ac7942da293baa8 (diff) | |
parent | d2d3a20624d968199353ccf6ddbae6f3ac39c9af (diff) |
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)
Bug: 166295507
Merged-In: I3d92a6de21a938f6b352ec26dc23420c0fe02b27
Change-Id: Ifdb80563ef042738778ebb8a7581a97c4e3d96e2
Diffstat (limited to 'cmds')
346 files changed, 30009 insertions, 22642 deletions
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java index 2adbc1f6e1ae..7c30c8b1e1dd 100644 --- a/cmds/am/src/com/android/commands/am/Instrument.java +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -17,8 +17,8 @@ package com.android.commands.am; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; +import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS; -import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; import android.app.IActivityManager; import android.app.IInstrumentationWatcher; @@ -512,7 +512,7 @@ public class Instrument { flags |= INSTR_FLAG_DISABLE_TEST_API_CHECKS; } if (disableIsolatedStorage) { - flags |= INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; + flags |= INSTR_FLAG_DISABLE_ISOLATED_STORAGE; } if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId, abi)) { diff --git a/cmds/bmgr/TEST_MAPPING b/cmds/bmgr/TEST_MAPPING new file mode 100644 index 000000000000..7c0e79e7692d --- /dev/null +++ b/cmds/bmgr/TEST_MAPPING @@ -0,0 +1,11 @@ +{ + "presubmit": [ + ], + "postsubmit": [ + ], + "imports": [ + { + "path": "frameworks/base/services/backup" + } + ] +} diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 680ccfc57ddf..ed717c491467 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -64,6 +64,10 @@ public class Bmgr { private static final String BMGR_NOT_RUNNING_ERR = "Error: Could not access the Backup Manager. Is the system running?"; + private static final String BMGR_NOT_ACTIVATED_FOR_USER = + "Error: Backup Manager is not activated for user "; + private static final String BMGR_ERR_NO_RESTORESESSION_FOR_USER = + "Error: Could not get restore session for user "; private static final String TRANSPORT_NOT_RUNNING_ERR = "Error: Could not access the backup transport. Is the system running?"; private static final String PM_NOT_RUNNING_ERR = @@ -121,6 +125,11 @@ public class Bmgr { return; } + if ("autorestore".equals(op)) { + doAutoRestore(userId); + return; + } + if ("enabled".equals(op)) { doEnabled(userId); return; @@ -190,21 +199,45 @@ public class Bmgr { showUsage(); } - boolean isBackupActive(@UserIdInt int userId) { + private void handleRemoteException(RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + + private boolean isBackupActive(@UserIdInt int userId) { try { if (!mBmgr.isBackupServiceActive(userId)) { - System.err.println(BMGR_NOT_RUNNING_ERR); + System.err.println(BMGR_NOT_ACTIVATED_FOR_USER + userId); return false; } } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); return false; } return true; } + private void doAutoRestore(int userId) { + String arg = nextArg(); + if (arg == null) { + showUsage(); + return; + } + + try { + boolean enable = Boolean.parseBoolean(arg); + mBmgr.setAutoRestore(enable); + System.out.println( + "Auto restore is now " + + (enable ? "enabled" : "disabled") + + " for user " + + userId); + } catch (RemoteException e) { + handleRemoteException(e); + } + } + private String activatedToString(boolean activated) { return activated ? "activated" : "deactivated"; } @@ -214,8 +247,7 @@ public class Bmgr { System.out.println("Backup Manager currently " + activatedToString(mBmgr.isBackupServiceActive(userId))); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -230,8 +262,7 @@ public class Bmgr { System.out.println("Backup Manager currently " + enableToString(isEnabled)); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -250,8 +281,7 @@ public class Bmgr { showUsage(); return; } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -259,8 +289,7 @@ public class Bmgr { try { mBmgr.backupNowForUser(userId); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -274,8 +303,7 @@ public class Bmgr { try { mBmgr.dataChangedForUser(userId, pkg); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -292,8 +320,7 @@ public class Bmgr { mBmgr.fullTransportBackupForUser( userId, allPkgs.toArray(new String[allPkgs.size()])); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } } @@ -421,8 +448,7 @@ public class Bmgr { try { filteredPackages = mBmgr.filterAppsEligibleForBackupForUser(userId, packages); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } backupNowPackages(userId, Arrays.asList(filteredPackages), nonIncrementalBackup, monitorState); @@ -455,8 +481,7 @@ public class Bmgr { System.err.println("Unable to run backup"); } } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -506,8 +531,7 @@ public class Bmgr { try { mBmgr.cancelBackupsForUser(userId); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } return; } @@ -537,8 +561,7 @@ public class Bmgr { } } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -569,8 +592,7 @@ public class Bmgr { } }); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); return; } @@ -598,8 +620,7 @@ public class Bmgr { mBmgr.clearBackupDataForUser(userId, transport, pkg); System.out.println("Wiped backup data for " + pkg + " on " + transport); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -632,8 +653,7 @@ public class Bmgr { observer.waitForCompletion(30*1000L); System.out.println("Initialization result: " + observer.result); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -648,7 +668,7 @@ public class Bmgr { try { mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { - System.err.println(BMGR_NOT_RUNNING_ERR); + System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; } @@ -658,8 +678,7 @@ public class Bmgr { mRestore.endRestoreSession(); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -686,8 +705,7 @@ public class Bmgr { System.out.println(pad + t); } } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -805,7 +823,7 @@ public class Bmgr { boolean didRestore = false; mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { - System.err.println(BMGR_NOT_RUNNING_ERR); + System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId); return; } RestoreSet[] sets = null; @@ -851,8 +869,7 @@ public class Bmgr { System.out.println("done"); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -865,8 +882,7 @@ public class Bmgr { } } } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -886,8 +902,7 @@ public class Bmgr { + " for user " + userId); } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(BMGR_NOT_RUNNING_ERR); + handleRemoteException(e); } } @@ -928,6 +943,7 @@ public class Bmgr { System.err.println(" bmgr init TRANSPORT..."); System.err.println(" bmgr activate BOOL"); System.err.println(" bmgr activated"); + System.err.println(" bmgr autorestore BOOL"); System.err.println(""); System.err.println("The '--user' option specifies the user on which the operation is run."); System.err.println("It must be the first argument before the operation."); @@ -1002,6 +1018,9 @@ public class Bmgr { System.err.println(""); System.err.println("The 'activated' command reports the current activated/deactivated"); System.err.println("state of the backup mechanism."); + System.err.println(""); + System.err.println("The 'autorestore' command enables or disables automatic restore when"); + System.err.println("a new package is installed."); } private static class BackupMonitor extends IBackupManagerMonitor.Stub { diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp index c60d08b2bda1..757c2b2a4cfa 100644 --- a/cmds/bootanimation/Android.bp +++ b/cmds/bootanimation/Android.bp @@ -43,6 +43,10 @@ cc_binary { ], init_rc: ["bootanim.rc"], + + cflags: [ + "-Wno-deprecated-declarations", + ], } // libbootanimation diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 85db4b9f0f8f..bb2de17b42f3 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -42,12 +42,13 @@ #include <android-base/properties.h> +#include <ui/DisplayConfig.h> #include <ui/PixelFormat.h> #include <ui/Rect.h> #include <ui/Region.h> -#include <ui/DisplayInfo.h> #include <gui/ISurfaceComposer.h> +#include <gui/DisplayEventReceiver.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> @@ -106,14 +107,15 @@ static constexpr size_t FONT_NUM_ROWS = FONT_NUM_CHARS / FONT_NUM_COLS; static const int TEXT_CENTER_VALUE = INT_MAX; static const int TEXT_MISSING_VALUE = INT_MIN; static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; +static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays"; static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1; static constexpr size_t TEXT_POS_LEN_MAX = 16; // --------------------------------------------------------------------------- BootAnimation::BootAnimation(sp<Callbacks> callbacks) - : Thread(false), mClockEnabled(true), mTimeIsAccurate(false), - mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) { + : Thread(false), mClockEnabled(true), mTimeIsAccurate(false), mTimeFormat12Hour(false), + mTimeCheckThread(nullptr), mCallbacks(callbacks), mLooper(new Looper(false)) { mSession = new SurfaceComposerClient(); std::string powerCtl = android::base::GetProperty("sys.powerctl", ""); @@ -153,8 +155,7 @@ sp<SurfaceComposerClient> BootAnimation::session() const { return mSession; } -void BootAnimation::binderDied(const wp<IBinder>&) -{ +void BootAnimation::binderDied(const wp<IBinder>&) { // woah, surfaceflinger died! SLOGD("SurfaceFlinger died, exiting..."); @@ -218,8 +219,7 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, return NO_ERROR; } -status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) -{ +status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { SkBitmap bitmap; sk_sp<SkData> data = SkData::MakeWithoutCopy(map->getDataPtr(), map->getDataLength()); @@ -277,48 +277,173 @@ status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) return NO_ERROR; } +class BootAnimation::DisplayEventCallback : public LooperCallback { + BootAnimation* mBootAnimation; + +public: + DisplayEventCallback(BootAnimation* bootAnimation) { + mBootAnimation = bootAnimation; + } + + int handleEvent(int /* fd */, int events, void* /* data */) { + if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { + ALOGE("Display event receiver pipe was closed or an error occurred. events=0x%x", + events); + return 0; // remove the callback + } + + if (!(events & Looper::EVENT_INPUT)) { + ALOGW("Received spurious callback for unhandled poll event. events=0x%x", events); + return 1; // keep the callback + } + + constexpr int kBufferSize = 100; + DisplayEventReceiver::Event buffer[kBufferSize]; + ssize_t numEvents; + do { + numEvents = mBootAnimation->mDisplayEventReceiver->getEvents(buffer, kBufferSize); + for (size_t i = 0; i < static_cast<size_t>(numEvents); i++) { + const auto& event = buffer[i]; + if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) { + SLOGV("Hotplug received"); + + if (!event.hotplug.connected) { + // ignore hotplug disconnect + continue; + } + auto token = SurfaceComposerClient::getPhysicalDisplayToken( + event.header.displayId); + + if (token != mBootAnimation->mDisplayToken) { + // ignore hotplug of a secondary display + continue; + } + + DisplayConfig displayConfig; + const status_t error = SurfaceComposerClient::getActiveDisplayConfig( + mBootAnimation->mDisplayToken, &displayConfig); + if (error != NO_ERROR) { + SLOGE("Can't get active display configuration."); + } + mBootAnimation->resizeSurface(displayConfig.resolution.getWidth(), + displayConfig.resolution.getHeight()); + } + } + } while (numEvents > 0); + + return 1; // keep the callback + } +}; + +EGLConfig BootAnimation::getEglConfig(const EGLDisplay& display) { + const EGLint attribs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_NONE + }; + EGLint numConfigs; + EGLConfig config; + eglChooseConfig(display, attribs, &config, 1, &numConfigs); + return config; +} + +ui::Size BootAnimation::limitSurfaceSize(int width, int height) const { + ui::Size limited(width, height); + bool wasLimited = false; + const float aspectRatio = float(width) / float(height); + if (mMaxWidth != 0 && width > mMaxWidth) { + limited.height = mMaxWidth / aspectRatio; + limited.width = mMaxWidth; + wasLimited = true; + } + if (mMaxHeight != 0 && limited.height > mMaxHeight) { + limited.height = mMaxHeight; + limited.width = mMaxHeight * aspectRatio; + wasLimited = true; + } + SLOGV_IF(wasLimited, "Surface size has been limited to [%dx%d] from [%dx%d]", + limited.width, limited.height, width, height); + return limited; +} + status_t BootAnimation::readyToRun() { mAssets.addDefaultAssets(); mDisplayToken = SurfaceComposerClient::getInternalDisplayToken(); if (mDisplayToken == nullptr) - return -1; - - DisplayInfo dinfo; - status_t status = SurfaceComposerClient::getDisplayInfo(mDisplayToken, &dinfo); - if (status) - return -1; - + return NAME_NOT_FOUND; + + DisplayConfig displayConfig; + const status_t error = + SurfaceComposerClient::getActiveDisplayConfig(mDisplayToken, &displayConfig); + if (error != NO_ERROR) + return error; + + mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0); + mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0); + ui::Size resolution = displayConfig.resolution; + resolution = limitSurfaceSize(resolution.width, resolution.height); // create the native surface sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"), - dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); + resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565); SurfaceComposerClient::Transaction t; + + // this guest property specifies multi-display IDs to show the boot animation + // multiple ids can be set with comma (,) as separator, for example: + // setprop persist.boot.animation.displays 19260422155234049,19261083906282754 + Vector<uint64_t> physicalDisplayIds; + char displayValue[PROPERTY_VALUE_MAX] = ""; + property_get(DISPLAYS_PROP_NAME, displayValue, ""); + bool isValid = displayValue[0] != '\0'; + if (isValid) { + char *p = displayValue; + while (*p) { + if (!isdigit(*p) && *p != ',') { + isValid = false; + break; + } + p ++; + } + if (!isValid) + SLOGE("Invalid syntax for the value of system prop: %s", DISPLAYS_PROP_NAME); + } + if (isValid) { + std::istringstream stream(displayValue); + for (PhysicalDisplayId id; stream >> id; ) { + physicalDisplayIds.add(id); + if (stream.peek() == ',') + stream.ignore(); + } + + // In the case of multi-display, boot animation shows on the specified displays + // in addition to the primary display + auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + constexpr uint32_t LAYER_STACK = 0; + for (auto id : physicalDisplayIds) { + if (std::find(ids.begin(), ids.end(), id) != ids.end()) { + sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(id); + if (token != nullptr) + t.setDisplayLayerStack(token, LAYER_STACK); + } + } + t.setLayerStack(control, LAYER_STACK); + } + t.setLayer(control, 0x40000000) .apply(); sp<Surface> s = control->getSurface(); // initialize opengl and egl - const EGLint attribs[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_NONE - }; - EGLint w, h; - EGLint numConfigs; - EGLConfig config; - EGLSurface surface; - EGLContext context; - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - eglInitialize(display, nullptr, nullptr); - eglChooseConfig(display, attribs, &config, 1, &numConfigs); - surface = eglCreateWindowSurface(display, config, s.get(), nullptr); - context = eglCreateContext(display, config, nullptr, nullptr); + EGLConfig config = getEglConfig(display); + EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr); + EGLContext context = eglCreateContext(display, config, nullptr, nullptr); + EGLint w, h; eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); @@ -334,9 +459,47 @@ status_t BootAnimation::readyToRun() { mFlingerSurface = s; mTargetInset = -1; + // Register a display event receiver + mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>(); + status_t status = mDisplayEventReceiver->initCheck(); + SLOGE_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver failed with status: %d", + status); + mLooper->addFd(mDisplayEventReceiver->getFd(), 0, Looper::EVENT_INPUT, + new DisplayEventCallback(this), nullptr); + return NO_ERROR; } +void BootAnimation::resizeSurface(int newWidth, int newHeight) { + // We assume this function is called on the animation thread. + if (newWidth == mWidth && newHeight == mHeight) { + return; + } + SLOGV("Resizing the boot animation surface to %d %d", newWidth, newHeight); + + eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(mDisplay, mSurface); + + const auto limitedSize = limitSurfaceSize(newWidth, newHeight); + mWidth = limitedSize.width; + mHeight = limitedSize.height; + + SurfaceComposerClient::Transaction t; + t.setSize(mFlingerSurfaceControl, mWidth, mHeight); + t.apply(); + + EGLConfig config = getEglConfig(mDisplay); + EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr); + if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) { + SLOGE("Can't make the new surface current. Error %d", eglGetError()); + return; + } + glViewport(0, 0, mWidth, mHeight); + glScissor(0, 0, mWidth, mHeight); + + mSurface = surface; +} + bool BootAnimation::preloadAnimation() { findBootAnimationFile(); if (!mZipFileName.isEmpty()) { @@ -397,15 +560,14 @@ void BootAnimation::findBootAnimationFile() { } } -bool BootAnimation::threadLoop() -{ - bool r; +bool BootAnimation::threadLoop() { + bool result; // We have no bootanimation file, so we use the stock android logo // animation. if (mZipFileName.isEmpty()) { - r = android(); + result = android(); } else { - r = movie(); + result = movie(); } eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); @@ -416,11 +578,10 @@ bool BootAnimation::threadLoop() eglTerminate(mDisplay); eglReleaseThread(); IPCThreadState::self()->stopProcess(); - return r; + return result; } -bool BootAnimation::android() -{ +bool BootAnimation::android() { SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime()); initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png"); @@ -439,19 +600,19 @@ bool BootAnimation::android() glEnable(GL_TEXTURE_2D); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - const GLint xc = (mWidth - mAndroid[0].w) / 2; - const GLint yc = (mHeight - mAndroid[0].h) / 2; - const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h); - - glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(), - updateRect.height()); - // Blend state glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); const nsecs_t startTime = systemTime(); do { + processDisplayEvents(); + const GLint xc = (mWidth - mAndroid[0].w) / 2; + const GLint yc = (mHeight - mAndroid[0].h) / 2; + const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h); + glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(), + updateRect.height()); + nsecs_t now = systemTime(); double time = now - startTime; float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w; @@ -566,8 +727,7 @@ static bool parseColor(const char str[7], float color[3]) { } -static bool readFile(ZipFileRO* zip, const char* name, String8& outString) -{ +static bool readFile(ZipFileRO* zip, const char* name, String8& outString) { ZipEntryRO entry = zip->findEntryByName(name); SLOGE_IF(!entry, "couldn't find %s", name); if (!entry) { @@ -688,8 +848,7 @@ void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) drawText(out, font, false, &x, &y); } -bool BootAnimation::parseAnimationDesc(Animation& animation) -{ +bool BootAnimation::parseAnimationDesc(Animation& animation) { String8 desString; if (!readFile(animation.zip, "desc.txt", desString)) { @@ -756,8 +915,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) return true; } -bool BootAnimation::preloadZip(Animation& animation) -{ +bool BootAnimation::preloadZip(Animation& animation) { // read all the data structures const size_t pcount = animation.parts.size(); void *cookie = nullptr; @@ -854,8 +1012,7 @@ bool BootAnimation::preloadZip(Animation& animation) return true; } -bool BootAnimation::movie() -{ +bool BootAnimation::movie() { if (mAnimation == nullptr) { mAnimation = loadAnimation(mZipFileName); } @@ -941,12 +1098,9 @@ bool BootAnimation::movie() return false; } -bool BootAnimation::playAnimation(const Animation& animation) -{ +bool BootAnimation::playAnimation(const Animation& animation) { const size_t pcount = animation.parts.size(); nsecs_t frameDuration = s2ns(1) / animation.fps; - const int animationX = (mWidth - animation.width) / 2; - const int animationY = (mHeight - animation.height) / 2; SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime()); @@ -977,6 +1131,11 @@ bool BootAnimation::playAnimation(const Animation& animation) 1.0f); for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) { + processDisplayEvents(); + + const int animationX = (mWidth - animation.width) / 2; + const int animationY = (mHeight - animation.height) / 2; + const Animation::Frame& frame(part.frames[j]); nsecs_t lastFrame = systemTime(); @@ -1060,6 +1219,12 @@ bool BootAnimation::playAnimation(const Animation& animation) return true; } +void BootAnimation::processDisplayEvents() { + // This will poll mDisplayEventReceiver and if there are new events it'll call + // displayEventCallback synchronously. + mLooper->pollOnce(0); +} + void BootAnimation::handleViewport(nsecs_t timestep) { if (mShuttingDown || !mFlingerSurfaceControl || mTargetInset == 0) { return; @@ -1092,7 +1257,7 @@ void BootAnimation::handleViewport(nsecs_t timestep) { SurfaceComposerClient::Transaction t; t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset) .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight)); - t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect); + t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect); t.apply(); mTargetInset = mCurrentInset = 0; @@ -1102,8 +1267,7 @@ void BootAnimation::handleViewport(nsecs_t timestep) { mCurrentInset += delta; } -void BootAnimation::releaseAnimation(Animation* animation) const -{ +void BootAnimation::releaseAnimation(Animation* animation) const { for (Vector<Animation::Part>::iterator it = animation->parts.begin(), e = animation->parts.end(); it != e; ++it) { if (it->animation) @@ -1114,8 +1278,7 @@ void BootAnimation::releaseAnimation(Animation* animation) const delete animation; } -BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) -{ +BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) { if (mLoadedFiles.indexOf(fn) >= 0) { SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed", fn.string()); @@ -1136,10 +1299,10 @@ BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) parseAnimationDesc(*animation); if (!preloadZip(*animation)) { + releaseAnimation(animation); return nullptr; } - mLoadedFiles.remove(fn); return animation; } @@ -1260,7 +1423,7 @@ status_t BootAnimation::TimeCheckThread::readyToRun() { if (mSystemWd < 0) { close(mInotifyFd); mInotifyFd = -1; - SLOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH); + SLOGE("Could not add watch for %s: %s", SYSTEM_DATA_DIR_PATH, strerror(errno)); return NO_INIT; } @@ -1277,5 +1440,4 @@ status_t BootAnimation::TimeCheckThread::readyToRun() { // --------------------------------------------------------------------------- -} -; // namespace android +} // namespace android diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 574d65e425cf..6ba7fd450fbb 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -18,11 +18,14 @@ #define ANDROID_BOOTANIMATION_H #include <vector> +#include <queue> #include <stdint.h> #include <sys/types.h> #include <androidfw/AssetManager.h> +#include <gui/DisplayEventReceiver.h> +#include <utils/Looper.h> #include <utils/Thread.h> #include <binder/IBinder.h> @@ -145,6 +148,11 @@ private: BootAnimation* mBootAnimation; }; + // Display event handling + class DisplayEventCallback; + int displayEventCallback(int fd, int events, void* data); + void processDisplayEvents(); + status_t initTexture(Texture* texture, AssetManager& asset, const char* name); status_t initTexture(FileMap* map, int* width, int* height); status_t initFont(Font* font, const char* fallback); @@ -161,6 +169,9 @@ private: void findBootAnimationFile(); bool findBootAnimationFileInternal(const std::vector<std::string>& files); bool preloadAnimation(); + EGLConfig getEglConfig(const EGLDisplay&); + ui::Size limitSurfaceSize(int width, int height) const; + void resizeSurface(int newWidth, int newHeight); void checkExit(); @@ -171,6 +182,8 @@ private: Texture mAndroid[2]; int mWidth; int mHeight; + int mMaxWidth = 0; + int mMaxHeight = 0; int mCurrentInset; int mTargetInset; bool mUseNpotTextures = false; @@ -189,6 +202,8 @@ private: sp<TimeCheckThread> mTimeCheckThread = nullptr; sp<Callbacks> mCallbacks; Animation* mAnimation = nullptr; + std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver; + sp<Looper> mLooper; }; // --------------------------------------------------------------------------- diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index 55dbc17dba5d..ca1d598ee7d7 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -32,8 +32,11 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; +import android.util.Pair; import java.io.FileDescriptor; +import java.util.ArrayList; +import java.util.List; /** * This class is a command line utility for manipulating content. A client @@ -72,7 +75,7 @@ public class Content { "usage: adb shell content [subcommand] [options]\n" + "\n" + "usage: adb shell content insert --uri <URI> [--user <USER_ID>]" - + " --bind <BINDING> [--bind <BINDING>...]\n" + + " --bind <BINDING> [--bind <BINDING>...] [--extra <BINDING>...]\n" + " <URI> a content provider URI.\n" + " <BINDING> binds a typed value to a column and is formatted:\n" + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n" @@ -84,7 +87,8 @@ public class Content { + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting" + " --bind value:s:new_value\n" + "\n" - + "usage: adb shell content update --uri <URI> [--user <USER_ID>] [--where <WHERE>]\n" + + "usage: adb shell content update --uri <URI> [--user <USER_ID>]" + + " [--where <WHERE>] [--extra <BINDING>...]\n" + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes" + " - see example below).\n" + " Example:\n" @@ -93,14 +97,15 @@ public class Content { + " value:s:newer_value --where \"name=\'new_setting\'\"\n" + "\n" + "usage: adb shell content delete --uri <URI> [--user <USER_ID>] --bind <BINDING>" - + " [--bind <BINDING>...] [--where <WHERE>]\n" + + " [--bind <BINDING>...] [--where <WHERE>] [--extra <BINDING>...]\n" + " Example:\n" + " # Remove \"new_setting\" secure setting.\n" + " adb shell content delete --uri content://settings/secure " + "--where \"name=\'new_setting\'\"\n" + "\n" + "usage: adb shell content query --uri <URI> [--user <USER_ID>]" - + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]\n" + + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]" + + " [--extra <BINDING>...]\n" + " <PROJECTION> is a list of colon separated column names and is formatted:\n" + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n" + " <SORT_ORDER> is the order in which rows in the result should be sorted.\n" @@ -196,6 +201,7 @@ public class Content { Uri uri = null; int userId = UserHandle.USER_SYSTEM; ContentValues values = new ContentValues(); + Bundle extras = new Bundle(); for (String argument; (argument = mTokenizer.nextArg()) != null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); @@ -203,6 +209,8 @@ public class Content { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_BIND.equals(argument)) { parseBindValue(values); + } else if (ARGUMENT_EXTRA.equals(argument)) { + parseBindValue(extras); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } @@ -215,20 +223,23 @@ public class Content { throw new IllegalArgumentException("Bindings not specified." + " Did you specify --bind argument(s)?"); } - return new InsertCommand(uri, userId, values); + return new InsertCommand(uri, userId, values, extras); } private DeleteCommand parseDeleteCommand() { Uri uri = null; int userId = UserHandle.USER_SYSTEM; - String where = null; + Bundle extras = new Bundle(); for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_WHERE.equals(argument)) { - where = argumentValueRequired(argument); + extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, + argumentValueRequired(argument)); + } else if (ARGUMENT_EXTRA.equals(argument)) { + parseBindValue(extras); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } @@ -237,23 +248,26 @@ public class Content { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } - return new DeleteCommand(uri, userId, where); + return new DeleteCommand(uri, userId, extras); } private UpdateCommand parseUpdateCommand() { Uri uri = null; int userId = UserHandle.USER_SYSTEM; - String where = null; ContentValues values = new ContentValues(); + Bundle extras = new Bundle(); for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_WHERE.equals(argument)) { - where = argumentValueRequired(argument); + extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, + argumentValueRequired(argument)); } else if (ARGUMENT_BIND.equals(argument)) { parseBindValue(values); + } else if (ARGUMENT_EXTRA.equals(argument)) { + parseBindValue(extras); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } @@ -266,7 +280,7 @@ public class Content { throw new IllegalArgumentException("Bindings not specified." + " Did you specify --bind argument(s)?"); } - return new UpdateCommand(uri, userId, values, where); + return new UpdateCommand(uri, userId, values, extras); } public CallCommand parseCallCommand() { @@ -274,7 +288,7 @@ public class Content { int userId = UserHandle.USER_SYSTEM; String arg = null; Uri uri = null; - ContentValues values = new ContentValues(); + Bundle extras = new Bundle(); for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); @@ -285,11 +299,10 @@ public class Content { } else if (ARGUMENT_ARG.equals(argument)) { arg = argumentValueRequired(argument); } else if (ARGUMENT_EXTRA.equals(argument)) { - parseBindValue(values); + parseBindValue(extras); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } - } if (uri == null) { throw new IllegalArgumentException("Content provider URI not specified." @@ -298,7 +311,7 @@ public class Content { if (method == null) { throw new IllegalArgumentException("Content provider method not specified."); } - return new CallCommand(uri, userId, method, arg, values); + return new CallCommand(uri, userId, method, arg, extras); } private GetTypeCommand parseGetTypeCommand() { @@ -363,19 +376,22 @@ public class Content { Uri uri = null; int userId = UserHandle.USER_SYSTEM; String[] projection = null; - String sort = null; - String where = null; + Bundle extras = new Bundle(); for (String argument; (argument = mTokenizer.nextArg())!= null;) { if (ARGUMENT_URI.equals(argument)) { uri = Uri.parse(argumentValueRequired(argument)); } else if (ARGUMENT_USER.equals(argument)) { userId = Integer.parseInt(argumentValueRequired(argument)); } else if (ARGUMENT_WHERE.equals(argument)) { - where = argumentValueRequired(argument); + extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, + argumentValueRequired(argument)); } else if (ARGUMENT_SORT.equals(argument)) { - sort = argumentValueRequired(argument); + extras.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, + argumentValueRequired(argument)); } else if (ARGUMENT_PROJECTION.equals(argument)) { projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*"); + } else if (ARGUMENT_EXTRA.equals(argument)) { + parseBindValue(extras); } else { throw new IllegalArgumentException("Unsupported argument: " + argument); } @@ -384,40 +400,76 @@ public class Content { throw new IllegalArgumentException("Content provider URI not specified." + " Did you specify --uri argument?"); } - return new QueryCommand(uri, userId, projection, where, sort); + return new QueryCommand(uri, userId, projection, extras); + } + + private List<String> splitWithEscaping(String argument, char splitChar) { + final List<String> res = new ArrayList<>(); + final StringBuilder cur = new StringBuilder(); + for (int i = 0; i < argument.length(); i++) { + char c = argument.charAt(i); + if (c == '\\') { + if (++i == argument.length()) { + throw new IllegalArgumentException("Invalid escaping"); + } else { + // Skip escaping char and insert next + c = argument.charAt(i); + cur.append(c); + } + } else if (c == splitChar) { + // Splitting char means next string + res.add(cur.toString()); + cur.setLength(0); + } else { + // Copy non-escaping and non-splitting char + cur.append(c); + } + } + res.add(cur.toString()); + return res; } - private void parseBindValue(ContentValues values) { + private Pair<String, Object> parseBindValue() { String argument = mTokenizer.nextArg(); if (TextUtils.isEmpty(argument)) { throw new IllegalArgumentException("Binding not well formed: " + argument); } - final int firstColonIndex = argument.indexOf(COLON); - if (firstColonIndex < 0) { + final List<String> split = splitWithEscaping(argument, COLON.charAt(0)); + if (split.size() != 3) { throw new IllegalArgumentException("Binding not well formed: " + argument); } - final int secondColonIndex = argument.indexOf(COLON, firstColonIndex + 1); - if (secondColonIndex < 0) { - throw new IllegalArgumentException("Binding not well formed: " + argument); - } - String column = argument.substring(0, firstColonIndex); - String type = argument.substring(firstColonIndex + 1, secondColonIndex); - String value = argument.substring(secondColonIndex + 1); + String column = split.get(0); + String type = split.get(1); + String value = split.get(2); if (TYPE_STRING.equals(type)) { - values.put(column, value); + return Pair.create(column, value); } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) { - values.put(column, Boolean.parseBoolean(value)); - } else if (TYPE_INTEGER.equalsIgnoreCase(type) || TYPE_LONG.equalsIgnoreCase(type)) { - values.put(column, Long.parseLong(value)); - } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) { - values.put(column, Double.parseDouble(value)); + return Pair.create(column, Boolean.parseBoolean(value)); + } else if (TYPE_INTEGER.equalsIgnoreCase(type)) { + return Pair.create(column, Integer.parseInt(value)); + } else if (TYPE_LONG.equalsIgnoreCase(type)) { + return Pair.create(column, Long.parseLong(value)); + } else if (TYPE_FLOAT.equalsIgnoreCase(type)) { + return Pair.create(column, Float.parseFloat(value)); + } else if (TYPE_DOUBLE.equalsIgnoreCase(type)) { + return Pair.create(column, Double.parseDouble(value)); } else if (TYPE_NULL.equalsIgnoreCase(type)) { - values.putNull(column); + return Pair.create(column, null); } else { throw new IllegalArgumentException("Unsupported type: " + type); } } + private void parseBindValue(ContentValues values) { + final Pair<String, Object> columnValue = parseBindValue(); + values.putObject(columnValue.first, columnValue.second); + } + + private void parseBindValue(Bundle extras) { + final Pair<String, Object> columnValue = parseBindValue(); + extras.putObject(columnValue.first, columnValue.second); + } + private String argumentValueRequired(String argument) { String value = mTokenizer.nextArg(); if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) { @@ -500,64 +552,48 @@ public class Content { private static class InsertCommand extends Command { final ContentValues mContentValues; + final Bundle mExtras; - public InsertCommand(Uri uri, int userId, ContentValues contentValues) { + public InsertCommand(Uri uri, int userId, ContentValues contentValues, Bundle extras) { super(uri, userId); mContentValues = contentValues; + mExtras = extras; } @Override public void onExecute(IContentProvider provider) throws Exception { - provider.insert(resolveCallingPackage(), mUri, mContentValues); + provider.insert(resolveCallingPackage(), null, mUri, mContentValues, mExtras); } } private static class DeleteCommand extends Command { - final String mWhere; + final Bundle mExtras; - public DeleteCommand(Uri uri, int userId, String where) { + public DeleteCommand(Uri uri, int userId, Bundle extras) { super(uri, userId); - mWhere = where; + mExtras = extras; } @Override public void onExecute(IContentProvider provider) throws Exception { - provider.delete(resolveCallingPackage(), mUri, mWhere, null); + provider.delete(resolveCallingPackage(), null, mUri, mExtras); } } private static class CallCommand extends Command { final String mMethod, mArg; - Bundle mExtras = null; + final Bundle mExtras; - public CallCommand(Uri uri, int userId, String method, String arg, ContentValues values) { + public CallCommand(Uri uri, int userId, String method, String arg, Bundle extras) { super(uri, userId); mMethod = method; mArg = arg; - if (values != null) { - mExtras = new Bundle(); - for (String key : values.keySet()) { - final Object val = values.get(key); - if (val instanceof String) { - mExtras.putString(key, (String) val); - } else if (val instanceof Float) { - mExtras.putFloat(key, (Float) val); - } else if (val instanceof Double) { - mExtras.putDouble(key, (Double) val); - } else if (val instanceof Boolean) { - mExtras.putBoolean(key, (Boolean) val); - } else if (val instanceof Integer) { - mExtras.putInt(key, (Integer) val); - } else if (val instanceof Long) { - mExtras.putLong(key, (Long) val); - } - } - } + mExtras = extras; } @Override public void onExecute(IContentProvider provider) throws Exception { - Bundle result = provider.call(null, mUri.getAuthority(), mMethod, mArg, mExtras); + Bundle result = provider.call(null, null, mUri.getAuthority(), mMethod, mArg, mExtras); if (result != null) { result.size(); // unpack } @@ -584,7 +620,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { - try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) { + try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "r", null, null)) { FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out); } } @@ -597,27 +633,26 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { - try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) { + try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "w", null, null)) { FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor()); } } } - private static class QueryCommand extends DeleteCommand { + private static class QueryCommand extends Command { final String[] mProjection; - final String mSortOrder; + final Bundle mExtras; - public QueryCommand( - Uri uri, int userId, String[] projection, String where, String sortOrder) { - super(uri, userId, where); + public QueryCommand(Uri uri, int userId, String[] projection, Bundle extras) { + super(uri, userId); mProjection = projection; - mSortOrder = sortOrder; + mExtras = extras; } @Override public void onExecute(IContentProvider provider) throws Exception { - Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection, - ContentResolver.createSqlQueryBundle(mWhere, null, mSortOrder), null); + Cursor cursor = provider.query(resolveCallingPackage(), null, mUri, mProjection, + mExtras, null); if (cursor == null) { System.out.println("No result found."); return; @@ -669,17 +704,19 @@ public class Content { } } - private static class UpdateCommand extends InsertCommand { - final String mWhere; + private static class UpdateCommand extends Command { + final ContentValues mValues; + final Bundle mExtras; - public UpdateCommand(Uri uri, int userId, ContentValues contentValues, String where) { - super(uri, userId, contentValues); - mWhere = where; + public UpdateCommand(Uri uri, int userId, ContentValues values, Bundle extras) { + super(uri, userId); + mValues = values; + mExtras = extras; } @Override public void onExecute(IContentProvider provider) throws Exception { - provider.update(resolveCallingPackage(), mUri, mContentValues, mWhere, null); + provider.update(resolveCallingPackage(), null, mUri, mValues, mExtras); } } diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java index 6c6797a328c9..d0c2a24d5314 100644 --- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java +++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java @@ -48,8 +48,8 @@ public final class Dpm extends BaseCommand { private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record"; private static final String COMMAND_FORCE_NETWORK_LOGS = "force-network-logs"; private static final String COMMAND_FORCE_SECURITY_LOGS = "force-security-logs"; - private static final String COMMAND_GRANT_PO_DEVICE_ID_ACCESS = - "grant-profile-owner-device-ids-access"; + private static final String COMMAND_MARK_PO_ON_ORG_OWNED_DEVICE = + "mark-profile-owner-on-organization-owned-device"; private IDevicePolicyManager mDevicePolicyManager; private int mUserId = UserHandle.USER_SYSTEM; @@ -93,7 +93,7 @@ public final class Dpm extends BaseCommand { "dpm " + COMMAND_FORCE_SECURITY_LOGS + ": makes all security logs available to " + "the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed." + "\n" - + "usage: dpm " + COMMAND_GRANT_PO_DEVICE_ID_ACCESS + ": " + + "usage: dpm " + COMMAND_MARK_PO_ON_ORG_OWNED_DEVICE + ": " + "[ --user <USER_ID> | current ] <COMPONENT>\n"); } @@ -129,8 +129,8 @@ public final class Dpm extends BaseCommand { case COMMAND_FORCE_SECURITY_LOGS: runForceSecurityLogs(); break; - case COMMAND_GRANT_PO_DEVICE_ID_ACCESS: - runGrantProfileOwnerDeviceIdsAccess(); + case COMMAND_MARK_PO_ON_ORG_OWNED_DEVICE: + runMarkProfileOwnerOnOrganizationOwnedDevice(); break; default: throw new IllegalArgumentException ("unknown command '" + command + "'"); @@ -251,9 +251,9 @@ public final class Dpm extends BaseCommand { } - private void runGrantProfileOwnerDeviceIdsAccess() throws RemoteException { + private void runMarkProfileOwnerOnOrganizationOwnedDevice() throws RemoteException { parseArgs(/*canHaveName=*/ false); - mDevicePolicyManager.grantDeviceIdsAccessToProfileOwner(mComponent, mUserId); + mDevicePolicyManager.markProfileOwnerOnOrganizationOwnedDevice(mComponent, mUserId); System.out.println("Success"); } diff --git a/cmds/hid/README.md b/cmds/hid/README.md index 7e22d08eeaeb..620336f30327 100644 --- a/cmds/hid/README.md +++ b/cmds/hid/README.md @@ -38,17 +38,21 @@ legal JSON format, as this would imply multiple root elements). Register a new uhid device | Field | Type | Description | -|:-------------:|:-------------:|:--------------------------| +|:-------------:|:-------------:|:-------------------------- | | id | integer | Device id | | command | string | Must be set to "register" | | name | string | Device name | | vid | 16-bit integer| Vendor id | | pid | 16-bit integer| Product id | +| bus | string | Bus that device should use | | descriptor | byte array | USB HID report descriptor | Device ID is used for matching the subsequent commands to a specific device to avoid ambiguity when multiple devices are registered. +Device bus is used to determine how the uhid device is connected to the host. +The options are "usb" and "bluetooth". + USB HID report descriptor should be generated according the the USB HID spec and can be checked by reverse parsing using a variety of tools, for example [usbdescreqparser][5]. @@ -61,6 +65,7 @@ Example: "name": "Odie (Test)", "vid": 0x18d1, "pid": 0x2c40, + "bus": "usb", "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00, 0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a, @@ -142,4 +147,4 @@ for debugging purposes. [3]: ../../../../cts/tests/tests/hardware/res/raw/ [4]: https://developer.android.com/training/game-controllers/controller-input.html#button [5]: http://eleccelerator.com/usbdescreqparser/ -[6]: https://developer.android.com/training/game-controllers/controller-input.html
\ No newline at end of file +[6]: https://developer.android.com/training/game-controllers/controller-input.html diff --git a/cmds/hid/jni/Android.bp b/cmds/hid/jni/Android.bp index 095cfc6ceb53..2c07de04b6a7 100644 --- a/cmds/hid/jni/Android.bp +++ b/cmds/hid/jni/Android.bp @@ -5,6 +5,7 @@ cc_library_shared { shared_libs: [ "libandroid", + "libbase", "liblog", "libnativehelper", ], diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp index d4fdf85491d3..1e200c52a207 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.cpp +++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp @@ -21,17 +21,21 @@ #include <linux/uhid.h> #include <fcntl.h> +#include <inttypes.h> +#include <unistd.h> #include <cstdio> #include <cstring> #include <memory> -#include <unistd.h> +#include <android/log.h> +#include <android/looper.h> #include <jni.h> #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> -#include <android/looper.h> -#include <android/log.h> + +#include <android-base/stringprintf.h> #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) @@ -46,6 +50,7 @@ static const char* UHID_PATH = "/dev/uhid"; static struct { jmethodID onDeviceOpen; jmethodID onDeviceGetReport; + jmethodID onDeviceOutput; jmethodID onDeviceError; } gDeviceCallbackClassInfo; @@ -61,6 +66,26 @@ static void checkAndClearException(JNIEnv* env, const char* methodName) { } } +static ScopedLocalRef<jbyteArray> toJbyteArray(JNIEnv* env, const std::vector<uint8_t>& vector) { + ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(vector.size())); + if (array.get() == nullptr) { + jniThrowException(env, "java/lang/OutOfMemoryError", nullptr); + return array; + } + static_assert(sizeof(char) == sizeof(uint8_t)); + env->SetByteArrayRegion(array.get(), 0, vector.size(), + reinterpret_cast<const signed char*>(vector.data())); + return array; +} + +static std::string toString(const std::vector<uint8_t>& data) { + std::string s = ""; + for (uint8_t b : data) { + s += android::base::StringPrintf("%x ", b); + } + return s; +} + DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) : mCallbackObject(env->NewGlobalRef(callback)) { env->GetJavaVM(&mJavaVM); @@ -90,23 +115,30 @@ void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) { checkAndClearException(env, "onDeviceGetReport"); } +void DeviceCallback::onDeviceOutput(const std::vector<uint8_t>& data) { + JNIEnv* env = getJNIEnv(); + env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, + toJbyteArray(env, data).get()); + checkAndClearException(env, "onDeviceOutput"); +} + JNIEnv* DeviceCallback::getJNIEnv() { JNIEnv* env; mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); return env; } -Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, - std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback) { - +std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, + uint16_t bus, const std::vector<uint8_t>& descriptor, + std::unique_ptr<DeviceCallback> callback) { size_t size = descriptor.size(); if (size > HID_MAX_DESCRIPTOR_SIZE) { LOGE("Received invalid hid report with descriptor size %zu, skipping", size); return nullptr; } - int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC); - if (fd < 0) { + android::base::unique_fd fd(::open(UHID_PATH, O_RDWR | O_CLOEXEC)); + if (!fd.ok()) { LOGE("Failed to open uhid: %s", strerror(errno)); return nullptr; } @@ -114,10 +146,11 @@ Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, struct uhid_event ev = {}; ev.type = UHID_CREATE2; strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name)); - memcpy(&ev.u.create2.rd_data, descriptor.data(), - size * sizeof(ev.u.create2.rd_data[0])); + std::string uniq = android::base::StringPrintf("Id: %d", id); + strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq)); + memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0])); ev.u.create2.rd_size = size; - ev.u.create2.bus = BUS_BLUETOOTH; + ev.u.create2.bus = bus; ev.u.create2.vendor = vid; ev.u.create2.product = pid; ev.u.create2.version = 0; @@ -126,7 +159,6 @@ Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, errno = 0; ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev))); if (ret < 0 || ret != sizeof(ev)) { - ::close(fd); LOGE("Failed to create uhid node: %s", strerror(errno)); return nullptr; } @@ -134,21 +166,21 @@ Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, // Wait for the device to actually be created. ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev))); if (ret < 0 || ev.type != UHID_START) { - ::close(fd); LOGE("uhid node failed to start: %s", strerror(errno)); return nullptr; } - return new Device(id, fd, std::move(callback)); + // using 'new' to access non-public constructor + return std::unique_ptr<Device>(new Device(id, std::move(fd), std::move(callback))); } -Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback) : - mId(id), mFd(fd), mDeviceCallback(std::move(callback)) { +Device::Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback) + : mId(id), mFd(std::move(fd)), mDeviceCallback(std::move(callback)) { ALooper* aLooper = ALooper_forThread(); if (aLooper == NULL) { LOGE("Could not get ALooper, ALooper_forThread returned NULL"); aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); } - ALooper_addFd(aLooper, fd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents, + ALooper_addFd(aLooper, mFd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents, reinterpret_cast<void*>(this)); } @@ -162,8 +194,14 @@ Device::~Device() { struct uhid_event ev = {}; ev.type = UHID_DESTROY; TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); - ::close(mFd); - mFd = -1; +} + +// Send event over the fd. +static void writeEvent(int fd, struct uhid_event& ev, const char* messageType) { + ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev))); + if (ret < 0 || ret != sizeof(ev)) { + LOGE("Failed to send uhid_event %s: %s", messageType, strerror(errno)); + } } void Device::sendReport(const std::vector<uint8_t>& report) const { @@ -176,10 +214,7 @@ void Device::sendReport(const std::vector<uint8_t>& report) const { ev.type = UHID_INPUT2; ev.u.input2.size = report.size(); memcpy(&ev.u.input2.data, report.data(), report.size() * sizeof(ev.u.input2.data[0])); - ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); - if (ret < 0 || ret != sizeof(ev)) { - LOGE("Failed to send hid event: %s", strerror(errno)); - } + writeEvent(mFd, ev, "UHID_INPUT2"); } void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const { @@ -190,10 +225,7 @@ void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& ev.u.get_report_reply.size = report.size(); memcpy(&ev.u.get_report_reply.data, report.data(), report.size() * sizeof(ev.u.get_report_reply.data[0])); - ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); - if (ret < 0 || ret != sizeof(ev)) { - LOGE("Failed to send hid event (UHID_GET_REPORT_REPLY): %s", strerror(errno)); - } + writeEvent(mFd, ev, "UHID_GET_REPORT_REPLY"); } int Device::handleEvents(int events) { @@ -210,13 +242,37 @@ int Device::handleEvents(int events) { return 0; } - if (ev.type == UHID_OPEN) { - mDeviceCallback->onDeviceOpen(); - } else if (ev.type == UHID_GET_REPORT) { - mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum); - } else if (ev.type == UHID_SET_REPORT) { - LOGE("UHID_SET_REPORT is currently not supported"); - return 0; + switch (ev.type) { + case UHID_OPEN: { + mDeviceCallback->onDeviceOpen(); + break; + } + case UHID_GET_REPORT: { + mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum); + break; + } + case UHID_SET_REPORT: { + const struct uhid_set_report_req& set_report = ev.u.set_report; + if (set_report.size > UHID_DATA_MAX) { + LOGE("SET_REPORT contains too much data: size = %" PRIu16, set_report.size); + return 0; + } + + std::vector<uint8_t> data(set_report.data, set_report.data + set_report.size); + LOGI("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id, + set_report.rnum, toString(data).c_str()); + break; + } + case UHID_OUTPUT: { + struct uhid_output_req& output = ev.u.output; + std::vector<uint8_t> data(output.data, output.data + output.size); + mDeviceCallback->onDeviceOutput(data); + break; + } + default: { + LOGI("Unhandled event type: %" PRIu32, ev.type); + break; + } } return 1; @@ -239,8 +295,8 @@ std::vector<uint8_t> getData(JNIEnv* env, jbyteArray javaArray) { return data; } -static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, jint pid, - jbyteArray rawDescriptor, jobject callback) { +static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, + jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; @@ -250,9 +306,10 @@ static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint i std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback)); - uhid::Device* d = uhid::Device::open( - id, reinterpret_cast<const char*>(name.c_str()), vid, pid, desc, std::move(cb)); - return reinterpret_cast<jlong>(d); + std::unique_ptr<uhid::Device> d = + uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc, + std::move(cb)); + return reinterpret_cast<jlong>(d.release()); } static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray rawReport) { @@ -284,14 +341,14 @@ static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { } static JNINativeMethod sMethods[] = { - { "nativeOpenDevice", - "(Ljava/lang/String;III[B" - "Lcom/android/commands/hid/Device$DeviceCallback;)J", - reinterpret_cast<void*>(openDevice) }, - { "nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport) }, - { "nativeSendGetFeatureReportReply", "(JI[B)V", - reinterpret_cast<void*>(sendGetFeatureReportReply) }, - { "nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice) }, + {"nativeOpenDevice", + "(Ljava/lang/String;IIII[B" + "Lcom/android/commands/hid/Device$DeviceCallback;)J", + reinterpret_cast<void*>(openDevice)}, + {"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)}, + {"nativeSendGetFeatureReportReply", "(JI[B)V", + reinterpret_cast<void*>(sendGetFeatureReportReply)}, + {"nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice)}, }; int register_com_android_commands_hid_Device(JNIEnv* env) { @@ -304,6 +361,8 @@ int register_com_android_commands_hid_Device(JNIEnv* env) { env->GetMethodID(clazz, "onDeviceOpen", "()V"); uhid::gDeviceCallbackClassInfo.onDeviceGetReport = env->GetMethodID(clazz, "onDeviceGetReport", "(II)V"); + uhid::gDeviceCallbackClassInfo.onDeviceOutput = + env->GetMethodID(clazz, "onDeviceOutput", "([B)V"); uhid::gDeviceCallbackClassInfo.onDeviceError = env->GetMethodID(clazz, "onDeviceError", "()V"); if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL || diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h index 892c7cd12953..7202b45adcde 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.h +++ b/cmds/hid/jni/com_android_commands_hid_Device.h @@ -19,6 +19,8 @@ #include <jni.h> +#include <android-base/unique_fd.h> + namespace android { namespace uhid { @@ -29,6 +31,7 @@ public: void onDeviceOpen(); void onDeviceGetReport(uint32_t requestId, uint8_t reportId); + void onDeviceOutput(const std::vector<uint8_t>& data); void onDeviceError(); private: @@ -39,10 +42,10 @@ private: class Device { public: - static Device* open(int32_t id, const char* name, int32_t vid, int32_t pid, - std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback); + static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid, + uint16_t bus, const std::vector<uint8_t>& descriptor, + std::unique_ptr<DeviceCallback> callback); - Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback); ~Device(); void sendReport(const std::vector<uint8_t>& report) const; @@ -52,8 +55,9 @@ public: int handleEvents(int events); private: + Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback); int32_t mId; - int mFd; + android::base::unique_fd mFd; std::unique_ptr<DeviceCallback> mDeviceCallback; }; diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java index 616d411ef7bb..dade41511ae6 100644 --- a/cmds/hid/src/com/android/commands/hid/Device.java +++ b/cmds/hid/src/com/android/commands/hid/Device.java @@ -20,13 +20,16 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.MessageQueue; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import com.android.internal.os.SomeArgs; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Map; + public class Device { private static final String TAG = "HidDevice"; @@ -40,6 +43,7 @@ public class Device { private final DeviceHandler mHandler; // mFeatureReports is limited to 256 entries, because the report number is 8-bit private final SparseArray<byte[]> mFeatureReports; + private final Map<ByteBuffer, byte[]> mOutputs; private long mTimeToSend; private final Object mCond = new Object(); @@ -48,23 +52,25 @@ public class Device { System.loadLibrary("hidcommand_jni"); } - private static native long nativeOpenDevice(String name, int id, int vid, int pid, + private static native long nativeOpenDevice(String name, int id, int vid, int pid, int bus, byte[] descriptor, DeviceCallback callback); private static native void nativeSendReport(long ptr, byte[] data); private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data); private static native void nativeCloseDevice(long ptr); - public Device(int id, String name, int vid, int pid, byte[] descriptor, - byte[] report, SparseArray<byte[]> featureReports) { + public Device(int id, String name, int vid, int pid, int bus, byte[] descriptor, + byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) { mId = id; mThread = new HandlerThread("HidDeviceHandler"); mThread.start(); mHandler = new DeviceHandler(mThread.getLooper()); mFeatureReports = featureReports; + mOutputs = outputs; SomeArgs args = SomeArgs.obtain(); args.argi1 = id; args.argi2 = vid; args.argi3 = pid; + args.argi4 = bus; if (name != null) { args.arg1 = name; } else { @@ -110,7 +116,7 @@ public class Device { case MSG_OPEN_DEVICE: SomeArgs args = (SomeArgs) msg.obj; mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3, - (byte[]) args.arg2, new DeviceCallback()); + args.argi4, (byte[]) args.arg2, new DeviceCallback()); pauseEvents(); break; case MSG_SEND_REPORT: @@ -160,6 +166,11 @@ public class Device { } public void onDeviceGetReport(int requestId, int reportId) { + if (mFeatureReports == null) { + Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId + + ", but 'feature_reports' section is not found"); + return; + } byte[] report = mFeatureReports.get(reportId); if (report == null) { @@ -176,6 +187,29 @@ public class Device { mHandler.sendMessageAtTime(msg, mTimeToSend); } + // native callback + public void onDeviceOutput(byte[] data) { + if (mOutputs == null) { + Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found"); + return; + } + byte[] response = mOutputs.get(ByteBuffer.wrap(data)); + if (response == null) { + Log.i(TAG, + "Requested response for output " + Arrays.toString(data) + " is not found"); + return; + } + + Message msg; + msg = mHandler.obtainMessage(MSG_SEND_REPORT, response); + + // Message is set to asynchronous so it won't be blocked by synchronization + // barrier during UHID_OPEN. This is necessary for drivers that do + // UHID_OUTPUT requests during probe, and expect a response right away. + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, mTimeToSend); + } + public void onDeviceError() { Log.e(TAG, "Device error occurred, closing /dev/uhid"); Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java index 746e37289076..d4bf1d820a70 100644 --- a/cmds/hid/src/com/android/commands/hid/Event.java +++ b/cmds/hid/src/com/android/commands/hid/Event.java @@ -21,10 +21,13 @@ import android.util.JsonToken; import android.util.Log; import android.util.SparseArray; -import java.io.InputStreamReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; public class Event { private static final String TAG = "HidEvent"; @@ -33,14 +36,31 @@ public class Event { public static final String COMMAND_DELAY = "delay"; public static final String COMMAND_REPORT = "report"; + // These constants come from "include/uapi/linux/input.h" in the kernel + enum Bus { + USB(0x03), BLUETOOTH(0x05); + + Bus(int value) { + mValue = value; + } + + int getValue() { + return mValue; + } + + private int mValue; + } + private int mId; private String mCommand; private String mName; private byte[] mDescriptor; private int mVid; private int mPid; + private Bus mBus; private byte[] mReport; private SparseArray<byte[]> mFeatureReports; + private Map<ByteBuffer, byte[]> mOutputs; private int mDuration; public int getId() { @@ -67,6 +87,10 @@ public class Event { return mPid; } + public int getBus() { + return mBus.getValue(); + } + public byte[] getReport() { return mReport; } @@ -75,6 +99,10 @@ public class Event { return mFeatureReports; } + public Map<ByteBuffer, byte[]> getOutputs() { + return mOutputs; + } + public int getDuration() { return mDuration; } @@ -86,8 +114,10 @@ public class Event { + ", descriptor=" + Arrays.toString(mDescriptor) + ", vid=" + mVid + ", pid=" + mPid + + ", bus=" + mBus + ", report=" + Arrays.toString(mReport) + ", feature_reports=" + mFeatureReports.toString() + + ", outputs=" + mOutputs.toString() + ", duration=" + mDuration + "}"; } @@ -123,6 +153,10 @@ public class Event { mEvent.mFeatureReports = reports; } + public void setOutputs(Map<ByteBuffer, byte[]> outputs) { + mEvent.mOutputs = outputs; + } + public void setVid(int vid) { mEvent.mVid = vid; } @@ -131,6 +165,10 @@ public class Event { mEvent.mPid = pid; } + public void setBus(Bus bus) { + mEvent.mBus = bus; + } + public void setDuration(int duration) { mEvent.mDuration = duration; } @@ -193,12 +231,18 @@ public class Event { case "pid": eb.setPid(readInt()); break; + case "bus": + eb.setBus(readBus()); + break; case "report": eb.setReport(readData()); break; case "feature_reports": eb.setFeatureReports(readFeatureReports()); break; + case "outputs": + eb.setOutputs(readOutputs()); + break; case "duration": eb.setDuration(readInt()); break; @@ -248,9 +292,14 @@ public class Event { return Integer.decode(val); } + private Bus readBus() throws IOException { + String val = mReader.nextString(); + return Bus.valueOf(val.toUpperCase()); + } + private SparseArray<byte[]> readFeatureReports() throws IllegalStateException, IOException { - SparseArray<byte[]> featureReports = new SparseArray(); + SparseArray<byte[]> featureReports = new SparseArray<>(); try { mReader.beginArray(); while (mReader.hasNext()) { @@ -276,17 +325,60 @@ public class Event { } } mReader.endObject(); - if (data != null) + if (data != null) { featureReports.put(id, data); + } } mReader.endArray(); - } catch (IllegalStateException|NumberFormatException e) { + } catch (IllegalStateException | NumberFormatException e) { consumeRemainingElements(); mReader.endArray(); throw new IllegalStateException("Encountered malformed data.", e); - } finally { - return featureReports; } + return featureReports; + } + + private Map<ByteBuffer, byte[]> readOutputs() + throws IllegalStateException, IOException { + Map<ByteBuffer, byte[]> outputs = new HashMap<>(); + + try { + mReader.beginArray(); + while (mReader.hasNext()) { + byte[] output = null; + byte[] response = null; + mReader.beginObject(); + while (mReader.hasNext()) { + String name = mReader.nextName(); + switch (name) { + case "description": + // Description is only used to keep track of the output responses + mReader.nextString(); + break; + case "output": + output = readData(); + break; + case "response": + response = readData(); + break; + default: + consumeRemainingElements(); + mReader.endObject(); + throw new IllegalStateException("Invalid key in outputs: " + name); + } + } + mReader.endObject(); + if (output != null) { + outputs.put(ByteBuffer.wrap(output), response); + } + } + mReader.endArray(); + } catch (IllegalStateException | NumberFormatException e) { + consumeRemainingElements(); + mReader.endArray(); + throw new IllegalStateException("Encountered malformed data.", e); + } + return outputs; } private void consumeRemainingElements() throws IOException { @@ -296,10 +388,6 @@ public class Event { } } - private static void error(String msg) { - error(msg, null); - } - private static void error(String msg, Exception e) { System.out.println(msg); Log.e(TAG, msg); diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java index 54ac1b0733ff..fac0ab2ef125 100644 --- a/cmds/hid/src/com/android/commands/hid/Hid.java +++ b/cmds/hid/src/com/android/commands/hid/Hid.java @@ -16,22 +16,17 @@ package com.android.commands.hid; -import android.util.JsonReader; -import android.util.JsonToken; import android.util.Log; import android.util.SparseArray; import libcore.io.IoUtils; -import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; public class Hid { private static final String TAG = "HID"; @@ -118,8 +113,8 @@ public class Hid { "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!"); } int id = e.getId(); - Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), - e.getDescriptor(), e.getReport(), e.getFeatureReports()); + Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(), + e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs()); mDevices.append(id, d); } diff --git a/cmds/idmap/Android.bp b/cmds/idmap/Android.bp deleted file mode 100644 index ae5d74a47000..000000000000 --- a/cmds/idmap/Android.bp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2012 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. - -cc_binary { - name: "idmap", - - srcs: [ - "idmap.cpp", - "create.cpp", - "scan.cpp", - "inspect.cpp", - ], - - shared_libs: [ - "liblog", - "libutils", - "libandroidfw", - "libcutils", - ], - - cflags: [ - "-Wall", - "-Werror", - "-Wunused", - "-Wunreachable-code", - ], -} diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp deleted file mode 100644 index f415f8f5dd75..000000000000 --- a/cmds/idmap/create.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include "idmap.h" - -#include <memory> -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> -#include <androidfw/ZipFileRO.h> -#include <utils/String8.h> - -#include <fcntl.h> -#include <sys/file.h> -#include <sys/stat.h> - -using namespace android; - -namespace { - int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc) - { - std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(zip_path)); - if (zip.get() == NULL) { - return -1; - } - ZipEntryRO entry = zip->findEntryByName(entry_name); - if (entry == NULL) { - return -1; - } - if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, crc)) { - return -1; - } - zip->releaseEntry(entry); - return 0; - } - - int open_idmap(const char *path) - { - int fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)); - if (fd == -1) { - ALOGD("error: open %s: %s\n", path, strerror(errno)); - goto fail; - } - if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) { - ALOGD("error: fchmod %s: %s\n", path, strerror(errno)); - goto fail; - } - if (TEMP_FAILURE_RETRY(flock(fd, LOCK_EX)) != 0) { - ALOGD("error: flock %s: %s\n", path, strerror(errno)); - goto fail; - } - - return fd; -fail: - if (fd != -1) { - close(fd); - unlink(path); - } - return -1; - } - - int write_idmap(int fd, const uint32_t *data, size_t size) - { - if (lseek(fd, 0, SEEK_SET) < 0) { - return -1; - } - size_t bytesLeft = size; - while (bytesLeft > 0) { - ssize_t w = TEMP_FAILURE_RETRY(write(fd, data + size - bytesLeft, bytesLeft)); - if (w < 0) { - fprintf(stderr, "error: write: %s\n", strerror(errno)); - return -1; - } - bytesLeft -= static_cast<size_t>(w); - } - return 0; - } - - bool is_idmap_stale_fd(const char *target_apk_path, const char *overlay_apk_path, int idmap_fd) - { - static const size_t N = ResTable::IDMAP_HEADER_SIZE_BYTES; - struct stat st; - if (fstat(idmap_fd, &st) == -1) { - return true; - } - if (st.st_size < static_cast<off_t>(N)) { - // file is empty or corrupt - return true; - } - - char buf[N]; - size_t bytesLeft = N; - if (lseek(idmap_fd, 0, SEEK_SET) < 0) { - return true; - } - for (;;) { - ssize_t r = TEMP_FAILURE_RETRY(read(idmap_fd, buf + N - bytesLeft, bytesLeft)); - if (r < 0) { - return true; - } - bytesLeft -= static_cast<size_t>(r); - if (bytesLeft == 0) { - break; - } - if (r == 0) { - // "shouldn't happen" - return true; - } - } - - uint32_t version, cached_target_crc, cached_overlay_crc; - String8 cached_target_path, cached_overlay_path; - if (!ResTable::getIdmapInfo(buf, N, &version, &cached_target_crc, &cached_overlay_crc, - &cached_target_path, &cached_overlay_path)) { - return true; - } - - if (version != ResTable::IDMAP_CURRENT_VERSION) { - return true; - } - - if (cached_target_path != target_apk_path) { - return true; - } - if (cached_overlay_path != overlay_apk_path) { - return true; - } - - uint32_t actual_target_crc, actual_overlay_crc; - if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME, - &actual_target_crc) == -1) { - return true; - } - if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME, - &actual_overlay_crc) == -1) { - return true; - } - - return cached_target_crc != actual_target_crc || cached_overlay_crc != actual_overlay_crc; - } - - bool is_idmap_stale_path(const char *target_apk_path, const char *overlay_apk_path, - const char *idmap_path) - { - struct stat st; - if (stat(idmap_path, &st) == -1) { - // non-existing idmap is always stale; on other errors, abort idmap generation - return errno == ENOENT; - } - - int idmap_fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY)); - if (idmap_fd == -1) { - return false; - } - bool is_stale = is_idmap_stale_fd(target_apk_path, overlay_apk_path, idmap_fd); - close(idmap_fd); - return is_stale; - } - - int create_idmap(const char *target_apk_path, const char *overlay_apk_path, - uint32_t **data, size_t *size) - { - uint32_t target_crc, overlay_crc; - if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME, - &target_crc) == -1) { - return -1; - } - if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME, - &overlay_crc) == -1) { - return -1; - } - - AssetManager am; - bool b = am.createIdmap(target_apk_path, overlay_apk_path, target_crc, overlay_crc, - data, size); - return b ? 0 : -1; - } - - int create_and_write_idmap(const char *target_apk_path, const char *overlay_apk_path, - int fd, bool check_if_stale) - { - if (check_if_stale) { - if (!is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd)) { - // already up to date -- nothing to do - return 0; - } - } - - uint32_t *data = NULL; - size_t size; - - if (create_idmap(target_apk_path, overlay_apk_path, &data, &size) == -1) { - return -1; - } - - if (write_idmap(fd, data, size) == -1) { - free(data); - return -1; - } - - free(data); - return 0; - } -} - -int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path, - const char *idmap_path) -{ - if (!is_idmap_stale_path(target_apk_path, overlay_apk_path, idmap_path)) { - // already up to date -- nothing to do - return EXIT_SUCCESS; - } - - int fd = open_idmap(idmap_path); - if (fd == -1) { - return EXIT_FAILURE; - } - - int r = create_and_write_idmap(target_apk_path, overlay_apk_path, fd, false); - close(fd); - if (r != 0) { - unlink(idmap_path); - } - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} - -int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd) -{ - return create_and_write_idmap(target_apk_path, overlay_apk_path, fd, true) == 0 ? - EXIT_SUCCESS : EXIT_FAILURE; -} - -int idmap_verify_fd(const char *target_apk_path, const char *overlay_apk_path, int fd) -{ - return !is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd) ? - EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp deleted file mode 100644 index 8f86ed8f7d32..000000000000 --- a/cmds/idmap/idmap.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include "idmap.h" - -#include <private/android_filesystem_config.h> // for AID_SYSTEM - -#include <stdlib.h> -#include <string.h> - -namespace { - const char *usage = "NAME\n\ - idmap - create or display idmap files\n\ -\n\ -SYNOPSIS \n\ - idmap --help \n\ - idmap --fd target overlay fd \n\ - idmap --path target overlay idmap \n\ - idmap --scan target-package-name-to-look-for path-to-target-apk dir-to-hold-idmaps \\\ - dir-to-scan [additional-dir-to-scan [additional-dir-to-scan [...]]]\n\ - idmap --inspect idmap \n\ - idmap --verify target overlay fd \n\ -\n\ -DESCRIPTION \n\ - Idmap files play an integral part in the runtime resource overlay framework. An idmap \n\ - file contains a mapping of resource identifiers between overlay package and its target \n\ - package; this mapping is used during resource lookup. Idmap files also act as control \n\ - files by their existence: if not present, the corresponding overlay package is ignored \n\ - when the resource context is created. \n\ -\n\ - Idmap files are stored in /data/resource-cache. For each pair (target package, overlay \n\ - package), there exists exactly one idmap file, or none if the overlay should not be used. \n\ -\n\ -NOMENCLATURE \n\ - target: the original, non-overlay, package. Each target package may be associated with \n\ - any number of overlay packages. \n\ -\n\ - overlay: an overlay package. Each overlay package is associated with exactly one target \n\ - package, specified in the overlay's manifest using the <overlay target=\"...\"/> \n\ - tag. \n\ -\n\ -OPTIONS \n\ - --help: display this help \n\ -\n\ - --fd: create idmap for target package 'target' (path to apk) and overlay package 'overlay' \n\ - (path to apk); write results to file descriptor 'fd' (integer). This invocation \n\ - version is intended to be used by a parent process with higher privileges to call \n\ - idmap in a controlled way: the parent will open a suitable file descriptor, fork, \n\ - drop its privileges and exec. This tool will continue execution without the extra \n\ - privileges, but still have write access to a file it could not have opened on its \n\ - own. \n\ -\n\ - --path: create idmap for target package 'target' (path to apk) and overlay package \n\ - 'overlay' (path to apk); write results to 'idmap' (path). \n\ -\n\ - --scan: non-recursively search directory 'dir-to-scan' (path) for static overlay packages \n\ - with target package 'target-package-name-to-look-for' (package name) present at\n\ - 'path-to-target-apk' (path to apk). For each overlay package found, create an\n\ - idmap file in 'dir-to-hold-idmaps' (path). \n\ -\n\ - --inspect: decode the binary format of 'idmap' (path) and display the contents in a \n\ - debug-friendly format. \n\ -\n\ - --verify: verify if idmap corresponding to file descriptor 'fd' (integer) is made from \n\ - target package 'target' (path to apk) and overlay package 'overlay'. \n\ -\n\ -EXAMPLES \n\ - Create an idmap file: \n\ -\n\ - $ adb shell idmap --path /system/app/target.apk \\ \n\ - /vendor/overlay/overlay.apk \\ \n\ - /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\ -\n\ - Display an idmap file: \n\ -\n\ - $ adb shell idmap --inspect /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\ - SECTION ENTRY VALUE COMMENT \n\ - IDMAP HEADER magic 0x706d6469 \n\ - base crc 0xb65a383f \n\ - overlay crc 0x7b9675e8 \n\ - base path .......... /path/to/target.apk \n\ - overlay path .......... /path/to/overlay.apk \n\ - DATA HEADER target pkg 0x0000007f \n\ - types count 0x00000003 \n\ - DATA BLOCK target type 0x00000002 \n\ - overlay type 0x00000002 \n\ - entry count 0x00000001 \n\ - entry offset 0x00000000 \n\ - entry 0x00000000 drawable/drawable \n\ - DATA BLOCK target type 0x00000003 \n\ - overlay type 0x00000003 \n\ - entry count 0x00000001 \n\ - entry offset 0x00000000 \n\ - entry 0x00000000 xml/integer \n\ - DATA BLOCK target type 0x00000004 \n\ - overlay type 0x00000004 \n\ - entry count 0x00000001 \n\ - entry offset 0x00000000 \n\ - entry 0x00000000 raw/lorem_ipsum \n\ -\n\ - In this example, the overlay package provides three alternative resource values:\n\ - drawable/drawable, xml/integer, and raw/lorem_ipsum \n\ -\n\ -NOTES \n\ - This tool and its expected invocation from installd is modelled on dexopt."; - - bool verify_directory_readable(const char *path) - { - return access(path, R_OK | X_OK) == 0; - } - - bool verify_directory_writable(const char *path) - { - return access(path, W_OK) == 0; - } - - bool verify_file_readable(const char *path) - { - return access(path, R_OK) == 0; - } - - bool verify_root_or_system() - { - uid_t uid = getuid(); - gid_t gid = getgid(); - - return (uid == 0 && gid == 0) || (uid == AID_SYSTEM && gid == AID_SYSTEM); - } - - int maybe_create_fd(const char *target_apk_path, const char *overlay_apk_path, - const char *idmap_str) - { - // anyone (not just root or system) may do --fd -- the file has - // already been opened by someone else on our behalf - - char *endptr; - int idmap_fd = strtol(idmap_str, &endptr, 10); - if (*endptr != '\0') { - fprintf(stderr, "error: failed to parse file descriptor argument %s\n", idmap_str); - return -1; - } - - if (!verify_file_readable(target_apk_path)) { - ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); - return -1; - } - - if (!verify_file_readable(overlay_apk_path)) { - ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno)); - return -1; - } - - return idmap_create_fd(target_apk_path, overlay_apk_path, idmap_fd); - } - - int maybe_create_path(const char *target_apk_path, const char *overlay_apk_path, - const char *idmap_path) - { - if (!verify_root_or_system()) { - fprintf(stderr, "error: permission denied: not user root or user system\n"); - return -1; - } - - if (!verify_file_readable(target_apk_path)) { - ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); - return -1; - } - - if (!verify_file_readable(overlay_apk_path)) { - ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno)); - return -1; - } - - return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path); - } - - int maybe_verify_fd(const char *target_apk_path, const char *overlay_apk_path, - const char *idmap_str) - { - char *endptr; - int idmap_fd = strtol(idmap_str, &endptr, 10); - if (*endptr != '\0') { - fprintf(stderr, "error: failed to parse file descriptor argument %s\n", idmap_str); - return -1; - } - - if (!verify_file_readable(target_apk_path)) { - ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); - return -1; - } - - if (!verify_file_readable(overlay_apk_path)) { - ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno)); - return -1; - } - - return idmap_verify_fd(target_apk_path, overlay_apk_path, idmap_fd); - } - - int maybe_scan(const char *target_package_name, const char *target_apk_path, - const char *idmap_dir, const android::Vector<const char *> *overlay_dirs) - { - if (!verify_root_or_system()) { - fprintf(stderr, "error: permission denied: not user root or user system\n"); - return -1; - } - - if (!verify_file_readable(target_apk_path)) { - ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); - return -1; - } - - if (!verify_directory_writable(idmap_dir)) { - ALOGD("error: no write access to %s: %s\n", idmap_dir, strerror(errno)); - return -1; - } - - const size_t N = overlay_dirs->size(); - for (size_t i = 0; i < N; i++) { - const char *dir = overlay_dirs->itemAt(i); - if (!verify_directory_readable(dir)) { - ALOGD("error: no read access to %s: %s\n", dir, strerror(errno)); - return -1; - } - } - - return idmap_scan(target_package_name, target_apk_path, idmap_dir, overlay_dirs); - } - - int maybe_inspect(const char *idmap_path) - { - // anyone (not just root or system) may do --inspect - if (!verify_file_readable(idmap_path)) { - ALOGD("error: failed to read idmap %s: %s\n", idmap_path, strerror(errno)); - return -1; - } - return idmap_inspect(idmap_path); - } -} - -int main(int argc, char **argv) -{ -#if 0 - { - char buf[1024]; - buf[0] = '\0'; - for (int i = 0; i < argc; ++i) { - strncat(buf, argv[i], sizeof(buf) - 1); - strncat(buf, " ", sizeof(buf) - 1); - } - ALOGD("%s:%d: uid=%d gid=%d argv=%s\n", __FILE__, __LINE__, getuid(), getgid(), buf); - } -#endif - - if (argc == 2 && !strcmp(argv[1], "--help")) { - printf("%s\n", usage); - return 0; - } - - if (argc == 5 && !strcmp(argv[1], "--fd")) { - return maybe_create_fd(argv[2], argv[3], argv[4]); - } - - if (argc == 5 && !strcmp(argv[1], "--path")) { - return maybe_create_path(argv[2], argv[3], argv[4]); - } - - if (argc == 5 && !strcmp(argv[1], "--verify")) { - return maybe_verify_fd(argv[2], argv[3], argv[4]); - } - - if (argc >= 6 && !strcmp(argv[1], "--scan")) { - android::Vector<const char *> v; - for (int i = 5; i < argc; i++) { - v.push(argv[i]); - } - return maybe_scan(argv[2], argv[3], argv[4], &v); - } - - if (argc == 3 && !strcmp(argv[1], "--inspect")) { - return maybe_inspect(argv[2]); - } - - fprintf(stderr, "Usage: don't use this (cf dexopt usage).\n"); - return EXIT_FAILURE; -} diff --git a/cmds/idmap/idmap.h b/cmds/idmap/idmap.h deleted file mode 100644 index 5962108c9f7e..000000000000 --- a/cmds/idmap/idmap.h +++ /dev/null @@ -1,38 +0,0 @@ - -#ifndef _IDMAP_H_ -#define _IDMAP_H_ - -#define LOG_TAG "idmap" - -#include <utils/Log.h> -#include <utils/Vector.h> - -#include <errno.h> -#include <stdio.h> - -#ifndef TEMP_FAILURE_RETRY -// Used to retry syscalls that can return EINTR. -#define TEMP_FAILURE_RETRY(exp) ({ \ - typeof (exp) _rc; \ - do { \ - _rc = (exp); \ - } while (_rc == -1 && errno == EINTR); \ - _rc; }) -#endif - -int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path, - const char *idmap_path); - -int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd); - -int idmap_verify_fd(const char *target_apk_path, const char *overlay_apk_path, int fd); - -// Regarding target_package_name: the idmap_scan implementation should -// be able to extract this from the manifest in target_apk_path, -// simplifying the external API. -int idmap_scan(const char *target_package_name, const char *target_apk_path, - const char *idmap_dir, const android::Vector<const char *> *overlay_dirs); - -int idmap_inspect(const char *idmap_path); - -#endif // _IDMAP_H_ diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp deleted file mode 100644 index 20005e2766d8..000000000000 --- a/cmds/idmap/inspect.cpp +++ /dev/null @@ -1,313 +0,0 @@ -#include "idmap.h" - -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> -#include <utils/ByteOrder.h> -#include <utils/String8.h> - -#include <fcntl.h> -#include <sys/mman.h> -#include <sys/stat.h> - -using namespace android; - -namespace { - static const uint32_t IDMAP_MAGIC = 0x504D4449; - static const size_t PATH_LENGTH = 256; - - void printe(const char *fmt, ...); - - class IdmapBuffer { - private: - const char* buf_; - size_t len_; - size_t pos_; - public: - IdmapBuffer() : buf_((const char *)MAP_FAILED), len_(0), pos_(0) {} - - ~IdmapBuffer() { - if (buf_ != MAP_FAILED) { - munmap(const_cast<char*>(buf_), len_); - } - } - - status_t init(const char *idmap_path) { - struct stat st; - int fd; - - if (stat(idmap_path, &st) < 0) { - printe("failed to stat idmap '%s': %s\n", idmap_path, strerror(errno)); - return UNKNOWN_ERROR; - } - len_ = st.st_size; - if ((fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY))) < 0) { - printe("failed to open idmap '%s': %s\n", idmap_path, strerror(errno)); - return UNKNOWN_ERROR; - } - if ((buf_ = (const char*)mmap(NULL, len_, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { - close(fd); - printe("failed to mmap idmap: %s\n", strerror(errno)); - return UNKNOWN_ERROR; - } - close(fd); - return NO_ERROR; - } - - status_t nextUint32(uint32_t* i) { - if (!buf_) { - printe("failed to read next uint32_t: buffer not initialized\n"); - return UNKNOWN_ERROR; - } - - if (pos_ + sizeof(uint32_t) > len_) { - printe("failed to read next uint32_t: end of buffer reached at pos=0x%08x\n", - pos_); - return UNKNOWN_ERROR; - } - - if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x3) != 0) { - printe("failed to read next uint32_t: not aligned on 4-byte boundary\n"); - return UNKNOWN_ERROR; - } - - *i = dtohl(*reinterpret_cast<const uint32_t*>(buf_ + pos_)); - pos_ += sizeof(uint32_t); - return NO_ERROR; - } - - status_t nextUint16(uint16_t* i) { - if (!buf_) { - printe("failed to read next uint16_t: buffer not initialized\n"); - return UNKNOWN_ERROR; - } - - if (pos_ + sizeof(uint16_t) > len_) { - printe("failed to read next uint16_t: end of buffer reached at pos=0x%08x\n", - pos_); - return UNKNOWN_ERROR; - } - - if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x1) != 0) { - printe("failed to read next uint32_t: not aligned on 2-byte boundary\n"); - return UNKNOWN_ERROR; - } - - *i = dtohs(*reinterpret_cast<const uint16_t*>(buf_ + pos_)); - pos_ += sizeof(uint16_t); - return NO_ERROR; - } - - status_t nextPath(char *b) { - if (!buf_) { - printe("failed to read next path: buffer not initialized\n"); - return UNKNOWN_ERROR; - } - if (pos_ + PATH_LENGTH > len_) { - printe("failed to read next path: end of buffer reached at pos=0x%08x\n", pos_); - return UNKNOWN_ERROR; - } - memcpy(b, buf_ + pos_, PATH_LENGTH); - pos_ += PATH_LENGTH; - return NO_ERROR; - } - }; - - void printe(const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - fprintf(stderr, "error: "); - vfprintf(stderr, fmt, ap); - va_end(ap); - } - - void print_header() { - printf("SECTION ENTRY VALUE COMMENT\n"); - } - - void print(const char *section, const char *subsection, uint32_t value, const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - printf("%-12s %-12s 0x%08x ", section, subsection, value); - vprintf(fmt, ap); - printf("\n"); - va_end(ap); - } - - void print_path(const char *section, const char *subsection, const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - printf("%-12s %-12s .......... ", section, subsection); - vprintf(fmt, ap); - printf("\n"); - va_end(ap); - } - - status_t resource_metadata(const AssetManager& am, uint32_t res_id, - String8 *package, String8 *type, String8 *name) { - const ResTable& rt = am.getResources(); - struct ResTable::resource_name data; - if (!rt.getResourceName(res_id, false, &data)) { - printe("failed to get resource name id=0x%08x\n", res_id); - return UNKNOWN_ERROR; - } - if (package != NULL) { - *package = String8(String16(data.package, data.packageLen)); - } - if (type != NULL) { - *type = String8(String16(data.type, data.typeLen)); - } - if (name != NULL) { - *name = String8(String16(data.name, data.nameLen)); - } - return NO_ERROR; - } - - status_t parse_idmap_header(IdmapBuffer& buf, AssetManager& am) { - uint32_t i; - char path[PATH_LENGTH]; - - status_t err = buf.nextUint32(&i); - if (err != NO_ERROR) { - return err; - } - - if (i != IDMAP_MAGIC) { - printe("not an idmap file: actual magic constant 0x%08x does not match expected magic " - "constant 0x%08x\n", i, IDMAP_MAGIC); - return UNKNOWN_ERROR; - } - - print_header(); - print("IDMAP HEADER", "magic", i, ""); - - err = buf.nextUint32(&i); - if (err != NO_ERROR) { - return err; - } - print("", "version", i, ""); - - err = buf.nextUint32(&i); - if (err != NO_ERROR) { - return err; - } - print("", "base crc", i, ""); - - err = buf.nextUint32(&i); - if (err != NO_ERROR) { - return err; - } - print("", "overlay crc", i, ""); - - err = buf.nextPath(path); - if (err != NO_ERROR) { - // printe done from IdmapBuffer::nextPath - return err; - } - print_path("", "base path", "%s", path); - - if (!am.addAssetPath(String8(path), NULL)) { - printe("failed to add '%s' as asset path\n", path); - return UNKNOWN_ERROR; - } - - err = buf.nextPath(path); - if (err != NO_ERROR) { - // printe done from IdmapBuffer::nextPath - return err; - } - print_path("", "overlay path", "%s", path); - - return NO_ERROR; - } - - status_t parse_data(IdmapBuffer& buf, const AssetManager& am) { - const uint32_t packageId = am.getResources().getBasePackageId(0); - - uint16_t data16; - status_t err = buf.nextUint16(&data16); - if (err != NO_ERROR) { - return err; - } - print("DATA HEADER", "target pkg", static_cast<uint32_t>(data16), ""); - - err = buf.nextUint16(&data16); - if (err != NO_ERROR) { - return err; - } - print("", "types count", static_cast<uint32_t>(data16), ""); - - uint32_t typeCount = static_cast<uint32_t>(data16); - while (typeCount > 0) { - typeCount--; - - err = buf.nextUint16(&data16); - if (err != NO_ERROR) { - return err; - } - const uint32_t targetTypeId = static_cast<uint32_t>(data16); - print("DATA BLOCK", "target type", targetTypeId, ""); - - err = buf.nextUint16(&data16); - if (err != NO_ERROR) { - return err; - } - print("", "overlay type", static_cast<uint32_t>(data16), ""); - - err = buf.nextUint16(&data16); - if (err != NO_ERROR) { - return err; - } - const uint32_t entryCount = static_cast<uint32_t>(data16); - print("", "entry count", entryCount, ""); - - err = buf.nextUint16(&data16); - if (err != NO_ERROR) { - return err; - } - const uint32_t entryOffset = static_cast<uint32_t>(data16); - print("", "entry offset", entryOffset, ""); - - for (uint32_t i = 0; i < entryCount; i++) { - uint32_t data32; - err = buf.nextUint32(&data32); - if (err != NO_ERROR) { - return err; - } - - uint32_t resID = (packageId << 24) | (targetTypeId << 16) | (entryOffset + i); - String8 type; - String8 name; - err = resource_metadata(am, resID, NULL, &type, &name); - if (err != NO_ERROR) { - return err; - } - if (data32 != ResTable_type::NO_ENTRY) { - print("", "entry", data32, "%s/%s", type.string(), name.string()); - } - } - } - - return NO_ERROR; - } -} - -int idmap_inspect(const char *idmap_path) { - IdmapBuffer buf; - if (buf.init(idmap_path) < 0) { - // printe done from IdmapBuffer::init - return EXIT_FAILURE; - } - AssetManager am; - if (parse_idmap_header(buf, am) != NO_ERROR) { - // printe done from parse_idmap_header - return EXIT_FAILURE; - } - if (parse_data(buf, am) != NO_ERROR) { - // printe done from parse_data_header - return EXIT_FAILURE; - } - return EXIT_SUCCESS; -} diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp deleted file mode 100644 index 847dda3df91f..000000000000 --- a/cmds/idmap/scan.cpp +++ /dev/null @@ -1,281 +0,0 @@ -#include <dirent.h> -#include <inttypes.h> -#include <sys/file.h> -#include <sys/stat.h> - -#include "idmap.h" - -#include <memory> -#include <androidfw/ResourceTypes.h> -#include <androidfw/StreamingZipInflater.h> -#include <androidfw/ZipFileRO.h> -#include <cutils/properties.h> -#include <private/android_filesystem_config.h> // for AID_SYSTEM -#include <utils/SortedVector.h> -#include <utils/String16.h> -#include <utils/String8.h> - -#define NO_OVERLAY_TAG (-1000) - -using namespace android; - -namespace { - struct Overlay { - Overlay() {} - Overlay(const String8& a, const String8& i, int p) : - apk_path(a), idmap_path(i), priority(p) {} - - bool operator<(Overlay const& rhs) const - { - return rhs.priority > priority; - } - - String8 apk_path; - String8 idmap_path; - int priority; - }; - - bool writePackagesList(const char *filename, const SortedVector<Overlay>& overlayVector) - { - // the file is opened for appending so that it doesn't get truncated - // before we can guarantee mutual exclusion via the flock - FILE* fout = fopen(filename, "a"); - if (fout == NULL) { - return false; - } - - if (TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_EX)) != 0) { - fclose(fout); - return false; - } - - if (TEMP_FAILURE_RETRY(ftruncate(fileno(fout), 0)) != 0) { - TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_UN)); - fclose(fout); - return false; - } - - for (size_t i = 0; i < overlayVector.size(); ++i) { - const Overlay& overlay = overlayVector[i]; - fprintf(fout, "%s %s\n", overlay.apk_path.string(), overlay.idmap_path.string()); - } - - TEMP_FAILURE_RETRY(fflush(fout)); - TEMP_FAILURE_RETRY(flock(fileno(fout), LOCK_UN)); - fclose(fout); - - // Make file world readable since Zygote (running as root) will read - // it when creating the initial AssetManger object - const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // 0644 - if (chmod(filename, mode) == -1) { - unlink(filename); - return false; - } - - return true; - } - - String8 flatten_path(const char *path) - { - String16 tmp(path); - tmp.replaceAll('/', '@'); - return String8(tmp); - } - - bool check_property(String16 property, String16 value) { - char propBuf[PROPERTY_VALUE_MAX]; - property_get(String8(property).c_str(), propBuf, NULL); - return String8(value) == propBuf; - } - - int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name, - bool* is_static_overlay) - { - const size_t N = parser.getAttributeCount(); - String16 target; - int priority = -1; - String16 propName = String16(); - String16 propValue = String16(); - for (size_t i = 0; i < N; ++i) { - size_t len; - String16 key(parser.getAttributeName(i, &len)); - if (key == String16("targetPackage")) { - const char16_t *p = parser.getAttributeStringValue(i, &len); - if (p != NULL) { - target = String16(p, len); - } - } else if (key == String16("priority")) { - Res_value v; - if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) { - priority = v.data; - if (priority < 0 || priority > 9999) { - return -1; - } - } - } else if (key == String16("isStatic")) { - Res_value v; - if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) { - *is_static_overlay = (v.data != 0); - } - } else if (key == String16("requiredSystemPropertyName")) { - const char16_t *p = parser.getAttributeStringValue(i, &len); - if (p != NULL) { - propName = String16(p, len); - } - } else if (key == String16("requiredSystemPropertyValue")) { - const char16_t *p = parser.getAttributeStringValue(i, &len); - if (p != NULL) { - propValue = String16(p, len); - } - } - } - - // Note that conditional property enablement/exclusion only applies if - // the attribute is present. In its absence, all overlays are presumed enabled. - if (propName.size() > 0 && propValue.size() > 0) { - // if property set & equal to value, then include overlay - otherwise skip - if (!check_property(propName, propValue)) { - return NO_OVERLAY_TAG; - } - } - - if (target == String16(target_package_name)) { - return priority; - } - return NO_OVERLAY_TAG; - } - - int parse_manifest(const void *data, size_t size, const char *target_package_name) - { - ResXMLTree parser; - parser.setTo(data, size); - if (parser.getError() != NO_ERROR) { - ALOGD("%s failed to init xml parser, error=0x%08x\n", __FUNCTION__, parser.getError()); - return -1; - } - - ResXMLParser::event_code_t type; - bool is_static_overlay = false; - int priority = NO_OVERLAY_TAG; - do { - type = parser.next(); - if (type == ResXMLParser::START_TAG) { - size_t len; - String16 tag(parser.getElementName(&len)); - if (tag == String16("overlay")) { - priority = parse_overlay_tag(parser, target_package_name, &is_static_overlay); - break; - } - } - } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); - - if (is_static_overlay) { - return priority; - } - return NO_OVERLAY_TAG; - } - - int parse_apk(const char *path, const char *target_package_name) - { - std::unique_ptr<ZipFileRO> zip(ZipFileRO::open(path)); - if (zip.get() == NULL) { - ALOGW("%s: failed to open zip %s\n", __FUNCTION__, path); - return -1; - } - ZipEntryRO entry; - if ((entry = zip->findEntryByName("AndroidManifest.xml")) == NULL) { - ALOGW("%s: failed to find entry AndroidManifest.xml\n", __FUNCTION__); - return -1; - } - uint32_t uncompLen = 0; - uint16_t method; - if (!zip->getEntryInfo(entry, &method, &uncompLen, NULL, NULL, NULL, NULL)) { - ALOGW("%s: failed to read entry info\n", __FUNCTION__); - return -1; - } - if (method != ZipFileRO::kCompressDeflated) { - ALOGW("%s: cannot handle zip compression method %" PRIu16 "\n", __FUNCTION__, method); - return -1; - } - FileMap *dataMap = zip->createEntryFileMap(entry); - if (dataMap == NULL) { - ALOGW("%s: failed to create FileMap\n", __FUNCTION__); - return -1; - } - char *buf = new char[uncompLen]; - if (NULL == buf) { - ALOGW("%s: failed to allocate %" PRIu32 " byte\n", __FUNCTION__, uncompLen); - delete dataMap; - return -1; - } - StreamingZipInflater inflater(dataMap, uncompLen); - if (inflater.read(buf, uncompLen) < 0) { - ALOGW("%s: failed to inflate %" PRIu32 " byte\n", __FUNCTION__, uncompLen); - delete[] buf; - delete dataMap; - return -1; - } - - int priority = parse_manifest(buf, static_cast<size_t>(uncompLen), target_package_name); - delete[] buf; - delete dataMap; - return priority; - } -} - -int idmap_scan(const char *target_package_name, const char *target_apk_path, - const char *idmap_dir, const android::Vector<const char *> *overlay_dirs) -{ - String8 filename = String8(idmap_dir); - filename.appendPath("overlays.list"); - - SortedVector<Overlay> overlayVector; - const size_t N = overlay_dirs->size(); - for (size_t i = 0; i < N; ++i) { - const char *overlay_dir = overlay_dirs->itemAt(i); - DIR *dir = opendir(overlay_dir); - if (dir == NULL) { - return EXIT_FAILURE; - } - - struct dirent *dirent; - while ((dirent = readdir(dir)) != NULL) { - struct stat st; - char overlay_apk_path[PATH_MAX + 1]; - snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name); - if (stat(overlay_apk_path, &st) < 0) { - continue; - } - if (!S_ISREG(st.st_mode)) { - continue; - } - - int priority = parse_apk(overlay_apk_path, target_package_name); - if (priority < 0) { - continue; - } - - String8 idmap_path(idmap_dir); - idmap_path.appendPath(flatten_path(overlay_apk_path + 1)); - idmap_path.append("@idmap"); - - if (idmap_create_path(target_apk_path, overlay_apk_path, idmap_path.string()) != 0) { - ALOGE("error: failed to create idmap for target=%s overlay=%s idmap=%s\n", - target_apk_path, overlay_apk_path, idmap_path.string()); - continue; - } - - Overlay overlay(String8(overlay_apk_path), idmap_path, priority); - overlayVector.add(overlay); - } - - closedir(dir); - } - - if (!writePackagesList(filename.string(), overlayVector)) { - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} - diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index 4e57e884ee3e..878cef94b674 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -43,17 +43,7 @@ cc_library { ], host_supported: true, srcs: [ - "libidmap2/BinaryStreamVisitor.cpp", - "libidmap2/CommandLineOptions.cpp", - "libidmap2/FileUtils.cpp", - "libidmap2/Idmap.cpp", - "libidmap2/Policies.cpp", - "libidmap2/PrettyPrintVisitor.cpp", - "libidmap2/RawPrintVisitor.cpp", - "libidmap2/ResourceUtils.cpp", - "libidmap2/Result.cpp", - "libidmap2/Xml.cpp", - "libidmap2/ZipFile.cpp", + "libidmap2/**/*.cpp", ], export_include_dirs: ["include"], target: { @@ -67,6 +57,7 @@ cc_library { "libcutils", "libutils", "libziparchive", + "libidmap2_policies", ], }, host: { @@ -79,6 +70,37 @@ cc_library { "libcutils", "libutils", "libziparchive", + "libidmap2_policies", + ], + }, + }, +} + +cc_library { + name: "libidmap2_policies", + defaults: [ + "idmap2_defaults", + ], + host_supported: true, + export_include_dirs: ["libidmap2_policies/include"], + target: { + windows: { + enabled: true, + }, + android: { + static: { + enabled: false, + }, + shared_libs: [ + "libandroidfw", + ], + }, + host: { + shared: { + enabled: false, + }, + static_libs: [ + "libandroidfw", ], }, }, @@ -104,9 +126,10 @@ cc_test { "tests/PoliciesTests.cpp", "tests/PrettyPrintVisitorTests.cpp", "tests/RawPrintVisitorTests.cpp", + "tests/ResourceMappingTests.cpp", "tests/ResourceUtilsTests.cpp", "tests/ResultTests.cpp", - "tests/XmlTests.cpp", + "tests/XmlParserTests.cpp", "tests/ZipFileTests.cpp", ], required: [ @@ -123,6 +146,7 @@ cc_test { "libutils", "libz", "libziparchive", + "libidmap2_policies", ], }, host: { @@ -134,6 +158,7 @@ cc_test { "liblog", "libutils", "libziparchive", + "libidmap2_policies", ], shared_libs: [ "libz", @@ -150,12 +175,13 @@ cc_binary { ], host_supported: true, srcs: [ + "idmap2/CommandUtils.cpp", "idmap2/Create.cpp", + "idmap2/CreateMultiple.cpp", "idmap2/Dump.cpp", "idmap2/Lookup.cpp", "idmap2/Main.cpp", "idmap2/Scan.cpp", - "idmap2/Verify.cpp", ], target: { android: { @@ -166,6 +192,7 @@ cc_binary { "libidmap2", "libutils", "libziparchive", + "libidmap2_policies", ], }, host: { @@ -177,12 +204,14 @@ cc_binary { "liblog", "libutils", "libziparchive", + "libidmap2_policies", ], shared_libs: [ "libz", ], }, }, + } cc_binary { @@ -203,6 +232,7 @@ cc_binary { "libidmap2", "libutils", "libziparchive", + "libidmap2_policies", ], static_libs: [ "libidmap2daidl", @@ -235,3 +265,17 @@ filegroup { ], path: "idmap2d/aidl", } + +aidl_interface { + name: "overlayable_policy_aidl", + unstable: true, + srcs: [":overlayable_policy_aidl_files"], +} + +filegroup { + name: "overlayable_policy_aidl_files", + srcs: [ + "idmap2d/aidl/android/os/OverlayablePolicy.aidl", + ], + path: "idmap2d/aidl", +} diff --git a/cmds/idmap2/CPPLINT.cfg b/cmds/idmap2/CPPLINT.cfg index 9dc6b4a77380..20ed43c2a76a 100644 --- a/cmds/idmap2/CPPLINT.cfg +++ b/cmds/idmap2/CPPLINT.cfg @@ -15,4 +15,4 @@ set noparent linelength=100 root=.. -filter=+build/include_alpha +filter=+build/include_alpha,-runtime/references,-build/c++ diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING index 26ccf038cba2..9e0fb84c7949 100644 --- a/cmds/idmap2/TEST_MAPPING +++ b/cmds/idmap2/TEST_MAPPING @@ -3,5 +3,10 @@ { "name" : "idmap2_tests" } + ], + "imports": [ + { + "path": "frameworks/base/services/core/java/com/android/server/om" + } ] } diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp index 9cb67b33e6cf..8f5845bf2e53 100644 --- a/cmds/idmap2/idmap2/Verify.cpp +++ b/cmds/idmap2/idmap2/CommandUtils.cpp @@ -19,30 +19,19 @@ #include <string> #include <vector> -#include "idmap2/CommandLineOptions.h" #include "idmap2/Idmap.h" #include "idmap2/Result.h" #include "idmap2/SysTrace.h" -using android::idmap2::CommandLineOptions; using android::idmap2::Error; using android::idmap2::IdmapHeader; using android::idmap2::Result; using android::idmap2::Unit; -Result<Unit> Verify(const std::vector<std::string>& args) { - SYSTRACE << "Verify " << args; - std::string idmap_path; - - const CommandLineOptions opts = - CommandLineOptions("idmap2 verify") - .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path); - - const auto opts_ok = opts.Parse(args); - if (!opts_ok) { - return opts_ok.GetError(); - } - +Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path, + const std::string& overlay_path, PolicyBitmask fulfilled_policies, + bool enforce_overlayable) { + SYSTRACE << "Verify " << idmap_path; std::ifstream fin(idmap_path); const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); fin.close(); @@ -50,7 +39,8 @@ Result<Unit> Verify(const std::vector<std::string>& args) { return Error("failed to parse idmap header"); } - const auto header_ok = header->IsUpToDate(); + const auto header_ok = header->IsUpToDate(target_path.c_str(), overlay_path.c_str(), + fulfilled_policies, enforce_overlayable); if (!header_ok) { return Error(header_ok.GetError(), "idmap not up to date"); } diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h new file mode 100644 index 000000000000..e717e046d15d --- /dev/null +++ b/cmds/idmap2/idmap2/CommandUtils.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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 IDMAP2_IDMAP2_COMMAND_UTILS_H_ +#define IDMAP2_IDMAP2_COMMAND_UTILS_H_ + +#include "idmap2/PolicyUtils.h" +#include "idmap2/Result.h" + +android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path, + const std::string& target_path, + const std::string& overlay_path, + PolicyBitmask fulfilled_policies, + bool enforce_overlayable); + +#endif // IDMAP2_IDMAP2_COMMAND_UTILS_H_ diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h index 718e361b38ab..69eea8d262d2 100644 --- a/cmds/idmap2/idmap2/Commands.h +++ b/cmds/idmap2/idmap2/Commands.h @@ -23,9 +23,9 @@ #include "idmap2/Result.h" android::idmap2::Result<android::idmap2::Unit> Create(const std::vector<std::string>& args); +android::idmap2::Result<android::idmap2::Unit> CreateMultiple(const std::vector<std::string>& args); android::idmap2::Result<android::idmap2::Unit> Dump(const std::vector<std::string>& args); android::idmap2::Result<android::idmap2::Unit> Lookup(const std::vector<std::string>& args); android::idmap2::Result<android::idmap2::Unit> Scan(const std::vector<std::string>& args); -android::idmap2::Result<android::idmap2::Unit> Verify(const std::vector<std::string>& args); #endif // IDMAP2_IDMAP2_COMMANDS_H_ diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp index f482191b09a8..9682b6ead293 100644 --- a/cmds/idmap2/idmap2/Create.cpp +++ b/cmds/idmap2/idmap2/Create.cpp @@ -20,15 +20,14 @@ #include <fstream> #include <memory> #include <ostream> -#include <sstream> -#include <string> #include <vector> +#include "androidfw/ResourceTypes.h" #include "idmap2/BinaryStreamVisitor.h" #include "idmap2/CommandLineOptions.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" -#include "idmap2/Policies.h" +#include "idmap2/PolicyUtils.h" #include "idmap2/SysTrace.h" using android::ApkAssets; @@ -36,14 +35,15 @@ using android::idmap2::BinaryStreamVisitor; using android::idmap2::CommandLineOptions; using android::idmap2::Error; using android::idmap2::Idmap; -using android::idmap2::PoliciesToBitmask; -using android::idmap2::PolicyBitmask; -using android::idmap2::PolicyFlags; using android::idmap2::Result; using android::idmap2::Unit; using android::idmap2::utils::kIdmapFilePermissionMask; +using android::idmap2::utils::PoliciesToBitmaskResult; using android::idmap2::utils::UidHasWriteAccessToPath; +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + Result<Unit> Create(const std::vector<std::string>& args) { SYSTRACE << "Create " << args; std::string target_apk_path; @@ -78,7 +78,7 @@ Result<Unit> Create(const std::vector<std::string>& args) { } PolicyBitmask fulfilled_policies = 0; - auto conv_result = PoliciesToBitmask(policies); + auto conv_result = PoliciesToBitmaskResult(policies); if (conv_result) { fulfilled_policies |= *conv_result; } else { @@ -86,7 +86,7 @@ Result<Unit> Create(const std::vector<std::string>& args) { } if (fulfilled_policies == 0) { - fulfilled_policies |= PolicyFlags::POLICY_PUBLIC; + fulfilled_policies |= PolicyFlags::PUBLIC; } const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); @@ -99,8 +99,8 @@ Result<Unit> Create(const std::vector<std::string>& args) { return Error("failed to load apk %s", overlay_apk_path.c_str()); } - const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, - *overlay_apk, fulfilled_policies, !ignore_overlayable); + const auto idmap = + Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable); if (!idmap) { return Error(idmap.GetError(), "failed to create idmap"); } diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp new file mode 100644 index 000000000000..abdfaf4dccab --- /dev/null +++ b/cmds/idmap2/idmap2/CreateMultiple.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2020 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 <sys/stat.h> // umask +#include <sys/types.h> // umask + +#include <fstream> +#include <memory> +#include <ostream> +#include <vector> + +#include "Commands.h" +#include "android-base/stringprintf.h" +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/CommandLineOptions.h" +#include "idmap2/CommandUtils.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" +#include "idmap2/Policies.h" +#include "idmap2/PolicyUtils.h" +#include "idmap2/SysTrace.h" + +using android::ApkAssets; +using android::base::StringPrintf; +using android::idmap2::BinaryStreamVisitor; +using android::idmap2::CommandLineOptions; +using android::idmap2::Error; +using android::idmap2::Idmap; +using android::idmap2::Result; +using android::idmap2::Unit; +using android::idmap2::utils::kIdmapCacheDir; +using android::idmap2::utils::kIdmapFilePermissionMask; +using android::idmap2::utils::PoliciesToBitmaskResult; +using android::idmap2::utils::UidHasWriteAccessToPath; + +Result<Unit> CreateMultiple(const std::vector<std::string>& args) { + SYSTRACE << "CreateMultiple " << args; + std::string target_apk_path; + std::string idmap_dir = kIdmapCacheDir; + std::vector<std::string> overlay_apk_paths; + std::vector<std::string> policies; + bool ignore_overlayable = false; + + const CommandLineOptions opts = + CommandLineOptions("idmap2 create-multiple") + .MandatoryOption("--target-apk-path", + "input: path to apk which will have its resources overlaid", + &target_apk_path) + .MandatoryOption("--overlay-apk-path", + "input: path to apk which contains the new resource values", + &overlay_apk_paths) + .OptionalOption("--idmap-dir", + StringPrintf("output: path to the directory in which to write idmap file" + " (defaults to %s)", + kIdmapCacheDir), + &idmap_dir) + .OptionalOption("--policy", + "input: an overlayable policy this overlay fulfills" + " (if none or supplied, the overlay policy will default to \"public\")", + &policies) + .OptionalFlag("--ignore-overlayable", "disables overlayable and policy checks", + &ignore_overlayable); + const auto opts_ok = opts.Parse(args); + if (!opts_ok) { + return opts_ok.GetError(); + } + + PolicyBitmask fulfilled_policies = 0; + auto conv_result = PoliciesToBitmaskResult(policies); + if (conv_result) { + fulfilled_policies |= *conv_result; + } else { + return conv_result.GetError(); + } + + if (fulfilled_policies == 0) { + fulfilled_policies |= PolicyFlags::PUBLIC; + } + + const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + return Error("failed to load apk %s", target_apk_path.c_str()); + } + + std::vector<std::string> idmap_paths; + for (const std::string& overlay_apk_path : overlay_apk_paths) { + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(idmap_dir, overlay_apk_path); + const uid_t uid = getuid(); + if (!UidHasWriteAccessToPath(uid, idmap_path)) { + LOG(WARNING) << "uid " << uid << "does not have write access to " << idmap_path.c_str(); + continue; + } + + if (!Verify(idmap_path, target_apk_path, overlay_apk_path, fulfilled_policies, + !ignore_overlayable)) { + const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str(); + continue; + } + + const auto idmap = + Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable); + if (!idmap) { + LOG(WARNING) << "failed to create idmap"; + continue; + } + + umask(kIdmapFilePermissionMask); + std::ofstream fout(idmap_path); + if (fout.fail()) { + LOG(WARNING) << "failed to open idmap path " << idmap_path.c_str(); + continue; + } + + BinaryStreamVisitor visitor(fout); + (*idmap)->accept(&visitor); + fout.close(); + if (fout.fail()) { + LOG(WARNING) << "failed to write to idmap path %s" << idmap_path.c_str(); + continue; + } + } + + idmap_paths.emplace_back(idmap_path); + } + + for (const std::string& idmap_path : idmap_paths) { + std::cout << idmap_path << std::endl; + } + + return Unit{}; +} diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp index b7ae9d090cee..c44170928992 100644 --- a/cmds/idmap2/idmap2/Lookup.cpp +++ b/cmds/idmap2/idmap2/Lookup.cpp @@ -33,9 +33,10 @@ #include "androidfw/Util.h" #include "idmap2/CommandLineOptions.h" #include "idmap2/Idmap.h" +#include "idmap2/ResourceUtils.h" #include "idmap2/Result.h" #include "idmap2/SysTrace.h" -#include "idmap2/Xml.h" +#include "idmap2/XmlParser.h" #include "idmap2/ZipFile.h" #include "utils/String16.h" #include "utils/String8.h" @@ -57,8 +58,7 @@ using android::idmap2::IdmapHeader; using android::idmap2::ResourceId; using android::idmap2::Result; using android::idmap2::Unit; -using android::idmap2::Xml; -using android::idmap2::ZipFile; +using android::idmap2::utils::ExtractOverlayManifestInfo; using android::util::Utf16ToUtf8; namespace { @@ -85,76 +85,90 @@ Result<ResourceId> WARN_UNUSED ParseResReference(const AssetManager2& am, const return Error("failed to obtain resource id for %s", res.c_str()); } -Result<std::string> WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) { - Res_value value; - ResTable_config config; - uint32_t flags; - ApkAssetsCookie cookie = am.GetResource(resid, false, 0, &value, &config, &flags); - if (cookie == kInvalidCookie) { - return Error("no resource 0x%08x in asset manager", resid); - } - - std::string out; - - // TODO(martenkongstad): use optional parameter GetResource(..., std::string* - // stacktrace = NULL) instead - out.append(StringPrintf("cookie=%d ", cookie)); - - out.append("config='"); - out.append(config.toString().c_str()); - out.append("' value="); - +void PrintValue(AssetManager2* const am, const Res_value& value, const ApkAssetsCookie& cookie, + std::string* const out) { switch (value.dataType) { case Res_value::TYPE_INT_DEC: - out.append(StringPrintf("%d", value.data)); + out->append(StringPrintf("%d", value.data)); break; case Res_value::TYPE_INT_HEX: - out.append(StringPrintf("0x%08x", value.data)); + out->append(StringPrintf("0x%08x", value.data)); break; case Res_value::TYPE_INT_BOOLEAN: - out.append(value.data != 0 ? "true" : "false"); + out->append(value.data != 0 ? "true" : "false"); break; case Res_value::TYPE_STRING: { - const ResStringPool* pool = am.GetStringPoolForCookie(cookie); + const ResStringPool* pool = am->GetStringPoolForCookie(cookie); + out->append("\""); size_t len; if (pool->isUTF8()) { const char* str = pool->string8At(value.data, &len); - out.append(str, len); + out->append(str, len); } else { const char16_t* str16 = pool->stringAt(value.data, &len); - out += Utf16ToUtf8(StringPiece16(str16, len)); + out->append(Utf16ToUtf8(StringPiece16(str16, len))); } + out->append("\""); } break; default: - out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data)); + out->append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data)); break; } - return out; } -Result<std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) { - const auto zip = ZipFile::Open(apk_path); - if (!zip) { - return Error("failed to open %s as zip", apk_path.c_str()); - } - const auto entry = zip->Uncompress("AndroidManifest.xml"); - if (!entry) { - return Error("failed to uncompress AndroidManifest.xml in %s", apk_path.c_str()); - } - const auto xml = Xml::Create(entry->buf, entry->size); - if (!xml) { - return Error("failed to create XML buffer"); - } - const auto tag = xml->FindTag("overlay"); - if (!tag) { - return Error("failed to find <overlay> tag"); +Result<std::string> WARN_UNUSED GetValue(AssetManager2* const am, ResourceId resid) { + Res_value value; + ResTable_config config; + uint32_t flags; + ApkAssetsCookie cookie = am->GetResource(resid, true, 0, &value, &config, &flags); + if (cookie == kInvalidCookie) { + return Error("no resource 0x%08x in asset manager", resid); } - const auto iter = tag->find("targetPackage"); - if (iter == tag->end()) { - return Error("failed to find targetPackage attribute"); + + std::string out; + + // TODO(martenkongstad): use optional parameter GetResource(..., std::string* + // stacktrace = NULL) instead + out.append(StringPrintf("cookie=%d ", cookie)); + + out.append("config='"); + out.append(config.toString().c_str()); + out.append("' value="); + + if (value.dataType == Res_value::TYPE_REFERENCE) { + const android::ResolvedBag* bag = am->GetBag(static_cast<uint32_t>(value.data)); + if (bag == nullptr) { + out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data)); + return out; + } + out.append("["); + Res_value bag_val; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie bag_cookie; + for (size_t i = 0; i < bag->entry_count; ++i) { + const android::ResolvedBag::Entry& entry = bag->entries[i]; + bag_val = entry.value; + bag_cookie = am->ResolveReference(entry.cookie, &bag_val, &selected_config, &flags, &ref); + if (bag_cookie == kInvalidCookie) { + out.append( + StringPrintf("Error: dataType=0x%02x data=0x%08x", bag_val.dataType, bag_val.data)); + continue; + } + PrintValue(am, bag_val, bag_cookie, &out); + if (i != bag->entry_count - 1) { + out.append(", "); + } + } + out.append("]"); + } else { + PrintValue(am, value, cookie, &out); } - return iter->second; + + return out; } + } // namespace Result<Unit> Lookup(const std::vector<std::string>& args) { @@ -202,12 +216,12 @@ Result<Unit> Lookup(const std::vector<std::string>& args) { } apk_assets.push_back(std::move(target_apk)); - const Result<std::string> package_name = - GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string()); - if (!package_name) { - return Error("failed to parse android:targetPackage from overlay manifest"); + auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath().to_string(), + true /* assert_overlay */); + if (!manifest_info) { + return manifest_info.GetError(); } - target_package_name = *package_name; + target_package_name = (*manifest_info).target_package; } else if (target_path != idmap_header->GetTargetPath()) { return Error("different target APKs (expected target APK %s but %s has target APK %s)", target_path.c_str(), idmap_path.c_str(), @@ -235,7 +249,7 @@ Result<Unit> Lookup(const std::vector<std::string>& args) { return Error(resid.GetError(), "failed to parse resource ID"); } - const Result<std::string> value = GetValue(am, *resid); + const Result<std::string> value = GetValue(&am, *resid); if (!value) { return Error(value.GetError(), "resource 0x%08x not found", *resid); } diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp index 87949085cf1d..fb093f0f22a4 100644 --- a/cmds/idmap2/idmap2/Main.cpp +++ b/cmds/idmap2/idmap2/Main.cpp @@ -53,7 +53,8 @@ void PrintUsage(const NameToFunctionMap& commands, std::ostream& out) { int main(int argc, char** argv) { SYSTRACE << "main"; const NameToFunctionMap commands = { - {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify}, + {"create", Create}, {"create-multiple", CreateMultiple}, {"dump", Dump}, {"lookup", Lookup}, + {"scan", Scan}, }; if (argc <= 1) { PrintUsage(commands, std::cerr); diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp index da8c06e5f74f..36250450cc74 100644 --- a/cmds/idmap2/idmap2/Scan.cpp +++ b/cmds/idmap2/idmap2/Scan.cpp @@ -20,7 +20,6 @@ #include <memory> #include <ostream> #include <set> -#include <sstream> #include <string> #include <utility> #include <vector> @@ -28,30 +27,33 @@ #include "Commands.h" #include "android-base/properties.h" #include "idmap2/CommandLineOptions.h" +#include "idmap2/CommandUtils.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" +#include "idmap2/Policies.h" +#include "idmap2/PolicyUtils.h" #include "idmap2/ResourceUtils.h" #include "idmap2/Result.h" #include "idmap2/SysTrace.h" -#include "idmap2/Xml.h" -#include "idmap2/ZipFile.h" +#include "idmap2/XmlParser.h" using android::idmap2::CommandLineOptions; using android::idmap2::Error; using android::idmap2::Idmap; -using android::idmap2::kPolicyOdm; -using android::idmap2::kPolicyOem; -using android::idmap2::kPolicyProduct; -using android::idmap2::kPolicyPublic; -using android::idmap2::kPolicySystem; -using android::idmap2::kPolicyVendor; -using android::idmap2::PolicyBitmask; -using android::idmap2::PolicyFlags; using android::idmap2::Result; using android::idmap2::Unit; +using android::idmap2::policy::kPolicyOdm; +using android::idmap2::policy::kPolicyOem; +using android::idmap2::policy::kPolicyProduct; +using android::idmap2::policy::kPolicyPublic; +using android::idmap2::policy::kPolicySystem; +using android::idmap2::policy::kPolicyVendor; using android::idmap2::utils::ExtractOverlayManifestInfo; using android::idmap2::utils::FindFiles; using android::idmap2::utils::OverlayManifestInfo; +using android::idmap2::utils::PoliciesToBitmaskResult; + +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; namespace { @@ -98,6 +100,7 @@ Result<std::unique_ptr<std::vector<std::string>>> FindApkFiles(const std::vector } std::vector<std::string> PoliciesForPath(const std::string& apk_path) { + // clang-format off static const std::vector<std::pair<std::string, std::string>> values = { {"/odm/", kPolicyOdm}, {"/oem/", kPolicyOem}, @@ -106,6 +109,7 @@ std::vector<std::string> PoliciesForPath(const std::string& apk_path) { {"/system_ext/", kPolicySystem}, {"/vendor/", kPolicyVendor}, }; + // clang-format on std::vector<std::string> fulfilled_policies = {kPolicyPublic}; for (auto const& pair : values) { @@ -178,11 +182,11 @@ Result<Unit> Scan(const std::vector<std::string>& args) { // Note that conditional property enablement/exclusion only applies if // the attribute is present. In its absence, all overlays are presumed enabled. - if (!overlay_info->requiredSystemPropertyName.empty() - && !overlay_info->requiredSystemPropertyValue.empty()) { + if (!overlay_info->requiredSystemPropertyName.empty() && + !overlay_info->requiredSystemPropertyValue.empty()) { // if property set & equal to value, then include overlay - otherwise skip - if (android::base::GetProperty(overlay_info->requiredSystemPropertyName, "") - != overlay_info->requiredSystemPropertyValue) { + if (android::base::GetProperty(overlay_info->requiredSystemPropertyName, "") != + overlay_info->requiredSystemPropertyValue) { continue; } } @@ -215,7 +219,15 @@ Result<Unit> Scan(const std::vector<std::string>& args) { std::stringstream stream; for (const auto& overlay : interesting_apks) { - if (!Verify(std::vector<std::string>({"--idmap-path", overlay.idmap_path}))) { + const auto policy_bitmask = PoliciesToBitmaskResult(overlay.policies); + if (!policy_bitmask) { + LOG(WARNING) << "failed to create idmap for overlay apk path \"" << overlay.apk_path + << "\": " << policy_bitmask.GetErrorMessage(); + continue; + } + + if (!Verify(overlay.idmap_path, target_apk_path, overlay.apk_path, *policy_bitmask, + !overlay.ignore_overlayable)) { std::vector<std::string> create_args = {"--target-apk-path", target_apk_path, "--overlay-apk-path", overlay.apk_path, "--idmap-path", overlay.idmap_path}; diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index d7237763a2e5..15e22a3410cf 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -33,22 +33,29 @@ #include "idmap2/BinaryStreamVisitor.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" -#include "idmap2/Policies.h" +#include "idmap2/Result.h" #include "idmap2/SysTrace.h" +#include "idmap2/ZipFile.h" #include "utils/String8.h" using android::IPCThreadState; +using android::base::StringPrintf; using android::binder::Status; using android::idmap2::BinaryStreamVisitor; +using android::idmap2::GetPackageCrc; using android::idmap2::Idmap; using android::idmap2::IdmapHeader; -using android::idmap2::PolicyBitmask; +using android::idmap2::ZipFile; using android::idmap2::utils::kIdmapCacheDir; using android::idmap2::utils::kIdmapFilePermissionMask; using android::idmap2::utils::UidHasWriteAccessToPath; +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; + namespace { +constexpr const char* kFrameworkPath = "/system/framework/framework-res.apk"; + Status ok() { return Status::ok(); } @@ -62,6 +69,21 @@ PolicyBitmask ConvertAidlArgToPolicyBitmask(int32_t arg) { return static_cast<PolicyBitmask>(arg); } +Status GetCrc(const std::string& apk_path, uint32_t* out_crc) { + const auto zip = ZipFile::Open(apk_path); + if (!zip) { + return error(StringPrintf("failed to open apk %s", apk_path.c_str())); + } + + const auto crc = GetPackageCrc(*zip); + if (!crc) { + return error(crc.GetErrorMessage()); + } + + *out_crc = *crc; + return ok(); +} + } // namespace namespace android::os { @@ -93,21 +115,52 @@ Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path, return ok(); } -Status Idmap2Service::verifyIdmap(const std::string& overlay_apk_path, - int32_t fulfilled_policies ATTRIBUTE_UNUSED, - bool enforce_overlayable ATTRIBUTE_UNUSED, - int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) { +Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, + const std::string& overlay_apk_path, int32_t fulfilled_policies, + bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED, + bool* _aidl_return) { SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path; assert(_aidl_return); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); std::ifstream fin(idmap_path); const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); fin.close(); - *_aidl_return = header && header->IsUpToDate(); + if (!header) { + *_aidl_return = false; + return error("failed to parse idmap header"); + } - // TODO(b/119328308): Check that the set of fulfilled policies of the overlay has not changed + uint32_t target_crc; + if (target_apk_path == kFrameworkPath && android_crc_) { + target_crc = *android_crc_; + } else { + auto target_crc_status = GetCrc(target_apk_path, &target_crc); + if (!target_crc_status.isOk()) { + *_aidl_return = false; + return target_crc_status; + } + + // Loading the framework zip can take several milliseconds. Cache the crc of the framework + // resource APK to reduce repeated work during boot. + if (target_apk_path == kFrameworkPath) { + android_crc_ = target_crc; + } + } - return ok(); + uint32_t overlay_crc; + auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc); + if (!overlay_crc_status.isOk()) { + *_aidl_return = false; + return overlay_crc_status; + } + + auto up_to_date = + header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc, + ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable); + + *_aidl_return = static_cast<bool>(up_to_date); + return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage()); } Status Idmap2Service::createIdmap(const std::string& target_apk_path, @@ -137,21 +190,27 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, return error("failed to load apk " + overlay_apk_path); } - const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, - *overlay_apk, policy_bitmask, enforce_overlayable); + const auto idmap = + Idmap::FromApkAssets(*target_apk, *overlay_apk, policy_bitmask, enforce_overlayable); if (!idmap) { return error(idmap.GetErrorMessage()); } + // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees + // that existing memory maps will continue to be valid and unaffected. + unlink(idmap_path.c_str()); + umask(kIdmapFilePermissionMask); std::ofstream fout(idmap_path); if (fout.fail()) { return error("failed to open idmap path " + idmap_path); } + BinaryStreamVisitor visitor(fout); (*idmap)->accept(&visitor); fout.close(); if (fout.fail()) { + unlink(idmap_path.c_str()); return error("failed to write to idmap path " + idmap_path); } diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h index 73a236a961af..abee999dd2b2 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.h +++ b/cmds/idmap2/idmap2d/Idmap2Service.h @@ -20,9 +20,6 @@ #include <android-base/unique_fd.h> #include <binder/BinderService.h> -#include <optional> -#include <string> - #include "android/os/BnIdmap2.h" namespace android::os { @@ -34,18 +31,24 @@ class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 { } binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id, - std::string* _aidl_return); + std::string* _aidl_return) override; binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id, - bool* _aidl_return); + bool* _aidl_return) override; - binder::Status verifyIdmap(const std::string& overlay_apk_path, int32_t fulfilled_policies, - bool enforce_overlayable, int32_t user_id, bool* _aidl_return); + binder::Status verifyIdmap(const std::string& target_apk_path, + const std::string& overlay_apk_path, int32_t fulfilled_policies, + bool enforce_overlayable, int32_t user_id, + bool* _aidl_return) override; binder::Status createIdmap(const std::string& target_apk_path, const std::string& overlay_apk_path, int32_t fulfilled_policies, bool enforce_overlayable, int32_t user_id, - std::optional<std::string>* _aidl_return); + std::optional<std::string>* _aidl_return) override; + + private: + // Cache the crc of the android framework package since the crc cannot change without a reboot. + std::optional<uint32_t> android_crc_; }; } // namespace android::os diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl index cd474c0fe056..156f1d70e015 100644 --- a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl +++ b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl @@ -20,18 +20,13 @@ package android.os; * @hide */ interface IIdmap2 { - const int POLICY_PUBLIC = 0x00000001; - const int POLICY_SYSTEM_PARTITION = 0x00000002; - const int POLICY_VENDOR_PARTITION = 0x00000004; - const int POLICY_PRODUCT_PARTITION = 0x00000008; - const int POLICY_SIGNATURE = 0x00000010; - const int POLICY_ODM_PARTITION = 0x00000020; - const int POLICY_OEM_PARTITION = 0x00000040; - @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId); boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId); - boolean verifyIdmap(@utf8InCpp String overlayApkPath, int fulfilledPolicies, - boolean enforceOverlayable, int userId); + boolean verifyIdmap(@utf8InCpp String targetApkPath, + @utf8InCpp String overlayApkPath, + int fulfilledPolicies, + boolean enforceOverlayable, + int userId); @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, int fulfilledPolicies, diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.h b/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl index f650fccc447e..02b27a8800b6 100644 --- a/cmds/statsd/src/external/ResourceHealthManagerPuller.h +++ b/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 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. @@ -14,26 +14,19 @@ * limitations under the License. */ -#pragma once - -#include <utils/String16.h> -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { +package android.os; /** - * Reads Ihealth.hal + * @see ResourcesTypes.h ResTable_overlayable_policy_header::PolicyFlags + * @hide */ -class ResourceHealthManagerPuller : public StatsPuller { -public: - explicit ResourceHealthManagerPuller(int tagId); - -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; -}; - -} // namespace statsd -} // namespace os -} // namespace android +interface OverlayablePolicy { + const int PUBLIC = 0x00000001; + const int SYSTEM_PARTITION = 0x00000002; + const int VENDOR_PARTITION = 0x00000004; + const int PRODUCT_PARTITION = 0x00000008; + const int SIGNATURE = 0x00000010; + const int ODM_PARTITION = 0x00000020; + const int OEM_PARTITION = 0x00000040; + const int ACTOR_SIGNATURE = 0x00000080; +} diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h index 2c3e9d321881..ff45b1407dea 100644 --- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h +++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h @@ -29,16 +29,19 @@ class BinaryStreamVisitor : public Visitor { public: explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) { } - virtual void visit(const Idmap& idmap); - virtual void visit(const IdmapHeader& header); - virtual void visit(const IdmapData& data); - virtual void visit(const IdmapData::Header& header); - virtual void visit(const IdmapData::TypeEntry& type_entry); + ~BinaryStreamVisitor() override = default; + void visit(const Idmap& idmap) override; + void visit(const IdmapHeader& header) override; + void visit(const IdmapData& data) override; + void visit(const IdmapData::Header& header) override; private: + void Write(const void* value, size_t length); + void Write8(uint8_t value); void Write16(uint16_t value); void Write32(uint32_t value); - void WriteString(const StringPiece& value); + void WriteString256(const StringPiece& value); + void WriteString(const std::string& value); std::ostream& stream_; }; diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h index ebbb5ffc989d..0f05592b70f3 100644 --- a/cmds/idmap2/include/idmap2/Idmap.h +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -18,19 +18,21 @@ * # idmap file format (current version) * * idmap := header data* - * header := magic version target_crc overlay_crc target_path overlay_path + * header := magic version target_crc overlay_crc target_path overlay_path debug_info * data := data_header data_block* * data_header := target_package_id types_count * data_block := target_type overlay_type entry_count entry_offset entry* - * overlay_path := string - * target_path := string + * overlay_path := string256 + * target_path := string256 + * debug_info := string + * string := <uint32_t> <uint8_t>+ '\0'+ * entry := <uint32_t> * entry_count := <uint16_t> * entry_offset := <uint16_t> * magic := <uint32_t> * overlay_crc := <uint32_t> * overlay_type := <uint16_t> - * string := <uint8_t>[256] + * string256 := <uint8_t>[256] * target_crc := <uint32_t> * target_package_id := <uint16_t> * target_type := <uint16_t> @@ -41,6 +43,22 @@ * # idmap file format changelog * ## v1 * - Identical to idmap v1. + * + * ## v2 + * - Entries are no longer separated by type into type specific data blocks. + * - Added overlay-indexed target resource id lookup capabilities. + * - Target and overlay entries are stored as a sparse array in the data block. The target entries + * array maps from target resource id to overlay data type and value and the array is sorted by + * target resource id. The overlay entries array maps from overlay resource id to target resource + * id and the array is sorted by overlay resource id. It is important for both arrays to be sorted + * to allow for O(log(number_of_overlaid_resources)) performance when looking up resource + * mappings at runtime. + * - Idmap can now encode a type and value to override a resource without needing a table entry. + * - A string pool block is included to retrieve the value of strings that do not have a resource + * table entry. + * + * ## v3 + * - Add 'debug' block to IdmapHeader. */ #ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ @@ -55,21 +73,15 @@ #include "androidfw/ApkAssets.h" #include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" -#include "idmap2/Policies.h" +#include "idmap2/ResourceMapping.h" +#include "idmap2/ZipFile.h" namespace android::idmap2 { class Idmap; class Visitor; -// use typedefs to let the compiler warn us about implicit casts -typedef uint32_t ResourceId; // 0xpptteeee -typedef uint8_t PackageId; // pp in 0xpptteeee -typedef uint8_t TypeId; // tt in 0xpptteeee -typedef uint16_t EntryId; // eeee in 0xpptteeee - static constexpr const ResourceId kPadding = 0xffffffffu; - static constexpr const EntryId kNoEntry = 0xffffu; // magic number: all idmap files start with this @@ -82,6 +94,9 @@ static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVer // terminating null) static constexpr const size_t kIdmapStringLength = 256; +// Retrieves a crc generated using all of the files within the zip that can affect idmap generation. +Result<uint32_t> GetPackageCrc(const ZipFile& zip_info); + class IdmapHeader { public: static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream); @@ -102,6 +117,14 @@ class IdmapHeader { return overlay_crc_; } + inline uint32_t GetFulfilledPolicies() const { + return fulfilled_policies_; + } + + bool GetEnforceOverlayable() const { + return enforce_overlayable_; + } + inline StringPiece GetTargetPath() const { return StringPiece(target_path_); } @@ -110,10 +133,18 @@ class IdmapHeader { return StringPiece(overlay_path_); } + inline const std::string& GetDebugInfo() const { + return debug_info_; + } + // Invariant: anytime the idmap data encoding is changed, the idmap version // field *must* be incremented. Because of this, we know that if the idmap // header is up-to-date the entire file is up-to-date. - Result<Unit> IsUpToDate() const; + Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, + PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; + Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc, + uint32_t overlay_crc, PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const; void accept(Visitor* v) const; @@ -125,13 +156,15 @@ class IdmapHeader { uint32_t version_; uint32_t target_crc_; uint32_t overlay_crc_; + uint32_t fulfilled_policies_; + bool enforce_overlayable_; char target_path_[kIdmapStringLength]; char overlay_path_[kIdmapStringLength]; + std::string debug_info_; friend Idmap; DISALLOW_COPY_AND_ASSIGN(IdmapHeader); }; - class IdmapData { public: class Header { @@ -142,70 +175,72 @@ class IdmapData { return target_package_id_; } - inline uint16_t GetTypeCount() const { - return type_count_; - } - - void accept(Visitor* v) const; - - private: - Header() { - } - - PackageId target_package_id_; - uint16_t type_count_; - - friend Idmap; - DISALLOW_COPY_AND_ASSIGN(Header); - }; - - class TypeEntry { - public: - static std::unique_ptr<const TypeEntry> FromBinaryStream(std::istream& stream); - - inline TypeId GetTargetTypeId() const { - return target_type_id_; + inline PackageId GetOverlayPackageId() const { + return overlay_package_id_; } - inline TypeId GetOverlayTypeId() const { - return overlay_type_id_; + inline uint32_t GetTargetEntryCount() const { + return target_entry_count; } - inline uint16_t GetEntryCount() const { - return entries_.size(); + inline uint32_t GetOverlayEntryCount() const { + return overlay_entry_count; } - inline uint16_t GetEntryOffset() const { - return entry_offset_; + inline uint32_t GetStringPoolIndexOffset() const { + return string_pool_index_offset; } - inline EntryId GetEntry(size_t i) const { - return i < entries_.size() ? entries_[i] : 0xffffu; + inline uint32_t GetStringPoolLength() const { + return string_pool_len; } void accept(Visitor* v) const; private: - TypeEntry() { - } - - TypeId target_type_id_; - TypeId overlay_type_id_; - uint16_t entry_offset_; - std::vector<EntryId> entries_; + PackageId target_package_id_; + PackageId overlay_package_id_; + uint32_t target_entry_count; + uint32_t overlay_entry_count; + uint32_t string_pool_index_offset; + uint32_t string_pool_len; + Header() = default; friend Idmap; - DISALLOW_COPY_AND_ASSIGN(TypeEntry); + friend IdmapData; + DISALLOW_COPY_AND_ASSIGN(Header); + }; + + struct TargetEntry { + ResourceId target_id; + TargetValue::DataType data_type; + TargetValue::DataValue data_value; + }; + + struct OverlayEntry { + ResourceId overlay_id; + ResourceId target_id; }; static std::unique_ptr<const IdmapData> FromBinaryStream(std::istream& stream); + static Result<std::unique_ptr<const IdmapData>> FromResourceMapping( + const ResourceMapping& resource_mapping); + inline const std::unique_ptr<const Header>& GetHeader() const { return header_; } - inline const std::vector<std::unique_ptr<const TypeEntry>>& GetTypeEntries() const { - return type_entries_; + inline const std::vector<TargetEntry>& GetTargetEntries() const { + return target_entries_; + } + + inline const std::vector<OverlayEntry>& GetOverlayEntries() const { + return overlay_entries_; + } + + inline const void* GetStringPoolData() const { + return string_pool_.get(); } void accept(Visitor* v) const; @@ -215,7 +250,9 @@ class IdmapData { } std::unique_ptr<const Header> header_; - std::vector<std::unique_ptr<const TypeEntry>> type_entries_; + std::vector<TargetEntry> target_entries_; + std::vector<OverlayEntry> overlay_entries_; + std::unique_ptr<uint8_t[]> string_pool_; friend Idmap; DISALLOW_COPY_AND_ASSIGN(IdmapData); @@ -232,9 +269,7 @@ class Idmap { // file is used; change this in the next version of idmap to use a named // package instead; also update FromApkAssets to take additional parameters: // the target and overlay package names - static Result<std::unique_ptr<const Idmap>> FromApkAssets(const std::string& target_apk_path, - const ApkAssets& target_apk_assets, - const std::string& overlay_apk_path, + static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets, const ApkAssets& overlay_apk_assets, const PolicyBitmask& fulfilled_policies, bool enforce_overlayable); @@ -267,7 +302,6 @@ class Visitor { virtual void visit(const IdmapHeader& header) = 0; virtual void visit(const IdmapData& data) = 0; virtual void visit(const IdmapData::Header& header) = 0; - virtual void visit(const IdmapData::TypeEntry& type_entry) = 0; }; } // namespace android::idmap2 diff --git a/cmds/idmap2/include/idmap2/LogInfo.h b/cmds/idmap2/include/idmap2/LogInfo.h new file mode 100644 index 000000000000..a6237e6f6ba9 --- /dev/null +++ b/cmds/idmap2/include/idmap2/LogInfo.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_ +#define IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_ + +#include <algorithm> +#include <iterator> +#include <sstream> +#include <string> +#include <vector> + +#if __ANDROID__ +#include "android-base/logging.h" +#else +#include <iostream> +#endif + +namespace android::idmap2 { + +class LogMessage { + public: + LogMessage() = default; + + template <typename T> + LogMessage& operator<<(const T& value) { + stream_ << value; + return *this; + } + + std::string GetString() const { + return stream_.str(); + } + + private: + std::stringstream stream_; +}; + +class LogInfo { + public: + LogInfo() = default; + + inline void Info(const LogMessage& msg) { + lines_.push_back("I " + msg.GetString()); + } + + inline void Warning(const LogMessage& msg) { +#ifdef __ANDROID__ + LOG(WARNING) << msg.GetString(); +#else + std::cerr << "W " << msg.GetString() << std::endl; +#endif + lines_.push_back("W " + msg.GetString()); + } + + inline std::string GetString() const { + std::ostringstream stream; + std::copy(lines_.begin(), lines_.end(), std::ostream_iterator<std::string>(stream, "\n")); + return stream.str(); + } + + private: + std::vector<std::string> lines_; +}; + +} // namespace android::idmap2 + +#endif // IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_ diff --git a/cmds/idmap2/include/idmap2/Policies.h b/cmds/idmap2/include/idmap2/Policies.h deleted file mode 100644 index 87edd3506d33..000000000000 --- a/cmds/idmap2/include/idmap2/Policies.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2019 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 <string> -#include <vector> - -#include "Result.h" -#include "androidfw/ResourceTypes.h" -#include "androidfw/StringPiece.h" - -#ifndef IDMAP2_INCLUDE_IDMAP2_POLICIES_H_ -#define IDMAP2_INCLUDE_IDMAP2_POLICIES_H_ - -namespace android::idmap2 { - -constexpr const char* kPolicyOdm = "odm"; -constexpr const char* kPolicyOem = "oem"; -constexpr const char* kPolicyProduct = "product"; -constexpr const char* kPolicyPublic = "public"; -constexpr const char* kPolicySignature = "signature"; -constexpr const char* kPolicySystem = "system"; -constexpr const char* kPolicyVendor = "vendor"; - -using PolicyFlags = ResTable_overlayable_policy_header::PolicyFlags; -using PolicyBitmask = uint32_t; - -// Parses the string representations of policies into a bitmask. -Result<PolicyBitmask> PoliciesToBitmask(const std::vector<std::string>& policies); - -// Retrieves the string representations of policies in the bitmask. -std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask); - -} // namespace android::idmap2 - -#endif // IDMAP2_INCLUDE_IDMAP2_POLICIES_H_ diff --git a/cmds/idmap2/include/idmap2/PolicyUtils.h b/cmds/idmap2/include/idmap2/PolicyUtils.h new file mode 100644 index 000000000000..b95b8b420658 --- /dev/null +++ b/cmds/idmap2/include/idmap2/PolicyUtils.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 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 IDMAP2_INCLUDE_IDMAP2_POLICYUTILS_H_ +#define IDMAP2_INCLUDE_IDMAP2_POLICYUTILS_H_ + +#include <string> +#include <vector> + +#include "androidfw/ResourceTypes.h" +#include "idmap2/Policies.h" +#include "idmap2/Result.h" + +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; + +namespace android::idmap2::utils { + +// Returns a Result object containing a policy flag bitmask built from a list of policy strings. +// On error will contain a human readable message listing the invalid policies. +Result<PolicyBitmask> PoliciesToBitmaskResult(const std::vector<std::string>& policies); + +// Converts a bitmask of policy flags into a list of their string representation as would be written +// into XML +std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask); + +} // namespace android::idmap2::utils + +#endif // IDMAP2_INCLUDE_IDMAP2_POLICYUTILS_H_ diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h index 5111bb2eaab2..5dcf217e2aa3 100644 --- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h +++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h @@ -33,17 +33,16 @@ class PrettyPrintVisitor : public Visitor { public: explicit PrettyPrintVisitor(std::ostream& stream) : stream_(stream) { } - virtual void visit(const Idmap& idmap); - virtual void visit(const IdmapHeader& header); - virtual void visit(const IdmapData& data); - virtual void visit(const IdmapData::Header& header); - virtual void visit(const IdmapData::TypeEntry& type_entry); + ~PrettyPrintVisitor() override = default; + void visit(const Idmap& idmap) override; + void visit(const IdmapHeader& header) override; + void visit(const IdmapData& data) override; + void visit(const IdmapData::Header& header) override; private: std::ostream& stream_; std::unique_ptr<const ApkAssets> target_apk_; AssetManager2 target_am_; - PackageId last_seen_package_id_; }; } // namespace idmap2 diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h index 2e543d4fabdd..92c186453611 100644 --- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h +++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h @@ -34,22 +34,25 @@ class RawPrintVisitor : public Visitor { public: explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) { } - virtual void visit(const Idmap& idmap); - virtual void visit(const IdmapHeader& header); - virtual void visit(const IdmapData& data); - virtual void visit(const IdmapData::Header& header); - virtual void visit(const IdmapData::TypeEntry& type_entry); + ~RawPrintVisitor() override = default; + void visit(const Idmap& idmap) override; + void visit(const IdmapHeader& header) override; + void visit(const IdmapData& data) override; + void visit(const IdmapData::Header& header) override; private: + void print(uint8_t value, const char* fmt, ...); void print(uint16_t value, const char* fmt, ...); void print(uint32_t value, const char* fmt, ...); - void print(const std::string& value, const char* fmt, ...); + void print(const std::string& value, size_t encoded_size, const char* fmt, ...); + void print_raw(uint32_t length, const char* fmt, ...); std::ostream& stream_; std::unique_ptr<const ApkAssets> target_apk_; + std::unique_ptr<const ApkAssets> overlay_apk_; AssetManager2 target_am_; + AssetManager2 overlay_am_; size_t offset_; - PackageId last_seen_package_id_; }; } // namespace idmap2 diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h new file mode 100644 index 000000000000..5869409e7db9 --- /dev/null +++ b/cmds/idmap2/include/idmap2/ResourceMapping.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_ +#define IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_ + +#include <map> +#include <memory> +#include <utility> + +#include "androidfw/ApkAssets.h" +#include "idmap2/LogInfo.h" +#include "idmap2/Policies.h" +#include "idmap2/ResourceUtils.h" +#include "idmap2/Result.h" +#include "idmap2/XmlParser.h" + +using android::idmap2::utils::OverlayManifestInfo; + +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; + +namespace android::idmap2 { + +struct TargetValue { + typedef uint8_t DataType; + typedef uint32_t DataValue; + DataType data_type; + DataValue data_value; +}; + +using TargetResourceMap = std::map<ResourceId, TargetValue>; +using OverlayResourceMap = std::map<ResourceId, ResourceId>; + +class ResourceMapping { + public: + // Creates a ResourceMapping using the target and overlay APKs. Setting enforce_overlayable to + // `false` disables all overlayable and policy enforcement: this is intended for backwards + // compatibility pre-Q and unit tests. + static Result<ResourceMapping> FromApkAssets(const ApkAssets& target_apk_assets, + const ApkAssets& overlay_apk_assets, + const OverlayManifestInfo& overlay_info, + const PolicyBitmask& fulfilled_policies, + bool enforce_overlayable, LogInfo& log_info); + + // Retrieves the mapping of target resource id to overlay value. + inline TargetResourceMap GetTargetToOverlayMap() const { + return target_map_; + } + + // Retrieves the mapping of overlay resource id to target resource id. This allows a reference to + // an overlay resource to appear as a reference to its corresponding target resource at runtime. + OverlayResourceMap GetOverlayToTargetMap() const; + + // Retrieves the build-time package id of the target package. + inline uint32_t GetTargetPackageId() const { + return target_package_id_; + } + + // Retrieves the build-time package id of the overlay package. + inline uint32_t GetOverlayPackageId() const { + return overlay_package_id_; + } + + // Retrieves the offset that was added to the index of inline string overlay values so the indices + // do not collide with the indices of the overlay resource table string pool. + inline uint32_t GetStringPoolOffset() const { + return string_pool_offset_; + } + + // Retrieves the raw string pool data from the xml referenced in android:resourcesMap. + inline const std::pair<const uint8_t*, uint32_t> GetStringPoolData() const { + return std::make_pair(string_pool_data_.get(), string_pool_data_length_); + } + + private: + ResourceMapping() = default; + + // Apps a mapping of target resource id to the type and value of the data that overlays the + // target resource. The data_type is the runtime format of the data value (see + // Res_value::dataType). If rewrite_overlay_reference is `true` then references to an overlay + // resource should appear as a reference to its corresponding target resource at runtime. + Result<Unit> AddMapping(ResourceId target_resource, TargetValue::DataType data_type, + TargetValue::DataValue data_value, bool rewrite_overlay_reference); + + // Removes the overlay value mapping for the target resource. + void RemoveMapping(ResourceId target_resource); + + // Parses the mapping of target resources to overlay resources to generate a ResourceMapping. + static Result<ResourceMapping> CreateResourceMapping(const AssetManager2* target_am, + const LoadedPackage* target_package, + const LoadedPackage* overlay_package, + size_t string_pool_offset, + const XmlParser& overlay_parser, + LogInfo& log_info); + + // Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay + // a target resource, a resource must exist in the overlay with the same type and entry name as + // the target resource. + static Result<ResourceMapping> CreateResourceMappingLegacy(const AssetManager2* target_am, + const AssetManager2* overlay_am, + const LoadedPackage* target_package, + const LoadedPackage* overlay_package); + + // Removes resources that do not pass policy or overlayable checks of the target package. + void FilterOverlayableResources(const AssetManager2* target_am, + const LoadedPackage* target_package, + const LoadedPackage* overlay_package, + const OverlayManifestInfo& overlay_info, + const PolicyBitmask& fulfilled_policies, LogInfo& log_info); + + TargetResourceMap target_map_; + std::multimap<ResourceId, ResourceId> overlay_map_; + + uint32_t target_package_id_ = 0; + uint32_t overlay_package_id_ = 0; + uint32_t string_pool_offset_ = 0; + uint32_t string_pool_data_length_ = 0; + std::unique_ptr<uint8_t[]> string_pool_data_ = nullptr; +}; + +} // namespace android::idmap2 + +#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_ diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index 9a0c2abced5a..c643b0e8800c 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -21,19 +21,36 @@ #include <string> #include "androidfw/AssetManager2.h" -#include "idmap2/Idmap.h" #include "idmap2/Result.h" #include "idmap2/ZipFile.h" -namespace android::idmap2::utils { +namespace android::idmap2 { + +// use typedefs to let the compiler warn us about implicit casts +typedef uint32_t ResourceId; // 0xpptteeee +typedef uint8_t PackageId; // pp in 0xpptteeee +typedef uint8_t TypeId; // tt in 0xpptteeee +typedef uint16_t EntryId; // eeee in 0xpptteeee + +#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16) +#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid)) + +namespace utils { + +// Returns whether the Res_value::data_type represents a dynamic or regular resource reference. +bool IsReference(uint8_t data_type); + +// Converts the Res_value::data_type to a human-readable string representation. +StringPiece DataTypeToString(uint8_t data_type); struct OverlayManifestInfo { - std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes) - std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes) - std::string requiredSystemPropertyName; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes) + std::string requiredSystemPropertyName; // NOLINT(misc-non-private-member-variables-in-classes) std::string requiredSystemPropertyValue; // NOLINT(misc-non-private-member-variables-in-classes) - bool is_static; // NOLINT(misc-non-private-member-variables-in-classes) - int priority = -1; // NOLINT(misc-non-private-member-variables-in-classes) + uint32_t resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes) + bool is_static; // NOLINT(misc-non-private-member-variables-in-classes) + int priority = -1; // NOLINT(misc-non-private-member-variables-in-classes) }; Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, @@ -41,6 +58,8 @@ Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid); -} // namespace android::idmap2::utils +} // namespace utils + +} // namespace android::idmap2 #endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_ diff --git a/cmds/idmap2/include/idmap2/Xml.h b/cmds/idmap2/include/idmap2/Xml.h deleted file mode 100644 index dd89dee0f64f..000000000000 --- a/cmds/idmap2/include/idmap2/Xml.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IDMAP2_INCLUDE_IDMAP2_XML_H_ -#define IDMAP2_INCLUDE_IDMAP2_XML_H_ - -#include <map> -#include <memory> -#include <string> - -#include "android-base/macros.h" -#include "androidfw/ResourceTypes.h" -#include "utils/String16.h" - -namespace android::idmap2 { - -class Xml { - public: - static std::unique_ptr<const Xml> Create(const uint8_t* data, size_t size, bool copyData = false); - - std::unique_ptr<std::map<std::string, std::string>> FindTag(const std::string& name) const; - - ~Xml(); - - private: - Xml() { - } - - mutable ResXMLTree xml_; - - DISALLOW_COPY_AND_ASSIGN(Xml); -}; - -} // namespace android::idmap2 - -#endif // IDMAP2_INCLUDE_IDMAP2_XML_H_ diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h new file mode 100644 index 000000000000..972a6d7e3427 --- /dev/null +++ b/cmds/idmap2/include/idmap2/XmlParser.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_ +#define IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_ + +#include <iostream> +#include <map> +#include <memory> +#include <string> + +#include "Result.h" +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" +#include "utils/String16.h" + +namespace android::idmap2 { + +class XmlParser { + public: + using Event = ResXMLParser::event_code_t; + class iterator; + + class Node { + public: + Event event() const; + std::string name() const; + + Result<std::string> GetAttributeStringValue(const std::string& name) const; + Result<Res_value> GetAttributeValue(const std::string& name) const; + + bool operator==(const Node& rhs) const; + bool operator!=(const Node& rhs) const; + + private: + explicit Node(const ResXMLTree& tree); + Node(const ResXMLTree& tree, const ResXMLParser::ResXMLPosition& pos); + + // Retrieves/Sets the position of the position of the xml parser in the xml tree. + ResXMLParser::ResXMLPosition get_position() const; + void set_position(const ResXMLParser::ResXMLPosition& pos); + + // If `inner_child` is true, seek advances the parser to the first inner child of the current + // node. Otherwise, seek advances the parser to the following node. Returns false if there is + // no node to seek to. + bool Seek(bool inner_child); + + ResXMLParser parser_; + friend iterator; + }; + + class iterator { + public: + iterator(const iterator& other) : iterator(other.tree_, other.iter_) { + } + + inline iterator& operator=(const iterator& rhs) { + iter_.set_position(rhs.iter_.get_position()); + return *this; + } + + inline bool operator==(const iterator& rhs) const { + return iter_ == rhs.iter_; + } + + inline bool operator!=(const iterator& rhs) const { + return !(*this == rhs); + } + + inline iterator operator++() { + // Seek to the following xml node. + iter_.Seek(false /* inner_child */); + return *this; + } + + iterator begin() const { + iterator child_it(*this); + // Seek to the first inner child of the current node. + child_it.iter_.Seek(true /* inner_child */); + return child_it; + } + + iterator end() const { + iterator child_it = begin(); + while (child_it.iter_.Seek(false /* inner_child */)) { + // Continue iterating until the end tag is found. + } + + return child_it; + } + + inline const Node operator*() { + return Node(tree_, iter_.get_position()); + } + + inline const Node* operator->() { + return &iter_; + } + + private: + explicit iterator(const ResXMLTree& tree) : tree_(tree), iter_(Node(tree)) { + } + iterator(const ResXMLTree& tree, const Node& node) + : tree_(tree), iter_(Node(tree, node.get_position())) { + } + + const ResXMLTree& tree_; + Node iter_; + friend XmlParser; + }; + + // Creates a new xml parser beginning at the first tag. + static Result<std::unique_ptr<const XmlParser>> Create(const void* data, size_t size, + bool copy_data = false); + ~XmlParser(); + + inline iterator tree_iterator() const { + return iterator(tree_); + } + + inline const ResStringPool& get_strings() const { + return tree_.getStrings(); + } + + private: + XmlParser() = default; + mutable ResXMLTree tree_; + + DISALLOW_COPY_AND_ASSIGN(XmlParser); +}; + +} // namespace android::idmap2 + +#endif // IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_ diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp index dee2d219cbe1..255212ad4c66 100644 --- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -24,6 +24,14 @@ namespace android::idmap2 { +void BinaryStreamVisitor::Write(const void* value, size_t length) { + stream_.write(reinterpret_cast<const char*>(value), length); +} + +void BinaryStreamVisitor::Write8(uint8_t value) { + stream_.write(reinterpret_cast<char*>(&value), sizeof(uint8_t)); +} + void BinaryStreamVisitor::Write16(uint16_t value) { uint16_t x = htodl(value); stream_.write(reinterpret_cast<char*>(&x), sizeof(uint16_t)); @@ -34,13 +42,21 @@ void BinaryStreamVisitor::Write32(uint32_t value) { stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t)); } -void BinaryStreamVisitor::WriteString(const StringPiece& value) { +void BinaryStreamVisitor::WriteString256(const StringPiece& value) { char buf[kIdmapStringLength]; memset(buf, 0, sizeof(buf)); memcpy(buf, value.data(), std::min(value.size(), sizeof(buf))); stream_.write(buf, sizeof(buf)); } +void BinaryStreamVisitor::WriteString(const std::string& value) { + // pad with null to nearest word boundary; include at least one terminating null + size_t padding_size = 4 - (value.size() % 4); + Write32(value.size() + padding_size); + stream_.write(value.c_str(), value.size()); + stream_.write("\0\0\0\0", padding_size); +} + void BinaryStreamVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { // nothing to do } @@ -50,30 +66,35 @@ void BinaryStreamVisitor::visit(const IdmapHeader& header) { Write32(header.GetVersion()); Write32(header.GetTargetCrc()); Write32(header.GetOverlayCrc()); - WriteString(header.GetTargetPath()); - WriteString(header.GetOverlayPath()); + Write32(header.GetFulfilledPolicies()); + Write8(static_cast<uint8_t>(header.GetEnforceOverlayable())); + WriteString256(header.GetTargetPath()); + WriteString256(header.GetOverlayPath()); + WriteString(header.GetDebugInfo()); } -void BinaryStreamVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { - // nothing to do -} +void BinaryStreamVisitor::visit(const IdmapData& data) { + for (const auto& target_entry : data.GetTargetEntries()) { + Write32(target_entry.target_id); + Write8(target_entry.data_type); + Write32(target_entry.data_value); + } -void BinaryStreamVisitor::visit(const IdmapData::Header& header) { - Write16(header.GetTargetPackageId()); - Write16(header.GetTypeCount()); -} + for (const auto& overlay_entry : data.GetOverlayEntries()) { + Write32(overlay_entry.overlay_id); + Write32(overlay_entry.target_id); + } -void BinaryStreamVisitor::visit(const IdmapData::TypeEntry& type_entry) { - const uint16_t entryCount = type_entry.GetEntryCount(); + Write(data.GetStringPoolData(), data.GetHeader()->GetStringPoolLength()); +} - Write16(type_entry.GetTargetTypeId()); - Write16(type_entry.GetOverlayTypeId()); - Write16(entryCount); - Write16(type_entry.GetEntryOffset()); - for (uint16_t i = 0; i < entryCount; i++) { - EntryId entry_id = type_entry.GetEntry(i); - Write32(entry_id != kNoEntry ? static_cast<uint32_t>(entry_id) : kPadding); - } +void BinaryStreamVisitor::visit(const IdmapData::Header& header) { + Write8(header.GetTargetPackageId()); + Write8(header.GetOverlayPackageId()); + Write32(header.GetTargetEntryCount()); + Write32(header.GetOverlayEntryCount()); + Write32(header.GetStringPoolIndexOffset()); + Write32(header.GetStringPoolLength()); } } // namespace android::idmap2 diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 4649675965db..23c25a7089de 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -30,6 +30,7 @@ #include "android-base/macros.h" #include "android-base/stringprintf.h" #include "androidfw/AssetManager2.h" +#include "idmap2/ResourceMapping.h" #include "idmap2/ResourceUtils.h" #include "idmap2/Result.h" #include "idmap2/SysTrace.h" @@ -41,34 +42,10 @@ namespace android::idmap2 { namespace { -#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16) - -#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid)) - -class MatchingResources { - public: - void Add(ResourceId target_resid, ResourceId overlay_resid) { - TypeId target_typeid = EXTRACT_TYPE(target_resid); - if (map_.find(target_typeid) == map_.end()) { - map_.emplace(target_typeid, std::set<std::pair<ResourceId, ResourceId>>()); - } - map_[target_typeid].insert(std::make_pair(target_resid, overlay_resid)); - } - - inline const std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>>& WARN_UNUSED - Map() const { - return map_; - } - - private: - // target type id -> set { pair { overlay entry id, overlay entry id } } - std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>> map_; -}; - -bool WARN_UNUSED Read16(std::istream& stream, uint16_t* out) { - uint16_t value; - if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint16_t))) { - *out = dtohl(value); +bool WARN_UNUSED Read8(std::istream& stream, uint8_t* out) { + uint8_t value; + if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint8_t))) { + *out = value; return true; } return false; @@ -83,8 +60,17 @@ bool WARN_UNUSED Read32(std::istream& stream, uint32_t* out) { return false; } +bool WARN_UNUSED ReadBuffer(std::istream& stream, std::unique_ptr<uint8_t[]>* out, size_t length) { + auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[length]); + if (stream.read(reinterpret_cast<char*>(buffer.get()), length)) { + *out = std::move(buffer); + return true; + } + return false; +} + // a string is encoded as a kIdmapStringLength char array; the array is always null-terminated -bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) { +bool WARN_UNUSED ReadString256(std::istream& stream, char out[kIdmapStringLength]) { char buf[kIdmapStringLength]; memset(buf, 0, sizeof(buf)); if (!stream.read(buf, sizeof(buf))) { @@ -97,29 +83,26 @@ bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) return true; } -ResourceId NameToResid(const AssetManager2& am, const std::string& name) { - return am.GetResourceId(name); -} - -// TODO(martenkongstad): scan for package name instead of assuming package at index 0 -// -// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package -// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so -// this assumption tends to work out. That said, the correct thing to do is to scan -// resources.arsc for a package with a given name as read from the package manifest instead of -// relying on a hard-coded index. This however requires storing the package name in the idmap -// header, which in turn requires incrementing the idmap version. Because the initial version of -// idmap2 is compatible with idmap, this will have to wait for now. -const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) { - const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages(); - if (packages.empty()) { - return nullptr; +Result<std::string> ReadString(std::istream& stream) { + uint32_t size; + if (!Read32(stream, &size)) { + return Error("failed to read string size"); + } + if (size == 0) { + return std::string(""); } - int id = packages[0]->GetPackageId(); - return loaded_arsc.GetPackageById(id); + std::string buf(size, '\0'); + if (!stream.read(buf.data(), size)) { + return Error("failed to read string of size %u", size); + } + // buf is guaranteed to be null terminated (with enough nulls to end on a word boundary) + buf.resize(strlen(buf.c_str())); + return buf; } -Result<uint32_t> GetCrc(const ZipFile& zip) { +} // namespace + +Result<uint32_t> GetPackageCrc(const ZipFile& zip) { const Result<uint32_t> a = zip.Crc("resources.arsc"); const Result<uint32_t> b = zip.Crc("AndroidManifest.xml"); return a && b @@ -127,22 +110,59 @@ Result<uint32_t> GetCrc(const ZipFile& zip) { : Error("failed to get CRC for \"%s\"", a ? "AndroidManifest.xml" : "resources.arsc"); } -} // namespace - std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) { std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader()); - + uint8_t enforce_overlayable; if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) || !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) || - !ReadString(stream, idmap_header->target_path_) || - !ReadString(stream, idmap_header->overlay_path_)) { + !Read32(stream, &idmap_header->fulfilled_policies_) || !Read8(stream, &enforce_overlayable) || + !ReadString256(stream, idmap_header->target_path_) || + !ReadString256(stream, idmap_header->overlay_path_)) { return nullptr; } + idmap_header->enforce_overlayable_ = static_cast<bool>(enforce_overlayable); + + auto debug_str = ReadString(stream); + if (!debug_str) { + return nullptr; + } + idmap_header->debug_info_ = std::move(*debug_str); + return std::move(idmap_header); } -Result<Unit> IdmapHeader::IsUpToDate() const { +Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { + const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path); + if (!target_zip) { + return Error("failed to open target %s", target_path); + } + + const Result<uint32_t> target_crc = GetPackageCrc(*target_zip); + if (!target_crc) { + return Error("failed to get target crc"); + } + + const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path); + if (!overlay_zip) { + return Error("failed to overlay target %s", overlay_path); + } + + const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip); + if (!overlay_crc) { + return Error("failed to get overlay crc"); + } + + return IsUpToDate(target_path, overlay_path, *target_crc, *overlay_crc, fulfilled_policies, + enforce_overlayable); +} + +Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, + uint32_t target_crc, uint32_t overlay_crc, + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { if (magic_ != kIdmapMagic) { return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic); } @@ -151,34 +171,34 @@ Result<Unit> IdmapHeader::IsUpToDate() const { return Error("bad version: actual 0x%08x, expected 0x%08x", version_, kIdmapCurrentVersion); } - const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_); - if (!target_zip) { - return Error("failed to open target %s", GetTargetPath().to_string().c_str()); + if (target_crc_ != target_crc) { + return Error("bad target crc: idmap version 0x%08x, file system version 0x%08x", target_crc_, + target_crc); } - Result<uint32_t> target_crc = GetCrc(*target_zip); - if (!target_crc) { - return Error("failed to get target crc"); + if (overlay_crc_ != overlay_crc) { + return Error("bad overlay crc: idmap version 0x%08x, file system version 0x%08x", overlay_crc_, + overlay_crc); } - if (target_crc_ != *target_crc) { - return Error("bad target crc: idmap version 0x%08x, file system version 0x%08x", target_crc_, - *target_crc); + if (fulfilled_policies_ != fulfilled_policies) { + return Error("bad fulfilled policies: idmap version 0x%08x, file system version 0x%08x", + fulfilled_policies, fulfilled_policies_); } - const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_); - if (!overlay_zip) { - return Error("failed to open overlay %s", GetOverlayPath().to_string().c_str()); + if (enforce_overlayable != enforce_overlayable_) { + return Error("bad enforce overlayable: idmap version %s, file system version %s", + enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false"); } - Result<uint32_t> overlay_crc = GetCrc(*overlay_zip); - if (!overlay_crc) { - return Error("failed to get overlay crc"); + if (strcmp(target_path, target_path_) != 0) { + return Error("bad target path: idmap version %s, file system version %s", target_path, + target_path_); } - if (overlay_crc_ != *overlay_crc) { - return Error("bad overlay crc: idmap version 0x%08x, file system version 0x%08x", overlay_crc_, - *overlay_crc); + if (strcmp(overlay_path, overlay_path_) != 0) { + return Error("bad overlay path: idmap version %s, file system version %s", overlay_path, + overlay_path_); } return Unit{}; @@ -187,51 +207,48 @@ Result<Unit> IdmapHeader::IsUpToDate() const { std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) { std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header()); - uint16_t target_package_id16; - if (!Read16(stream, &target_package_id16) || !Read16(stream, &idmap_data_header->type_count_)) { + if (!Read8(stream, &idmap_data_header->target_package_id_) || + !Read8(stream, &idmap_data_header->overlay_package_id_) || + !Read32(stream, &idmap_data_header->target_entry_count) || + !Read32(stream, &idmap_data_header->overlay_entry_count) || + !Read32(stream, &idmap_data_header->string_pool_index_offset) || + !Read32(stream, &idmap_data_header->string_pool_len)) { return nullptr; } - idmap_data_header->target_package_id_ = target_package_id16; return std::move(idmap_data_header); } -std::unique_ptr<const IdmapData::TypeEntry> IdmapData::TypeEntry::FromBinaryStream( - std::istream& stream) { - std::unique_ptr<IdmapData::TypeEntry> data(new IdmapData::TypeEntry()); - uint16_t target_type16; - uint16_t overlay_type16; - uint16_t entry_count; - if (!Read16(stream, &target_type16) || !Read16(stream, &overlay_type16) || - !Read16(stream, &entry_count) || !Read16(stream, &data->entry_offset_)) { - return nullptr; - } - data->target_type_id_ = target_type16; - data->overlay_type_id_ = overlay_type16; - for (uint16_t i = 0; i < entry_count; i++) { - ResourceId resid; - if (!Read32(stream, &resid)) { - return nullptr; - } - data->entries_.push_back(resid); - } - - return std::move(data); -} - std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& stream) { std::unique_ptr<IdmapData> data(new IdmapData()); data->header_ = IdmapData::Header::FromBinaryStream(stream); if (!data->header_) { return nullptr; } - for (size_t type_count = 0; type_count < data->header_->GetTypeCount(); type_count++) { - std::unique_ptr<const TypeEntry> type = IdmapData::TypeEntry::FromBinaryStream(stream); - if (!type) { + // Read the mapping of target resource id to overlay resource value. + for (size_t i = 0; i < data->header_->GetTargetEntryCount(); i++) { + TargetEntry target_entry{}; + if (!Read32(stream, &target_entry.target_id) || !Read8(stream, &target_entry.data_type) || + !Read32(stream, &target_entry.data_value)) { return nullptr; } - data->type_entries_.push_back(std::move(type)); + data->target_entries_.emplace_back(target_entry); } + + // Read the mapping of overlay resource id to target resource id. + for (size_t i = 0; i < data->header_->GetOverlayEntryCount(); i++) { + OverlayEntry overlay_entry{}; + if (!Read32(stream, &overlay_entry.overlay_id) || !Read32(stream, &overlay_entry.target_id)) { + return nullptr; + } + data->overlay_entries_.emplace_back(overlay_entry); + } + + // Read raw string pool bytes. + if (!ReadBuffer(stream, &data->string_pool_, data->header_->string_pool_len)) { + return nullptr; + } + return std::move(data); } @@ -266,95 +283,45 @@ Result<std::unique_ptr<const Idmap>> Idmap::FromBinaryStream(std::istream& strea return {std::move(idmap)}; } -std::string ConcatPolicies(const std::vector<std::string>& policies) { - std::string message; - for (const std::string& policy : policies) { - if (!message.empty()) { - message.append("|"); - } - message.append(policy); +Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping( + const ResourceMapping& resource_mapping) { + if (resource_mapping.GetTargetToOverlayMap().empty()) { + return Error("no resources were overlaid"); } - return message; -} - -Result<Unit> CheckOverlayable(const LoadedPackage& target_package, - const utils::OverlayManifestInfo& overlay_info, - const PolicyBitmask& fulfilled_policies, const ResourceId& resid) { - static constexpr const PolicyBitmask sDefaultPolicies = - PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION | - PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION | - PolicyFlags::POLICY_PRODUCT_PARTITION | PolicyFlags::POLICY_SIGNATURE; - - // If the resource does not have an overlayable definition, allow the resource to be overlaid if - // the overlay is preinstalled or signed with the same signature as the target. - if (!target_package.DefinesOverlayable()) { - return (sDefaultPolicies & fulfilled_policies) != 0 - ? Result<Unit>({}) - : Error( - "overlay must be preinstalled or signed with the same signature as the " - "target"); + std::unique_ptr<IdmapData> data(new IdmapData()); + for (const auto& mappings : resource_mapping.GetTargetToOverlayMap()) { + data->target_entries_.emplace_back(IdmapData::TargetEntry{ + mappings.first, mappings.second.data_type, mappings.second.data_value}); } - const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(resid); - if (overlayable_info == nullptr) { - // Do not allow non-overlayable resources to be overlaid. - return Error("resource has no overlayable declaration"); + for (const auto& mappings : resource_mapping.GetOverlayToTargetMap()) { + data->overlay_entries_.emplace_back(IdmapData::OverlayEntry{mappings.first, mappings.second}); } - if (overlay_info.target_name != overlayable_info->name) { - // If the overlay supplies a target overlayable name, the resource must belong to the - // overlayable defined with the specified name to be overlaid. - return Error("<overlay> android:targetName '%s' does not match overlayable name '%s'", - overlay_info.target_name.c_str(), overlayable_info->name.c_str()); - } + std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header()); + data_header->target_package_id_ = resource_mapping.GetTargetPackageId(); + data_header->overlay_package_id_ = resource_mapping.GetOverlayPackageId(); + data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size()); + data_header->overlay_entry_count = static_cast<uint32_t>(data->overlay_entries_.size()); + data_header->string_pool_index_offset = resource_mapping.GetStringPoolOffset(); - // Enforce policy restrictions if the resource is declared as overlayable. - if ((overlayable_info->policy_flags & fulfilled_policies) == 0) { - return Error("overlay with policies '%s' does not fulfill any overlayable policies '%s'", - ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(), - ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str()); - } + const auto string_pool_data = resource_mapping.GetStringPoolData(); + data_header->string_pool_len = string_pool_data.second; + data->string_pool_ = std::unique_ptr<uint8_t[]>(new uint8_t[data_header->string_pool_len]); + memcpy(data->string_pool_.get(), string_pool_data.first, data_header->string_pool_len); - return Result<Unit>({}); + data->header_ = std::move(data_header); + return {std::move(data)}; } -Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const std::string& target_apk_path, - const ApkAssets& target_apk_assets, - const std::string& overlay_apk_path, +Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets, const ApkAssets& overlay_apk_assets, const PolicyBitmask& fulfilled_policies, bool enforce_overlayable) { SYSTRACE << "Idmap::FromApkAssets"; - AssetManager2 target_asset_manager; - if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) { - return Error("failed to create target asset manager"); - } - - AssetManager2 overlay_asset_manager; - if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) { - return Error("failed to create overlay asset manager"); - } - - const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc(); - if (target_arsc == nullptr) { - return Error("failed to load target resources.arsc"); - } - - const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc(); - if (overlay_arsc == nullptr) { - return Error("failed to load overlay resources.arsc"); - } - - const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc); - if (target_pkg == nullptr) { - return Error("failed to load target package from resources.arsc"); - } - - const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc); - if (overlay_pkg == nullptr) { - return Error("failed to load overlay package from resources.arsc"); - } + const std::string& target_apk_path = target_apk_assets.GetPath(); + const std::string& overlay_apk_path = overlay_apk_assets.GetPath(); const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path); if (!target_zip) { @@ -366,27 +333,25 @@ Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const std::string& tar return Error("failed to open overlay as zip"); } - auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path); - if (!overlay_info) { - return overlay_info.GetError(); - } - std::unique_ptr<IdmapHeader> header(new IdmapHeader()); header->magic_ = kIdmapMagic; header->version_ = kIdmapCurrentVersion; - Result<uint32_t> crc = GetCrc(*target_zip); + Result<uint32_t> crc = GetPackageCrc(*target_zip); if (!crc) { return Error(crc.GetError(), "failed to get zip CRC for target"); } header->target_crc_ = *crc; - crc = GetCrc(*overlay_zip); + crc = GetPackageCrc(*overlay_zip); if (!crc) { return Error(crc.GetError(), "failed to get zip CRC for overlay"); } header->overlay_crc_ = *crc; + header->fulfilled_policies_ = fulfilled_policies; + header->enforce_overlayable_ = enforce_overlayable; + if (target_apk_path.size() > sizeof(header->target_path_)) { return Error("target apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(), sizeof(header->target_path_)); @@ -395,78 +360,34 @@ Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const std::string& tar memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size()); if (overlay_apk_path.size() > sizeof(header->overlay_path_)) { - return Error("overlay apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(), + return Error("overlay apk path \"%s\" longer than maximum size %zu", overlay_apk_path.c_str(), sizeof(header->target_path_)); } memset(header->overlay_path_, 0, sizeof(header->overlay_path_)); memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size()); - std::unique_ptr<Idmap> idmap(new Idmap()); - idmap->header_ = std::move(header); - - // find the resources that exist in both packages - MatchingResources matching_resources; - const auto end = overlay_pkg->end(); - for (auto iter = overlay_pkg->begin(); iter != end; ++iter) { - const ResourceId overlay_resid = *iter; - Result<std::string> name = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid); - if (!name) { - continue; - } - // prepend "<package>:" to turn name into "<package>:<type>/<name>" - const std::string full_name = - base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name->c_str()); - const ResourceId target_resid = NameToResid(target_asset_manager, full_name); - if (target_resid == 0) { - continue; - } - - if (enforce_overlayable) { - Result<Unit> success = - CheckOverlayable(*target_pkg, *overlay_info, fulfilled_policies, target_resid); - if (!success) { - LOG(WARNING) << "overlay \"" << overlay_apk_path - << "\" is not allowed to overlay resource \"" << full_name - << "\": " << success.GetErrorMessage(); - continue; - } - } - - matching_resources.Add(target_resid, overlay_resid); + auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path); + if (!overlay_info) { + return overlay_info.GetError(); } - if (matching_resources.Map().empty()) { - return Error("overlay \"%s\" does not successfully overlay any resource", - overlay_apk_path.c_str()); + LogInfo log_info; + auto resource_mapping = + ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *overlay_info, + fulfilled_policies, enforce_overlayable, log_info); + if (!resource_mapping) { + return resource_mapping.GetError(); } - // encode idmap data - std::unique_ptr<IdmapData> data(new IdmapData()); - const auto types_end = matching_resources.Map().cend(); - for (auto ti = matching_resources.Map().cbegin(); ti != types_end; ++ti) { - auto ei = ti->second.cbegin(); - std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry()); - type->target_type_id_ = EXTRACT_TYPE(ei->first); - type->overlay_type_id_ = EXTRACT_TYPE(ei->second); - type->entry_offset_ = EXTRACT_ENTRY(ei->first); - EntryId last_target_entry = kNoEntry; - for (; ei != ti->second.cend(); ++ei) { - if (last_target_entry != kNoEntry) { - int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1; - type->entries_.insert(type->entries_.end(), count, kNoEntry); - } - type->entries_.push_back(EXTRACT_ENTRY(ei->second)); - last_target_entry = EXTRACT_ENTRY(ei->first); - } - data->type_entries_.push_back(std::move(type)); + auto idmap_data = IdmapData::FromResourceMapping(*resource_mapping); + if (!idmap_data) { + return idmap_data.GetError(); } - std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header()); - data_header->target_package_id_ = target_pkg->GetPackageId(); - data_header->type_count_ = data->type_entries_.size(); - data->header_ = std::move(data_header); - - idmap->data_.push_back(std::move(data)); + std::unique_ptr<Idmap> idmap(new Idmap()); + header->debug_info_ = log_info.GetString(); + idmap->header_ = std::move(header); + idmap->data_.push_back(std::move(*idmap_data)); return {std::move(idmap)}; } @@ -481,25 +402,16 @@ void IdmapData::Header::accept(Visitor* v) const { v->visit(*this); } -void IdmapData::TypeEntry::accept(Visitor* v) const { - assert(v != nullptr); - v->visit(*this); -} - void IdmapData::accept(Visitor* v) const { assert(v != nullptr); - v->visit(*this); header_->accept(v); - auto end = type_entries_.cend(); - for (auto iter = type_entries_.cbegin(); iter != end; ++iter) { - (*iter)->accept(v); - } + v->visit(*this); } void Idmap::accept(Visitor* v) const { assert(v != nullptr); - v->visit(*this); header_->accept(v); + v->visit(*this); auto end = data_.cend(); for (auto iter = data_.cbegin(); iter != end; ++iter) { (*iter)->accept(v); diff --git a/cmds/idmap2/libidmap2/Policies.cpp b/cmds/idmap2/libidmap2/Policies.cpp deleted file mode 100644 index 495fe615a91f..000000000000 --- a/cmds/idmap2/libidmap2/Policies.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2019 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 "idmap2/Policies.h" - -#include <iterator> -#include <string> -#include <unordered_map> -#include <vector> - -#include "androidfw/ResourceTypes.h" -#include "idmap2/Idmap.h" -#include "idmap2/Result.h" - -namespace android::idmap2 { - -Result<PolicyBitmask> PoliciesToBitmask(const std::vector<std::string>& policies) { - static const std::unordered_map<android::StringPiece, PolicyFlags> kStringToFlag = { - {kPolicyOdm, PolicyFlags::POLICY_ODM_PARTITION}, - {kPolicyOem, PolicyFlags::POLICY_OEM_PARTITION}, - {kPolicyPublic, PolicyFlags::POLICY_PUBLIC}, - {kPolicyProduct, PolicyFlags::POLICY_PRODUCT_PARTITION}, - {kPolicySignature, PolicyFlags::POLICY_SIGNATURE}, - {kPolicySystem, PolicyFlags::POLICY_SYSTEM_PARTITION}, - {kPolicyVendor, PolicyFlags::POLICY_VENDOR_PARTITION}, - }; - - PolicyBitmask bitmask = 0; - for (const std::string& policy : policies) { - const auto iter = kStringToFlag.find(policy); - if (iter != kStringToFlag.end()) { - bitmask |= iter->second; - } else { - return Error("unknown policy \"%s\"", policy.c_str()); - } - } - - return Result<PolicyBitmask>(bitmask); -} - -std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask) { - std::vector<std::string> policies; - - if ((bitmask & PolicyFlags::POLICY_ODM_PARTITION) != 0) { - policies.emplace_back(kPolicyOdm); - } - - if ((bitmask & PolicyFlags::POLICY_OEM_PARTITION) != 0) { - policies.emplace_back(kPolicyOem); - } - - if ((bitmask & PolicyFlags::POLICY_PUBLIC) != 0) { - policies.emplace_back(kPolicyPublic); - } - - if ((bitmask & PolicyFlags::POLICY_PRODUCT_PARTITION) != 0) { - policies.emplace_back(kPolicyProduct); - } - - if ((bitmask & PolicyFlags::POLICY_SIGNATURE) != 0) { - policies.emplace_back(kPolicySignature); - } - - if ((bitmask & PolicyFlags::POLICY_SYSTEM_PARTITION) != 0) { - policies.emplace_back(kPolicySystem); - } - - if ((bitmask & PolicyFlags::POLICY_VENDOR_PARTITION) != 0) { - policies.emplace_back(kPolicyVendor); - } - - return policies; -} - -} // namespace android::idmap2 diff --git a/cmds/idmap2/libidmap2/PolicyUtils.cpp b/cmds/idmap2/libidmap2/PolicyUtils.cpp new file mode 100644 index 000000000000..fc5182af61c1 --- /dev/null +++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "include/idmap2/PolicyUtils.h" + +#include <sstream> + +#include "android-base/strings.h" +#include "idmap2/Policies.h" + +using android::idmap2::policy::kPolicyStringToFlag; + +namespace android::idmap2::utils { + +Result<PolicyBitmask> PoliciesToBitmaskResult(const std::vector<std::string>& policies) { + std::vector<std::string> unknown_policies; + PolicyBitmask bitmask = 0; + for (const std::string& policy : policies) { + const auto result = std::find_if(kPolicyStringToFlag.begin(), kPolicyStringToFlag.end(), + [policy](const auto& it) { return policy == it.first; }); + if (result != kPolicyStringToFlag.end()) { + bitmask |= result->second; + } else { + unknown_policies.emplace_back(policy.empty() ? "empty" : policy); + } + } + + if (unknown_policies.empty()) { + return Result<PolicyBitmask>(bitmask); + } + + auto prefix = unknown_policies.size() == 1 ? "policy" : "policies"; + return Error("unknown %s: \"%s\"", prefix, android::base::Join(unknown_policies, ",").c_str()); +} + +std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask) { + std::vector<std::string> policies; + + for (const auto& policy : kPolicyStringToFlag) { + if ((bitmask & policy.second) != 0) { + policies.emplace_back(policy.first.to_string()); + } + } + + return policies; +} + +} // namespace android::idmap2::utils diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp index fbf2c777be9a..63ee8a648352 100644 --- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp @@ -16,6 +16,7 @@ #include "idmap2/PrettyPrintVisitor.h" +#include <istream> #include <string> #include "android-base/macros.h" @@ -28,42 +29,59 @@ namespace android::idmap2 { #define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry)) +#define TAB " " + void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { } void PrettyPrintVisitor::visit(const IdmapHeader& header) { - stream_ << "target apk path : " << header.GetTargetPath() << std::endl - << "overlay apk path : " << header.GetOverlayPath() << std::endl; + stream_ << "Paths:" << std::endl + << TAB "target apk path : " << header.GetTargetPath() << std::endl + << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl; + const std::string& debug = header.GetDebugInfo(); + if (!debug.empty()) { + std::istringstream debug_stream(debug); + std::string line; + stream_ << "Debug info:" << std::endl; + while (std::getline(debug_stream, line)) { + stream_ << TAB << line << std::endl; + } + } target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string()); if (target_apk_) { target_am_.SetApkAssets({target_apk_.get()}); } -} - -void PrettyPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { + stream_ << "Mapping:" << std::endl; } void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) { - last_seen_package_id_ = header.GetTargetPackageId(); } -void PrettyPrintVisitor::visit(const IdmapData::TypeEntry& type_entry) { +void PrettyPrintVisitor::visit(const IdmapData& data) { const bool target_package_loaded = !target_am_.GetApkAssets().empty(); - for (uint16_t i = 0; i < type_entry.GetEntryCount(); i++) { - const EntryId entry = type_entry.GetEntry(i); - if (entry == kNoEntry) { - continue; + const ResStringPool string_pool(data.GetStringPoolData(), + data.GetHeader()->GetStringPoolLength()); + const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset(); + + for (auto& target_entry : data.GetTargetEntries()) { + stream_ << TAB << base::StringPrintf("0x%08x ->", target_entry.target_id); + + if (target_entry.data_type != Res_value::TYPE_REFERENCE && + target_entry.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) { + stream_ << " " << utils::DataTypeToString(target_entry.data_type); } - const ResourceId target_resid = - RESID(last_seen_package_id_, type_entry.GetTargetTypeId(), type_entry.GetEntryOffset() + i); - const ResourceId overlay_resid = - RESID(last_seen_package_id_, type_entry.GetOverlayTypeId(), entry); + if (target_entry.data_type == Res_value::TYPE_STRING) { + stream_ << " \"" + << string_pool.string8ObjectAt(target_entry.data_value - string_pool_offset).c_str() + << "\""; + } else { + stream_ << " " << base::StringPrintf("0x%08x", target_entry.data_value); + } - stream_ << base::StringPrintf("0x%08x -> 0x%08x", target_resid, overlay_resid); if (target_package_loaded) { - Result<std::string> name = utils::ResToTypeEntryName(target_am_, target_resid); + Result<std::string> name = utils::ResToTypeEntryName(target_am_, target_entry.target_id); if (name) { stream_ << " " << *name; } diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp index dd14fd47aea8..3f62a2ae2029 100644 --- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -16,21 +16,30 @@ #include "idmap2/RawPrintVisitor.h" +#include <algorithm> #include <cstdarg> #include <string> #include "android-base/macros.h" #include "android-base/stringprintf.h" #include "androidfw/ApkAssets.h" +#include "idmap2/PolicyUtils.h" #include "idmap2/ResourceUtils.h" #include "idmap2/Result.h" using android::ApkAssets; +using android::idmap2::policy::PoliciesToDebugString; -namespace android::idmap2 { +namespace { + +size_t StringSizeWhenEncoded(const std::string& s) { + size_t null_bytes = 4 - (s.size() % 4); + return sizeof(uint32_t) + s.size() + null_bytes; +} -// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils -#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry)) +} // namespace + +namespace android::idmap2 { void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { } @@ -40,53 +49,105 @@ void RawPrintVisitor::visit(const IdmapHeader& header) { print(header.GetVersion(), "version"); print(header.GetTargetCrc(), "target crc"); print(header.GetOverlayCrc(), "overlay crc"); - print(header.GetTargetPath().to_string(), "target path"); - print(header.GetOverlayPath().to_string(), "overlay path"); + print(header.GetFulfilledPolicies(), "fulfilled policies: %s", + PoliciesToDebugString(header.GetFulfilledPolicies()).c_str()); + print(static_cast<uint8_t>(header.GetEnforceOverlayable()), "enforce overlayable"); + print(header.GetTargetPath().to_string(), kIdmapStringLength, "target path"); + print(header.GetOverlayPath().to_string(), kIdmapStringLength, "overlay path"); + print("...", StringSizeWhenEncoded(header.GetDebugInfo()), "debug info"); target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string()); if (target_apk_) { target_am_.SetApkAssets({target_apk_.get()}); } + + overlay_apk_ = ApkAssets::Load(header.GetOverlayPath().to_string()); + if (overlay_apk_) { + overlay_am_.SetApkAssets({overlay_apk_.get()}); + } } void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { -} + const bool target_package_loaded = !target_am_.GetApkAssets().empty(); + const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty(); -void RawPrintVisitor::visit(const IdmapData::Header& header) { - print(static_cast<uint16_t>(header.GetTargetPackageId()), "target package id"); - print(header.GetTypeCount(), "type count"); - last_seen_package_id_ = header.GetTargetPackageId(); -} + for (auto& target_entry : data.GetTargetEntries()) { + Result<std::string> target_name(Error("")); + if (target_package_loaded) { + target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id); + } + if (target_name) { + print(target_entry.target_id, "target id: %s", target_name->c_str()); + } else { + print(target_entry.target_id, "target id"); + } -void RawPrintVisitor::visit(const IdmapData::TypeEntry& type_entry) { - const bool target_package_loaded = !target_am_.GetApkAssets().empty(); + print(target_entry.data_type, "type: %s", + utils::DataTypeToString(target_entry.data_type).data()); + + Result<std::string> overlay_name(Error("")); + if (overlay_package_loaded && (target_entry.data_type == Res_value::TYPE_REFERENCE || + target_entry.data_type == Res_value::TYPE_DYNAMIC_REFERENCE)) { + overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.data_value); + } + if (overlay_name) { + print(target_entry.data_value, "value: %s", overlay_name->c_str()); + } else { + print(target_entry.data_value, "value"); + } + } - print(static_cast<uint16_t>(type_entry.GetTargetTypeId()), "target type"); - print(static_cast<uint16_t>(type_entry.GetOverlayTypeId()), "overlay type"); - print(static_cast<uint16_t>(type_entry.GetEntryCount()), "entry count"); - print(static_cast<uint16_t>(type_entry.GetEntryOffset()), "entry offset"); + for (auto& overlay_entry : data.GetOverlayEntries()) { + Result<std::string> overlay_name(Error("")); + if (overlay_package_loaded) { + overlay_name = utils::ResToTypeEntryName(overlay_am_, overlay_entry.overlay_id); + } + + if (overlay_name) { + print(overlay_entry.overlay_id, "overlay id: %s", overlay_name->c_str()); + } else { + print(overlay_entry.overlay_id, "overlay id"); + } + + Result<std::string> target_name(Error("")); + if (target_package_loaded) { + target_name = utils::ResToTypeEntryName(target_am_, overlay_entry.target_id); + } - for (uint16_t i = 0; i < type_entry.GetEntryCount(); i++) { - const EntryId entry = type_entry.GetEntry(i); - if (entry == kNoEntry) { - print(kPadding, "no entry"); + if (target_name) { + print(overlay_entry.target_id, "target id: %s", target_name->c_str()); } else { - const ResourceId target_resid = RESID(last_seen_package_id_, type_entry.GetTargetTypeId(), - type_entry.GetEntryOffset() + i); - const ResourceId overlay_resid = - RESID(last_seen_package_id_, type_entry.GetOverlayTypeId(), entry); - Result<std::string> name(Error("")); - if (target_package_loaded) { - name = utils::ResToTypeEntryName(target_am_, target_resid); - } - if (name) { - print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x %s", target_resid, overlay_resid, - name->c_str()); - } else { - print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x", target_resid, overlay_resid); - } + print(overlay_entry.target_id, "target id"); } } + + const size_t string_pool_length = data.GetHeader()->GetStringPoolLength(); + if (string_pool_length > 0) { + print_raw(string_pool_length, "%zu raw string pool bytes", string_pool_length); + } +} + +void RawPrintVisitor::visit(const IdmapData::Header& header) { + print(header.GetTargetPackageId(), "target package id"); + print(header.GetOverlayPackageId(), "overlay package id"); + print(header.GetTargetEntryCount(), "target entry count"); + print(header.GetOverlayEntryCount(), "overlay entry count"); + print(header.GetStringPoolIndexOffset(), "string pool index offset"); + print(header.GetStringPoolLength(), "string pool byte length"); +} + +// NOLINTNEXTLINE(cert-dcl50-cpp) +void RawPrintVisitor::print(uint8_t value, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: %02x", offset_, value) << " " << comment + << std::endl; + + offset_ += sizeof(uint8_t); } // NOLINTNEXTLINE(cert-dcl50-cpp) @@ -116,17 +177,30 @@ void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) { } // NOLINTNEXTLINE(cert-dcl50-cpp) -void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) { +void RawPrintVisitor::print(const std::string& value, size_t encoded_size, const char* fmt, ...) { va_list ap; va_start(ap, fmt); std::string comment; base::StringAppendV(&comment, fmt, ap); va_end(ap); - stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value + stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value << std::endl; - offset_ += kIdmapStringLength; + offset_ += encoded_size; +} + +// NOLINTNEXTLINE(cert-dcl50-cpp) +void RawPrintVisitor::print_raw(uint32_t length, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << std::endl; + + offset_ += length; } } // namespace android::idmap2 diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp new file mode 100644 index 000000000000..34589a1c39dc --- /dev/null +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2019 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 "idmap2/ResourceMapping.h" + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "android-base/stringprintf.h" +#include "androidfw/ResourceTypes.h" +#include "idmap2/PolicyUtils.h" +#include "idmap2/ResourceUtils.h" + +using android::base::StringPrintf; +using android::idmap2::utils::BitmaskToPolicies; +using android::idmap2::utils::IsReference; +using android::idmap2::utils::ResToTypeEntryName; +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + +namespace android::idmap2 { + +namespace { + +#define REWRITE_PACKAGE(resid, package_id) \ + (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U)) +#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24) + +std::string ConcatPolicies(const std::vector<std::string>& policies) { + std::string message; + for (const std::string& policy : policies) { + if (!message.empty()) { + message.append("|"); + } + message.append(policy); + } + + return message; +} + +Result<Unit> CheckOverlayable(const LoadedPackage& target_package, + const OverlayManifestInfo& overlay_info, + const PolicyBitmask& fulfilled_policies, + const ResourceId& target_resource) { + static constexpr const PolicyBitmask sDefaultPolicies = + PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION | + PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE; + + // If the resource does not have an overlayable definition, allow the resource to be overlaid if + // the overlay is preinstalled or signed with the same signature as the target. + if (!target_package.DefinesOverlayable()) { + return (sDefaultPolicies & fulfilled_policies) != 0 + ? Result<Unit>({}) + : Error( + "overlay must be preinstalled or signed with the same signature as the " + "target"); + } + + const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(target_resource); + if (overlayable_info == nullptr) { + // Do not allow non-overlayable resources to be overlaid. + return Error("target resource has no overlayable declaration"); + } + + if (overlay_info.target_name != overlayable_info->name) { + // If the overlay supplies a target overlayable name, the resource must belong to the + // overlayable defined with the specified name to be overlaid. + return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")", + overlay_info.target_name.c_str(), overlayable_info->name.c_str()); + } + + // Enforce policy restrictions if the resource is declared as overlayable. + if ((overlayable_info->policy_flags & fulfilled_policies) == 0) { + return Error(R"(overlay with policies "%s" does not fulfill any overlayable policies "%s")", + ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(), + ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str()); + } + + return Result<Unit>({}); +} + +// TODO(martenkongstad): scan for package name instead of assuming package at index 0 +// +// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package +// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so +// this assumption tends to work out. That said, the correct thing to do is to scan +// resources.arsc for a package with a given name as read from the package manifest instead of +// relying on a hard-coded index. This however requires storing the package name in the idmap +// header, which in turn requires incrementing the idmap version. Because the initial version of +// idmap2 is compatible with idmap, this will have to wait for now. +const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) { + const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages(); + if (packages.empty()) { + return nullptr; + } + int id = packages[0]->GetPackageId(); + return loaded_arsc.GetPackageById(id); +} + +Result<std::unique_ptr<Asset>> OpenNonAssetFromResource(const ResourceId& resource_id, + const AssetManager2& asset_manager) { + Res_value value{}; + ResTable_config selected_config{}; + uint32_t flags; + auto cookie = + asset_manager.GetResource(resource_id, /* may_be_bag */ false, + /* density_override */ 0U, &value, &selected_config, &flags); + if (cookie == kInvalidCookie) { + return Error("failed to find resource for id 0x%08x", resource_id); + } + + if (value.dataType != Res_value::TYPE_STRING) { + return Error("resource for is 0x%08x is not a file", resource_id); + } + + auto string_pool = asset_manager.GetStringPoolForCookie(cookie); + size_t len; + auto file_path16 = string_pool->stringAt(value.data, &len); + if (file_path16 == nullptr) { + return Error("failed to find string for index %d", value.data); + } + + // Load the overlay resource mappings from the file specified using android:resourcesMap. + auto file_path = String8(String16(file_path16)); + auto asset = asset_manager.OpenNonAsset(file_path.c_str(), Asset::AccessMode::ACCESS_BUFFER); + if (asset == nullptr) { + return Error("file \"%s\" not found", file_path.c_str()); + } + + return asset; +} + +} // namespace + +Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManager2* target_am, + const LoadedPackage* target_package, + const LoadedPackage* overlay_package, + size_t string_pool_offset, + const XmlParser& overlay_parser, + LogInfo& log_info) { + ResourceMapping resource_mapping; + auto root_it = overlay_parser.tree_iterator(); + if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") { + return Error("root element is not <overlay> tag"); + } + + const uint8_t target_package_id = target_package->GetPackageId(); + const uint8_t overlay_package_id = overlay_package->GetPackageId(); + auto overlay_it_end = root_it.end(); + for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) { + if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) { + return Error("failed to parse overlay xml document"); + } + + if (overlay_it->event() != XmlParser::Event::START_TAG) { + continue; + } + + if (overlay_it->name() != "item") { + return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str()); + } + + Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target"); + if (!target_resource) { + return Error(R"(<item> tag missing expected attribute "target")"); + } + + Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value"); + if (!overlay_resource) { + return Error(R"(<item> tag missing expected attribute "value")"); + } + + ResourceId target_id = + target_am->GetResourceId(*target_resource, "", target_package->GetPackageName()); + if (target_id == 0U) { + log_info.Warning(LogMessage() << "failed to find resource \"" << *target_resource + << "\" in target resources"); + continue; + } + + // Retrieve the compile-time resource id of the target resource. + target_id = REWRITE_PACKAGE(target_id, target_package_id); + + if (overlay_resource->dataType == Res_value::TYPE_STRING) { + overlay_resource->data += string_pool_offset; + } + + // Only rewrite resources defined within the overlay package to their corresponding target + // resource ids at runtime. + bool rewrite_overlay_reference = + IsReference(overlay_resource->dataType) + ? overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data) + : false; + + if (rewrite_overlay_reference) { + overlay_resource->dataType = Res_value::TYPE_DYNAMIC_REFERENCE; + } + + resource_mapping.AddMapping(target_id, overlay_resource->dataType, overlay_resource->data, + rewrite_overlay_reference); + } + + return resource_mapping; +} + +Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy( + const AssetManager2* target_am, const AssetManager2* overlay_am, + const LoadedPackage* target_package, const LoadedPackage* overlay_package) { + ResourceMapping resource_mapping; + const uint8_t target_package_id = target_package->GetPackageId(); + const auto end = overlay_package->end(); + for (auto iter = overlay_package->begin(); iter != end; ++iter) { + const ResourceId overlay_resid = *iter; + Result<std::string> name = utils::ResToTypeEntryName(*overlay_am, overlay_resid); + if (!name) { + continue; + } + + // Find the resource with the same type and entry name within the target package. + const std::string full_name = + base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str()); + ResourceId target_resource = target_am->GetResourceId(full_name); + if (target_resource == 0U) { + continue; + } + + // Retrieve the compile-time resource id of the target resource. + target_resource = REWRITE_PACKAGE(target_resource, target_package_id); + + resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid, + /* rewrite_overlay_reference */ false); + } + + return resource_mapping; +} + +void ResourceMapping::FilterOverlayableResources(const AssetManager2* target_am, + const LoadedPackage* target_package, + const LoadedPackage* overlay_package, + const OverlayManifestInfo& overlay_info, + const PolicyBitmask& fulfilled_policies, + LogInfo& log_info) { + std::set<ResourceId> remove_ids; + for (const auto& target_map : target_map_) { + const ResourceId target_resid = target_map.first; + Result<Unit> success = + CheckOverlayable(*target_package, overlay_info, fulfilled_policies, target_resid); + if (success) { + continue; + } + + // Attempting to overlay a resource that is not allowed to be overlaid is treated as a + // warning. + Result<std::string> name = utils::ResToTypeEntryName(*target_am, target_resid); + if (!name) { + name = StringPrintf("0x%08x", target_resid); + } + + log_info.Warning(LogMessage() << "overlay \"" << overlay_package->GetPackageName() + << "\" is not allowed to overlay resource \"" << *name + << "\" in target: " << success.GetErrorMessage()); + + remove_ids.insert(target_resid); + } + + for (const ResourceId target_resid : remove_ids) { + RemoveMapping(target_resid); + } +} + +Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_apk_assets, + const ApkAssets& overlay_apk_assets, + const OverlayManifestInfo& overlay_info, + const PolicyBitmask& fulfilled_policies, + bool enforce_overlayable, + LogInfo& log_info) { + AssetManager2 target_asset_manager; + if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true /* invalidate_caches */, + false /* filter_incompatible_configs*/)) { + return Error("failed to create target asset manager"); + } + + AssetManager2 overlay_asset_manager; + if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true /* invalidate_caches */, + false /* filter_incompatible_configs */)) { + return Error("failed to create overlay asset manager"); + } + + const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc(); + if (target_arsc == nullptr) { + return Error("failed to load target resources.arsc"); + } + + const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc(); + if (overlay_arsc == nullptr) { + return Error("failed to load overlay resources.arsc"); + } + + const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc); + if (target_pkg == nullptr) { + return Error("failed to load target package from resources.arsc"); + } + + const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc); + if (overlay_pkg == nullptr) { + return Error("failed to load overlay package from resources.arsc"); + } + + size_t string_pool_data_length = 0U; + size_t string_pool_offset = 0U; + std::unique_ptr<uint8_t[]> string_pool_data; + Result<ResourceMapping> resource_mapping = {{}}; + if (overlay_info.resource_mapping != 0U) { + // Use the dynamic reference table to find the assigned resource id of the map xml. + const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0); + uint32_t resource_mapping_id = overlay_info.resource_mapping; + ref_table->lookupResourceId(&resource_mapping_id); + + // Load the overlay resource mappings from the file specified using android:resourcesMap. + auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager); + if (!asset) { + return Error("failed opening xml for android:resourcesMap: %s", + asset.GetErrorMessage().c_str()); + } + + auto parser = + XmlParser::Create((*asset)->getBuffer(true /* wordAligned*/), (*asset)->getLength()); + if (!parser) { + return Error("failed opening ResXMLTree"); + } + + // Copy the xml string pool data before the parse goes out of scope. + auto& string_pool = (*parser)->get_strings(); + string_pool_data_length = string_pool.bytes(); + string_pool_data.reset(new uint8_t[string_pool_data_length]); + memcpy(string_pool_data.get(), string_pool.data(), string_pool_data_length); + + // Offset string indices by the size of the overlay resource table string pool. + string_pool_offset = overlay_arsc->GetStringPool()->size(); + + resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg, + string_pool_offset, *(*parser), log_info); + } else { + // If no file is specified using android:resourcesMap, it is assumed that the overlay only + // defines resources intended to override target resources of the same type and name. + resource_mapping = CreateResourceMappingLegacy(&target_asset_manager, &overlay_asset_manager, + target_pkg, overlay_pkg); + } + + if (!resource_mapping) { + return resource_mapping.GetError(); + } + + if (enforce_overlayable) { + // Filter out resources the overlay is not allowed to override. + (*resource_mapping) + .FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info, + fulfilled_policies, log_info); + } + + resource_mapping->target_package_id_ = target_pkg->GetPackageId(); + resource_mapping->overlay_package_id_ = overlay_pkg->GetPackageId(); + resource_mapping->string_pool_offset_ = string_pool_offset; + resource_mapping->string_pool_data_ = std::move(string_pool_data); + resource_mapping->string_pool_data_length_ = string_pool_data_length; + return std::move(*resource_mapping); +} + +OverlayResourceMap ResourceMapping::GetOverlayToTargetMap() const { + // An overlay resource can override multiple target resources at once. Rewrite the overlay + // resource as the first target resource it overrides. + OverlayResourceMap map; + for (const auto& mappings : overlay_map_) { + map.insert(std::make_pair(mappings.first, mappings.second)); + } + return map; +} + +Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource, + TargetValue::DataType data_type, + TargetValue::DataValue data_value, + bool rewrite_overlay_reference) { + if (target_map_.find(target_resource) != target_map_.end()) { + return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource); + } + + // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the + // runtime types are not compatible, it could cause runtime crashes when the resource is resolved. + + target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value})); + + if (rewrite_overlay_reference && IsReference(data_type)) { + overlay_map_.insert(std::make_pair(data_value, target_resource)); + } + + return Result<Unit>({}); +} + +void ResourceMapping::RemoveMapping(ResourceId target_resource) { + auto target_iter = target_map_.find(target_resource); + if (target_iter == target_map_.end()) { + return; + } + + const TargetValue value = target_iter->second; + target_map_.erase(target_iter); + + if (!IsReference(value.data_type)) { + return; + } + + auto overlay_iter = overlay_map_.equal_range(value.data_value); + for (auto i = overlay_iter.first; i != overlay_iter.second; ++i) { + if (i->second == target_resource) { + overlay_map_.erase(i); + return; + } + } +} + +} // namespace android::idmap2 diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp index dce83e35978d..98d026bc70dc 100644 --- a/cmds/idmap2/libidmap2/ResourceUtils.cpp +++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp @@ -22,18 +22,56 @@ #include "androidfw/StringPiece.h" #include "androidfw/Util.h" #include "idmap2/Result.h" -#include "idmap2/Xml.h" +#include "idmap2/XmlParser.h" #include "idmap2/ZipFile.h" using android::StringPiece16; using android::idmap2::Result; -using android::idmap2::Xml; +using android::idmap2::XmlParser; using android::idmap2::ZipFile; using android::util::Utf16ToUtf8; namespace android::idmap2::utils { -Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid) { +bool IsReference(uint8_t data_type) { + return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE; +} + +StringPiece DataTypeToString(uint8_t data_type) { + switch (data_type) { + case Res_value::TYPE_NULL: + return "null"; + case Res_value::TYPE_REFERENCE: + return "reference"; + case Res_value::TYPE_ATTRIBUTE: + return "attribute"; + case Res_value::TYPE_STRING: + return "string"; + case Res_value::TYPE_FLOAT: + return "float"; + case Res_value::TYPE_DIMENSION: + return "dimension"; + case Res_value::TYPE_FRACTION: + return "fraction"; + case Res_value::TYPE_DYNAMIC_REFERENCE: + return "reference (dynamic)"; + case Res_value::TYPE_DYNAMIC_ATTRIBUTE: + return "attribute (dynamic)"; + case Res_value::TYPE_INT_DEC: + case Res_value::TYPE_INT_HEX: + return "integer"; + case Res_value::TYPE_INT_BOOLEAN: + return "boolean"; + case Res_value::TYPE_INT_COLOR_ARGB8: + case Res_value::TYPE_INT_COLOR_RGB8: + case Res_value::TYPE_INT_COLOR_RGB4: + return "color"; + default: + return "unknown"; + } +} + +Result<std::string> ResToTypeEntryName(const AssetManager2& am, uint32_t resid) { AssetManager2::ResourceName name; if (!am.GetResourceName(resid, &name)) { return Error("no resource 0x%08x in asset manager", resid); @@ -65,52 +103,72 @@ Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, return Error("failed to uncompress AndroidManifest.xml from %s", path.c_str()); } - std::unique_ptr<const Xml> xml = Xml::Create(entry->buf, entry->size); + Result<std::unique_ptr<const XmlParser>> xml = XmlParser::Create(entry->buf, entry->size); if (!xml) { return Error("failed to parse AndroidManifest.xml from %s", path.c_str()); } + auto manifest_it = (*xml)->tree_iterator(); + if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") { + return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str()); + } + + auto overlay_it = std::find_if(manifest_it.begin(), manifest_it.end(), [](const auto& it) { + return it.event() == XmlParser::Event::START_TAG && it.name() == "overlay"; + }); + OverlayManifestInfo info{}; - const auto tag = xml->FindTag("overlay"); - if (!tag) { - if (assert_overlay) { - return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str()); + if (overlay_it == manifest_it.end()) { + if (!assert_overlay) { + return info; } - return info; + return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str()); } - auto iter = tag->find("targetPackage"); - if (iter == tag->end()) { - if (assert_overlay) { - return Error("android:targetPackage missing from <overlay> of %s", path.c_str()); - } + if (auto result_str = overlay_it->GetAttributeStringValue("targetPackage")) { + info.target_package = *result_str; } else { - info.target_package = iter->second; + return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(), + result_str.GetErrorMessage().c_str()); } - iter = tag->find("targetName"); - if (iter != tag->end()) { - info.target_name = iter->second; + if (auto result_str = overlay_it->GetAttributeStringValue("targetName")) { + info.target_name = *result_str; } - iter = tag->find("isStatic"); - if (iter != tag->end()) { - info.is_static = std::stoul(iter->second) != 0U; + if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) { + if (IsReference((*result_value).dataType)) { + info.resource_mapping = (*result_value).data; + } else { + return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s", + path.c_str()); + } } - iter = tag->find("priority"); - if (iter != tag->end()) { - info.priority = std::stoi(iter->second); + if (auto result_value = overlay_it->GetAttributeValue("isStatic")) { + if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT && + (*result_value).dataType <= Res_value::TYPE_LAST_INT) { + info.is_static = (*result_value).data != 0U; + } else { + return Error("android:isStatic is not a boolean in AndroidManifest.xml of %s", path.c_str()); + } + } + + if (auto result_value = overlay_it->GetAttributeValue("priority")) { + if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT && + (*result_value).dataType <= Res_value::TYPE_LAST_INT) { + info.priority = (*result_value).data; + } else { + return Error("android:priority is not an integer in AndroidManifest.xml of %s", path.c_str()); + } } - iter = tag->find("requiredSystemPropertyName"); - if (iter != tag->end()) { - info.requiredSystemPropertyName = iter->second; + if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyName")) { + info.requiredSystemPropertyName = *result_str; } - iter = tag->find("requiredSystemPropertyValue"); - if (iter != tag->end()) { - info.requiredSystemPropertyValue = iter->second; + if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyValue")) { + info.requiredSystemPropertyValue = *result_str; } return info; diff --git a/cmds/idmap2/libidmap2/Xml.cpp b/cmds/idmap2/libidmap2/Xml.cpp deleted file mode 100644 index 264586829c47..000000000000 --- a/cmds/idmap2/libidmap2/Xml.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "idmap2/Xml.h" - -#include <map> -#include <memory> -#include <string> -#include <utility> - -namespace android::idmap2 { - -std::unique_ptr<const Xml> Xml::Create(const uint8_t* data, size_t size, bool copyData) { - std::unique_ptr<Xml> xml(new Xml()); - if (xml->xml_.setTo(data, size, copyData) != NO_ERROR) { - return nullptr; - } - return xml; -} - -std::unique_ptr<std::map<std::string, std::string>> Xml::FindTag(const std::string& name) const { - const String16 tag_to_find(name.c_str(), name.size()); - xml_.restart(); - ResXMLParser::event_code_t type; - do { - type = xml_.next(); - if (type == ResXMLParser::START_TAG) { - size_t len; - const String16 tag(xml_.getElementName(&len)); - if (tag == tag_to_find) { - std::unique_ptr<std::map<std::string, std::string>> map( - new std::map<std::string, std::string>()); - for (size_t i = 0; i < xml_.getAttributeCount(); i++) { - const String16 key16(xml_.getAttributeName(i, &len)); - std::string key = String8(key16).c_str(); - - std::string value; - switch (xml_.getAttributeDataType(i)) { - case Res_value::TYPE_STRING: { - const String16 value16(xml_.getAttributeStringValue(i, &len)); - value = String8(value16).c_str(); - } break; - case Res_value::TYPE_INT_DEC: - case Res_value::TYPE_INT_HEX: - case Res_value::TYPE_INT_BOOLEAN: { - Res_value resValue; - xml_.getAttributeValue(i, &resValue); - value = std::to_string(resValue.data); - } break; - default: - return nullptr; - } - - map->emplace(std::make_pair(key, value)); - } - return map; - } - } - } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); - return nullptr; -} - -Xml::~Xml() { - xml_.uninit(); -} - -} // namespace android::idmap2 diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp new file mode 100644 index 000000000000..526a560907aa --- /dev/null +++ b/cmds/idmap2/libidmap2/XmlParser.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2019 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 "idmap2/XmlParser.h" + +#include <iostream> +#include <map> +#include <memory> +#include <string> +#include <utility> + +namespace android::idmap2 { + +template <typename T> +ResXMLParser::ResXMLPosition get_tree_position(const T& tree) { + ResXMLParser::ResXMLPosition pos{}; + tree.getPosition(&pos); + return pos; +} + +XmlParser::Node::Node(const ResXMLTree& tree) : Node(tree, get_tree_position(tree)) { +} +XmlParser::Node::Node(const ResXMLTree& tree, const ResXMLParser::ResXMLPosition& pos) + : parser_(tree) { + set_position(pos); +} + +bool XmlParser::Node::operator==(const XmlParser::Node& rhs) const { + ResXMLParser::ResXMLPosition pos = get_position(); + ResXMLParser::ResXMLPosition rhs_pos = rhs.get_position(); + return pos.curExt == rhs_pos.curExt && pos.curNode == rhs_pos.curNode && + pos.eventCode == rhs_pos.eventCode; +} + +bool XmlParser::Node::operator!=(const XmlParser::Node& rhs) const { + return !(*this == rhs); +} + +ResXMLParser::ResXMLPosition XmlParser::Node::get_position() const { + return get_tree_position(parser_); +} + +void XmlParser::Node::set_position(const ResXMLParser::ResXMLPosition& pos) { + parser_.setPosition(pos); +} + +bool XmlParser::Node::Seek(bool inner_child) { + if (parser_.getEventType() == XmlParser::Event::END_TAG) { + return false; + } + + ssize_t depth = 0; + XmlParser::Event code; + while ((code = parser_.next()) != XmlParser::Event::BAD_DOCUMENT && + code != XmlParser::Event::END_DOCUMENT) { + if (code == XmlParser::Event::START_TAG) { + if (++depth == (inner_child ? 1 : 0)) { + return true; + } + } else if (code == XmlParser::Event::END_TAG) { + if (--depth == (inner_child ? -1 : -2)) { + return false; + } + } + } + + return false; +} + +XmlParser::Event XmlParser::Node::event() const { + return parser_.getEventType(); +} + +std::string XmlParser::Node::name() const { + size_t len; + const String16 key16(parser_.getElementName(&len)); + return String8(key16).c_str(); +} + +Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& name) const { + auto value = GetAttributeValue(name); + if (!value) { + return value.GetError(); + } + + switch ((*value).dataType) { + case Res_value::TYPE_STRING: { + size_t len; + const String16 value16(parser_.getStrings().stringAt((*value).data, &len)); + return std::string(String8(value16).c_str()); + } + case Res_value::TYPE_INT_DEC: + case Res_value::TYPE_INT_HEX: + case Res_value::TYPE_INT_BOOLEAN: { + return std::to_string((*value).data); + } + default: + return Error(R"(Failed to convert attribute "%s" value to a string)", name.c_str()); + } +} + +Result<Res_value> XmlParser::Node::GetAttributeValue(const std::string& name) const { + size_t len; + for (size_t i = 0; i < parser_.getAttributeCount(); i++) { + const String16 key16(parser_.getAttributeName(i, &len)); + std::string key = String8(key16).c_str(); + if (key != name) { + continue; + } + + Res_value res_value{}; + if (parser_.getAttributeValue(i, &res_value) == BAD_TYPE) { + return Error(R"(Bad value for attribute "%s")", name.c_str()); + } + + return res_value; + } + + return Error(R"(Failed to find attribute "%s")", name.c_str()); +} + +Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size, + bool copy_data) { + auto parser = std::unique_ptr<const XmlParser>(new XmlParser()); + if (parser->tree_.setTo(data, size, copy_data) != NO_ERROR) { + return Error("Malformed xml block"); + } + + // Find the beginning of the first tag. + XmlParser::Event event; + while ((event = parser->tree_.next()) != XmlParser::Event::BAD_DOCUMENT && + event != XmlParser::Event::END_DOCUMENT && event != XmlParser::Event::START_TAG) { + } + + if (event == XmlParser::Event::END_DOCUMENT) { + return Error("Root tag was not be found"); + } + + if (event == XmlParser::Event::BAD_DOCUMENT) { + return Error("Bad xml document"); + } + + return parser; +} + +XmlParser::~XmlParser() { + tree_.uninit(); +} + +} // namespace android::idmap2 diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp index 4f5e3a45f183..1e1a218163f0 100644 --- a/cmds/idmap2/libidmap2/ZipFile.cpp +++ b/cmds/idmap2/libidmap2/ZipFile.cpp @@ -34,6 +34,7 @@ std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) { ::ZipArchiveHandle handle; int32_t status = ::OpenArchive(path.c_str(), &handle); if (status != 0) { + ::CloseArchive(handle); return nullptr; } return std::unique_ptr<ZipFile>(new ZipFile(handle)); diff --git a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h new file mode 100644 index 000000000000..5bd353af4ad3 --- /dev/null +++ b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 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 IDMAP2_INCLUDE_IDMAP2_POLICIES_H_ +#define IDMAP2_INCLUDE_IDMAP2_POLICIES_H_ + +#include <array> +#include <string> +#include <vector> + +#include "android-base/stringprintf.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" + +using android::base::StringPrintf; + +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + +namespace android::idmap2::policy { + +constexpr const char* kPolicyActor = "actor"; +constexpr const char* kPolicyOdm = "odm"; +constexpr const char* kPolicyOem = "oem"; +constexpr const char* kPolicyProduct = "product"; +constexpr const char* kPolicyPublic = "public"; +constexpr const char* kPolicySignature = "signature"; +constexpr const char* kPolicySystem = "system"; +constexpr const char* kPolicyVendor = "vendor"; + +inline static const std::array<std::pair<StringPiece, PolicyFlags>, 8> kPolicyStringToFlag = { + std::pair{kPolicyActor, PolicyFlags::ACTOR_SIGNATURE}, + {kPolicyOdm, PolicyFlags::ODM_PARTITION}, + {kPolicyOem, PolicyFlags::OEM_PARTITION}, + {kPolicyProduct, PolicyFlags::PRODUCT_PARTITION}, + {kPolicyPublic, PolicyFlags::PUBLIC}, + {kPolicySignature, PolicyFlags::SIGNATURE}, + {kPolicySystem, PolicyFlags::SYSTEM_PARTITION}, + {kPolicyVendor, PolicyFlags::VENDOR_PARTITION}, +}; + +inline static std::string PoliciesToDebugString(PolicyBitmask policies) { + std::string str; + uint32_t remaining = policies; + for (auto const& policy : kPolicyStringToFlag) { + if ((policies & policy.second) != policy.second) { + continue; + } + if (!str.empty()) { + str.append("|"); + } + str.append(policy.first.data()); + remaining &= ~policy.second; + } + if (remaining != 0) { + if (!str.empty()) { + str.append("|"); + } + str.append(StringPrintf("0x%08x", remaining)); + } + return !str.empty() ? str : "none"; +} + +} // namespace android::idmap2::policy + +#endif // IDMAP2_INCLUDE_IDMAP2_POLICIES_H_ diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh index 41d3c69540b2..a372abdaa146 100755 --- a/cmds/idmap2/static-checks.sh +++ b/cmds/idmap2/static-checks.sh @@ -27,10 +27,11 @@ function _eval() local red="\e[31m" local green="\e[32m" local reset="\e[0m" + local output _log "${green}[ RUN ]${reset} ${label}" - local output="$(eval "$cmd")" - if [[ -z "${output}" ]]; then + output="$(eval "$cmd" 2>&1)" + if [[ $? -eq 0 ]]; then _log "${green}[ OK ]${reset} ${label}" return 0 else diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp index 9348ab721493..5fea7bcdaac5 100644 --- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp +++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp @@ -18,6 +18,7 @@ #include <sstream> #include <string> #include <utility> +#include <vector> #include "TestHelpers.h" #include "androidfw/ApkAssets.h" @@ -47,118 +48,49 @@ TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) { ASSERT_TRUE(result2); const auto idmap2 = std::move(*result2); + ASSERT_EQ(idmap1->GetHeader()->GetFulfilledPolicies(), + idmap2->GetHeader()->GetFulfilledPolicies()); + ASSERT_EQ(idmap1->GetHeader()->GetEnforceOverlayable(), + idmap2->GetHeader()->GetEnforceOverlayable()); + ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath()); ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc()); ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath()); ASSERT_EQ(idmap1->GetData().size(), 1U); ASSERT_EQ(idmap1->GetData().size(), idmap2->GetData().size()); - const auto& data1 = idmap1->GetData()[0]; - const auto& data2 = idmap2->GetData()[0]; - - ASSERT_EQ(data1->GetHeader()->GetTargetPackageId(), data2->GetHeader()->GetTargetPackageId()); - ASSERT_EQ(data1->GetTypeEntries().size(), 2U); - ASSERT_EQ(data1->GetTypeEntries().size(), data2->GetTypeEntries().size()); - ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(0), data2->GetTypeEntries()[0]->GetEntry(0)); - ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(1), data2->GetTypeEntries()[0]->GetEntry(1)); - ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(2), data2->GetTypeEntries()[0]->GetEntry(2)); - ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(0), data2->GetTypeEntries()[1]->GetEntry(0)); - ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(1), data2->GetTypeEntries()[1]->GetEntry(1)); - ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(2), data2->GetTypeEntries()[1]->GetEntry(2)); -} - -TEST(BinaryStreamVisitorTests, CreateIdmapFromApkAssetsInteropWithLoadedIdmap) { - const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); - std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); - ASSERT_THAT(target_apk, NotNull()); - - const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); - std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); - ASSERT_THAT(overlay_apk, NotNull()); - - const auto idmap = - Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, - PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true); - ASSERT_TRUE(idmap); - - std::stringstream stream; - BinaryStreamVisitor visitor(stream); - (*idmap)->accept(&visitor); - const std::string str = stream.str(); - const StringPiece data(str); - std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(data); - ASSERT_THAT(loaded_idmap, NotNull()); - ASSERT_EQ(loaded_idmap->TargetPackageId(), 0x7f); - - const IdmapEntry_header* header = loaded_idmap->GetEntryMapForType(0x01); - ASSERT_THAT(header, NotNull()); - - EntryId entry; - bool success = LoadedIdmap::Lookup(header, 0x0000, &entry); - ASSERT_TRUE(success); - ASSERT_EQ(entry, 0x0000); - - header = loaded_idmap->GetEntryMapForType(0x02); - ASSERT_THAT(header, NotNull()); - - success = LoadedIdmap::Lookup(header, 0x0000, &entry); // string/a - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0001, &entry); // string/b - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0002, &entry); // string/c - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0003, &entry); // string/policy_odm - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0004, &entry); // string/policy_oem - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0005, &entry); // string/other - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0006, &entry); // string/not_overlayable - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0007, &entry); // string/policy_product - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0008, &entry); // string/policy_public - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x0009, &entry); // string/policy_system - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x000a, &entry); // string/policy_system_vendor - ASSERT_FALSE(success); - - success = LoadedIdmap::Lookup(header, 0x000b, &entry); // string/policy_signature - ASSERT_FALSE(success); + const std::vector<std::unique_ptr<const IdmapData>>& data_blocks1 = idmap1->GetData(); + ASSERT_EQ(data_blocks1.size(), 1U); + const std::unique_ptr<const IdmapData>& data1 = data_blocks1[0]; + ASSERT_THAT(data1, NotNull()); - success = LoadedIdmap::Lookup(header, 0x000c, &entry); // string/str1 - ASSERT_TRUE(success); - ASSERT_EQ(entry, 0x0000); + const std::vector<std::unique_ptr<const IdmapData>>& data_blocks2 = idmap2->GetData(); + ASSERT_EQ(data_blocks2.size(), 1U); + const std::unique_ptr<const IdmapData>& data2 = data_blocks2[0]; + ASSERT_THAT(data2, NotNull()); - success = LoadedIdmap::Lookup(header, 0x000d, &entry); // string/str2 - ASSERT_FALSE(success); + const auto& target_entries1 = data1->GetTargetEntries(); + const auto& target_entries2 = data2->GetTargetEntries(); + ASSERT_EQ(target_entries1.size(), target_entries2.size()); + ASSERT_EQ(target_entries1[0].target_id, target_entries2[0].target_id); + ASSERT_EQ(target_entries1[0].data_value, target_entries2[0].data_value); - success = LoadedIdmap::Lookup(header, 0x000e, &entry); // string/str3 - ASSERT_TRUE(success); - ASSERT_EQ(entry, 0x0001); + ASSERT_EQ(target_entries1[1].target_id, target_entries2[1].target_id); + ASSERT_EQ(target_entries1[1].data_value, target_entries2[1].data_value); - success = LoadedIdmap::Lookup(header, 0x000f, &entry); // string/str4 - ASSERT_TRUE(success); - ASSERT_EQ(entry, 0x0002); + ASSERT_EQ(target_entries1[2].target_id, target_entries2[2].target_id); + ASSERT_EQ(target_entries1[2].data_value, target_entries2[2].data_value); - success = LoadedIdmap::Lookup(header, 0x0010, &entry); // string/x - ASSERT_FALSE(success); + const auto& overlay_entries1 = data1->GetOverlayEntries(); + const auto& overlay_entries2 = data2->GetOverlayEntries(); + ASSERT_EQ(overlay_entries1.size(), overlay_entries2.size()); + ASSERT_EQ(overlay_entries1[0].overlay_id, overlay_entries2[0].overlay_id); + ASSERT_EQ(overlay_entries1[0].target_id, overlay_entries2[0].target_id); - success = LoadedIdmap::Lookup(header, 0x0011, &entry); // string/y - ASSERT_FALSE(success); + ASSERT_EQ(overlay_entries1[1].overlay_id, overlay_entries2[1].overlay_id); + ASSERT_EQ(overlay_entries1[1].target_id, overlay_entries2[1].target_id); - success = LoadedIdmap::Lookup(header, 0x0012, &entry); // string/z - ASSERT_FALSE(success); + ASSERT_EQ(overlay_entries1[2].overlay_id, overlay_entries2[2].overlay_id); + ASSERT_EQ(overlay_entries1[2].target_id, overlay_entries2[2].target_id); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp index f55acee029dc..8af4037be954 100644 --- a/cmds/idmap2/tests/FileUtilsTests.cpp +++ b/cmds/idmap2/tests/FileUtilsTests.cpp @@ -56,12 +56,12 @@ TEST(FileUtilsTests, FindFilesFindApkFilesRecursive) { return type == DT_REG && path.size() > 4 && path.compare(path.size() - 4, 4, ".apk") == 0; }); ASSERT_THAT(v, NotNull()); - ASSERT_EQ(v->size(), 10U); + ASSERT_EQ(v->size(), 11U); ASSERT_EQ(std::set<std::string>(v->begin(), v->end()), std::set<std::string>( {root + "/target/target.apk", root + "/target/target-no-overlayable.apk", root + "/overlay/overlay.apk", root + "/overlay/overlay-no-name.apk", - root + "/overlay/overlay-no-name-static.apk", + root + "/overlay/overlay-no-name-static.apk", root + "/overlay/overlay-shared.apk", root + "/overlay/overlay-static-1.apk", root + "/overlay/overlay-static-2.apk", root + "/signature-overlay/signature-overlay.apk", root + "/system-overlay/system-overlay.apk", diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp index 499eb99af290..d896cf9c11ba 100644 --- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp +++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp @@ -34,6 +34,7 @@ #include <string> #include <vector> +#include "R.h" #include "TestHelpers.h" #include "androidfw/PosixUtils.h" #include "gmock/gmock.h" @@ -127,10 +128,14 @@ TEST_F(Idmap2BinaryTests, Dump) { // clang-format on ASSERT_THAT(result, NotNull()); ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; - ASSERT_NE(result->stdout.find("0x7f010000 -> 0x7f010000 integer/int1"), std::string::npos); - ASSERT_NE(result->stdout.find("0x7f02000c -> 0x7f020000 string/str1"), std::string::npos); - ASSERT_NE(result->stdout.find("0x7f02000e -> 0x7f020001 string/str3"), std::string::npos); - ASSERT_NE(result->stdout.find("0x7f02000f -> 0x7f020002 string/str4"), std::string::npos); + ASSERT_NE(result->stdout.find(R::target::integer::literal::int1 + " -> 0x7f010000 integer/int1"), + std::string::npos); + ASSERT_NE(result->stdout.find(R::target::string::literal::str1 + " -> 0x7f020000 string/str1"), + std::string::npos); + ASSERT_NE(result->stdout.find(R::target::string::literal::str3 + " -> 0x7f020001 string/str3"), + std::string::npos); + ASSERT_NE(result->stdout.find(R::target::string::literal::str4 + " -> 0x7f020002 string/str4"), + std::string::npos); // clang-format off result = ExecuteBinary({"idmap2", @@ -141,7 +146,6 @@ TEST_F(Idmap2BinaryTests, Dump) { ASSERT_THAT(result, NotNull()); ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; ASSERT_NE(result->stdout.find("00000000: 504d4449 magic"), std::string::npos); - ASSERT_NE(result->stdout.find("00000210: 007f target package id"), std::string::npos); // clang-format off result = ExecuteBinary({"idmap2", @@ -298,7 +302,7 @@ TEST_F(Idmap2BinaryTests, Lookup) { "lookup", "--idmap-path", GetIdmapPath(), "--config", "", - "--resid", "0x7f02000c"}); // string/str1 + "--resid", R::target::string::literal::str1}); // clang-format on ASSERT_THAT(result, NotNull()); ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index 0f47f1e77d7b..6fab5e0f8ae1 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -22,6 +22,8 @@ #include <utility> #include <vector> +#include "R.h" +#include "TestConstants.h" #include "TestHelpers.h" #include "android-base/macros.h" #include "androidfw/ApkAssets.h" @@ -30,12 +32,25 @@ #include "idmap2/BinaryStreamVisitor.h" #include "idmap2/CommandLineOptions.h" #include "idmap2/Idmap.h" +#include "idmap2/LogInfo.h" +using android::Res_value; using ::testing::IsNull; using ::testing::NotNull; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace android::idmap2 { +#define ASSERT_TARGET_ENTRY(entry, target_resid, type, value) \ + ASSERT_EQ(entry.target_id, target_resid); \ + ASSERT_EQ(entry.data_type, type); \ + ASSERT_EQ(entry.data_value, value) + +#define ASSERT_OVERLAY_ENTRY(entry, overlay_resid, target_resid) \ + ASSERT_EQ(entry.overlay_id, overlay_resid); \ + ASSERT_EQ(entry.target_id, target_resid) + TEST(IdmapTests, TestCanonicalIdmapPathFor) { ASSERT_EQ(Idmap::CanonicalIdmapPathFor("/foo", "/vendor/overlay/bar.apk"), "/foo/vendor@overlay@bar.apk@idmap"); @@ -47,17 +62,20 @@ TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); ASSERT_EQ(header->GetMagic(), 0x504d4449U); - ASSERT_EQ(header->GetVersion(), 0x01U); + ASSERT_EQ(header->GetVersion(), 0x04U); ASSERT_EQ(header->GetTargetCrc(), 0x1234U); ASSERT_EQ(header->GetOverlayCrc(), 0x5678U); - ASSERT_EQ(header->GetTargetPath().to_string(), "target.apk"); - ASSERT_EQ(header->GetOverlayPath().to_string(), "overlay.apk"); + ASSERT_EQ(header->GetFulfilledPolicies(), 0x11); + ASSERT_EQ(header->GetEnforceOverlayable(), true); + ASSERT_EQ(header->GetTargetPath().to_string(), "targetX.apk"); + ASSERT_EQ(header->GetOverlayPath().to_string(), "overlayX.apk"); + ASSERT_EQ(header->GetDebugInfo(), "debug"); } TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) { std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); // overwrite the target path string, including the terminating null, with '.' - for (size_t i = 0x10; i < 0x110; i++) { + for (size_t i = 0x15; i < 0x115; i++) { raw[i] = '.'; } std::istringstream stream(raw); @@ -66,58 +84,40 @@ TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) { } TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { - const size_t offset = 0x210; + const size_t offset = 0x221; std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), idmap_raw_data_len - offset); std::istringstream stream(raw); std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); - ASSERT_EQ(header->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(header->GetTypeCount(), 2U); -} - -TEST(IdmapTests, CreateIdmapDataResourceTypeFromBinaryStream) { - const size_t offset = 0x214; - std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), - idmap_raw_data_len - offset); - std::istringstream stream(raw); - - std::unique_ptr<const IdmapData::TypeEntry> data = IdmapData::TypeEntry::FromBinaryStream(stream); - ASSERT_THAT(data, NotNull()); - ASSERT_EQ(data->GetTargetTypeId(), 0x02U); - ASSERT_EQ(data->GetOverlayTypeId(), 0x02U); - ASSERT_EQ(data->GetEntryCount(), 1U); - ASSERT_EQ(data->GetEntryOffset(), 0U); - ASSERT_EQ(data->GetEntry(0), 0U); + ASSERT_EQ(header->GetTargetEntryCount(), 0x03); + ASSERT_EQ(header->GetOverlayEntryCount(), 0x03); } TEST(IdmapTests, CreateIdmapDataFromBinaryStream) { - const size_t offset = 0x210; + const size_t offset = 0x221; std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), idmap_raw_data_len - offset); std::istringstream stream(raw); std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream); ASSERT_THAT(data, NotNull()); - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U); - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 2U); - - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetEntryCount(), 1U); - ASSERT_EQ(types[0]->GetEntryOffset(), 0U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); - - ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03U); - ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03U); - ASSERT_EQ(types[1]->GetEntryCount(), 3U); - ASSERT_EQ(types[1]->GetEntryOffset(), 3U); - ASSERT_EQ(types[1]->GetEntry(0), 0x0000U); - ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); - ASSERT_EQ(types[1]->GetEntry(2), 0x0001U); + + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 3U); + ASSERT_TARGET_ENTRY(target_entries[0], 0x7f020000, 0x01 /* Res_value::TYPE_REFERENCE */, + 0x7f020000); + ASSERT_TARGET_ENTRY(target_entries[1], 0x7f030000, 0x01 /* Res_value::TYPE_REFERENCE */, + 0x7f030000); + ASSERT_TARGET_ENTRY(target_entries[2], 0x7f030002, 0x01 /* Res_value::TYPE_REFERENCE */, + 0x7f030001); + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(target_entries.size(), 3U); + ASSERT_OVERLAY_ENTRY(overlay_entries[0], 0x7f020000, 0x7f020000); + ASSERT_OVERLAY_ENTRY(overlay_entries[1], 0x7f030000, 0x7f030000); + ASSERT_OVERLAY_ENTRY(overlay_entries[2], 0x7f030001, 0x7f030002); } TEST(IdmapTests, CreateIdmapFromBinaryStream) { @@ -130,34 +130,31 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) { ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01U); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U); - ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "target.apk"); - ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlay.apk"); + ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), 0x11); + ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true); + ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "targetX.apk"); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlayX.apk"); const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); ASSERT_EQ(dataBlocks.size(), 1U); const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U); - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 2U); - - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetEntryCount(), 1U); - ASSERT_EQ(types[0]->GetEntryOffset(), 0U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); - - ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03U); - ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03U); - ASSERT_EQ(types[1]->GetEntryCount(), 3U); - ASSERT_EQ(types[1]->GetEntryOffset(), 3U); - ASSERT_EQ(types[1]->GetEntry(0), 0x0000U); - ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); - ASSERT_EQ(types[1]->GetEntry(2), 0x0001U); + ASSERT_THAT(data, NotNull()); + + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 3U); + ASSERT_TARGET_ENTRY(target_entries[0], 0x7f020000, Res_value::TYPE_REFERENCE, 0x7f020000); + ASSERT_TARGET_ENTRY(target_entries[1], 0x7f030000, Res_value::TYPE_REFERENCE, 0x7f030000); + ASSERT_TARGET_ENTRY(target_entries[2], 0x7f030002, Res_value::TYPE_REFERENCE, 0x7f030001); + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(target_entries.size(), 3U); + ASSERT_OVERLAY_ENTRY(overlay_entries[0], 0x7f020000, 0x7f020000); + ASSERT_OVERLAY_ENTRY(overlay_entries[1], 0x7f030000, 0x7f030000); + ASSERT_OVERLAY_ENTRY(overlay_entries[2], 0x7f030001, 0x7f030002); } TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) { @@ -169,301 +166,192 @@ TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) { ASSERT_FALSE(result); } -void CreateIdmap(const StringPiece& target_apk_path, const StringPiece& overlay_apk_path, - const PolicyBitmask& fulfilled_policies, bool enforce_overlayable, - std::unique_ptr<const Idmap>* out_idmap) { - std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path.to_string()); +TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) { + std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; + std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk"; + + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); ASSERT_THAT(target_apk, NotNull()); - std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path.to_string()); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - auto result = - Idmap::FromApkAssets(target_apk_path.to_string(), *target_apk, overlay_apk_path.to_string(), - *overlay_apk, fulfilled_policies, enforce_overlayable); - *out_idmap = result ? std::move(*result) : nullptr; -} - -TEST(IdmapTests, CreateIdmapFromApkAssets) { - std::unique_ptr<const Idmap> idmap; - std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; - std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk"; - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PUBLIC, - /* enforce_overlayable */ true, &idmap); + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); + auto& idmap = *idmap_result; + ASSERT_THAT(idmap, NotNull()); ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01U); - ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x76a20829); - ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x8635c2ed); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U); + ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC); + ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC); + ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC); + ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true); ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path); ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); - ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); - - const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); - ASSERT_EQ(dataBlocks.size(), 1U); - - const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; - - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U); - - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 2U); - - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetEntryCount(), 1U); - ASSERT_EQ(types[0]->GetEntryOffset(), 0U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); - - ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02U); - ASSERT_EQ(types[1]->GetEntryCount(), 4U); - ASSERT_EQ(types[1]->GetEntryOffset(), 12U); - ASSERT_EQ(types[1]->GetEntry(0), 0x0000U); - ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); - ASSERT_EQ(types[1]->GetEntry(2), 0x0001U); - ASSERT_EQ(types[1]->GetEntry(3), 0x0002U); } -// Overlays should abide by all overlayable restrictions if enforcement of overlayable is enabled. -TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySystemPublic) { - std::unique_ptr<const Idmap> idmap; - std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; - std::string overlay_apk_path = GetTestDataPath() + "/system-overlay/system-overlay.apk"; - CreateIdmap(target_apk_path, overlay_apk_path, - PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - - const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); - ASSERT_EQ(dataBlocks.size(), 1U); +Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets( + const android::StringPiece& local_target_apk_path, + const android::StringPiece& local_overlay_apk_path, const OverlayManifestInfo& overlay_info, + const PolicyBitmask& fulfilled_policies, bool enforce_overlayable) { + const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data()); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + return Error(R"(Failed to load target apk "%s")", target_apk_path.data()); + } - const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data()); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data()); + } - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U); + LogInfo log_info; + auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, + fulfilled_policies, enforce_overlayable, log_info); - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 1U); + if (!mapping) { + return mapping.GetError(); + } - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetEntryCount(), 4U); - ASSERT_EQ(types[0]->GetEntryOffset(), 8U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); // string/policy_public - ASSERT_EQ(types[0]->GetEntry(1), kNoEntry); // string/policy_signature - ASSERT_EQ(types[0]->GetEntry(2), 0x0001U); // string/policy_system - ASSERT_EQ(types[0]->GetEntry(3), 0x0002U); // string/policy_system_vendor + return IdmapData::FromResourceMapping(*mapping); } -TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySignature) { - std::unique_ptr<const Idmap> idmap; +TEST(IdmapTests, CreateIdmapDataFromApkAssets) { std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; - std::string overlay_apk_path = GetTestDataPath() + "/signature-overlay/signature-overlay.apk"; - CreateIdmap(target_apk_path, overlay_apk_path, - PolicyFlags::POLICY_PUBLIC | PolicyFlags::POLICY_SIGNATURE, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - - const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); - ASSERT_EQ(dataBlocks.size(), 1U); - - const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; - - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U); + std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk"; - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 1U); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetEntryCount(), 1U); - ASSERT_EQ(types[0]->GetEntryOffset(), 9U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); // string/policy_signature -} + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); -// Overlays should abide by all overlayable restrictions if enforcement of overlayable is enabled. -TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) { - std::unique_ptr<const Idmap> idmap; - std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; - std::string overlay_apk_path = - GetTestDataPath() + "/system-overlay-invalid/system-overlay-invalid.apk"; - CreateIdmap(target_apk_path, overlay_apk_path, - PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC, - /* enforce_overlayable */ true, &idmap); + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); + auto& idmap = *idmap_result; ASSERT_THAT(idmap, NotNull()); const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); ASSERT_EQ(dataBlocks.size(), 1U); const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + ASSERT_THAT(data, NotNull()); - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U); - - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 1U); - - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetEntryCount(), 4U); - ASSERT_EQ(types[0]->GetEntryOffset(), 8U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0005U); // string/policy_public - ASSERT_EQ(types[0]->GetEntry(1), kNoEntry); // string/policy_signature - ASSERT_EQ(types[0]->GetEntry(2), 0x0007U); // string/policy_system - ASSERT_EQ(types[0]->GetEntry(3), 0x0008U); // string/policy_system_vendor + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1, + Res_value::TYPE_DYNAMIC_REFERENCE, R::overlay::integer::int1); + ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay::string::str1); + ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay::string::str3); + ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay::string::str4); + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay::integer::int1, R::target::integer::int1); + ASSERT_OVERLAY_ENTRY(overlay_entries[1], R::overlay::string::str1, R::target::string::str1); + ASSERT_OVERLAY_ENTRY(overlay_entries[2], R::overlay::string::str3, R::target::string::str3); + ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4); } -// Overlays should ignore all overlayable restrictions if enforcement of overlayable is disabled. -TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) { - std::unique_ptr<const Idmap> idmap; +TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) { std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; - std::string overlay_apk_path = - GetTestDataPath() + "/system-overlay-invalid/system-overlay-invalid.apk"; - CreateIdmap(target_apk_path, overlay_apk_path, - PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC, - /* enforce_overlayable */ false, &idmap); - ASSERT_THAT(idmap, NotNull()); + std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk"; - const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); - ASSERT_EQ(dataBlocks.size(), 1U); - - const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U); - - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 1U); - - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetEntryCount(), 9U); - ASSERT_EQ(types[0]->GetEntryOffset(), 3U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); // string/not_overlayable - ASSERT_EQ(types[0]->GetEntry(1), 0x0001U); // string/policy_odm - ASSERT_EQ(types[0]->GetEntry(2), 0x0002U); // string/policy_oem - ASSERT_EQ(types[0]->GetEntry(3), 0x0003U); // string/other - ASSERT_EQ(types[0]->GetEntry(4), 0x0004U); // string/policy_product - ASSERT_EQ(types[0]->GetEntry(5), 0x0005U); // string/policy_public - ASSERT_EQ(types[0]->GetEntry(6), 0x0006U); // string/policy_signature - ASSERT_EQ(types[0]->GetEntry(7), 0x0007U); // string/policy_system - ASSERT_EQ(types[0]->GetEntry(8), 0x0008U); // string/policy_system_vendor -} + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); -// Overlays that do not specify a target <overlayable> can overlay resources defined as overlayable. -TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsNoDefinedOverlayableAndNoTargetName) { - std::unique_ptr<const Idmap> idmap; - std::string target_apk_path = GetTestDataPath() + "/target/target-no-overlayable.apk"; - std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-no-name.apk"; - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PUBLIC, - /* enforce_overlayable */ false, &idmap); + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); + auto& idmap = *idmap_result; ASSERT_THAT(idmap, NotNull()); const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); ASSERT_EQ(dataBlocks.size(), 1U); const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + ASSERT_THAT(data, NotNull()); - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2U); - - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 2U); - - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetEntryCount(), 1U); - ASSERT_EQ(types[0]->GetEntryOffset(), 0U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); // string/int1 - - ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02U); - ASSERT_EQ(types[1]->GetEntryCount(), 4U); - ASSERT_EQ(types[1]->GetEntryOffset(), 12U); - ASSERT_EQ(types[1]->GetEntry(0), 0x0000U); // string/str1 - ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); // string/str2 - ASSERT_EQ(types[1]->GetEntry(2), 0x0001U); // string/str3 - ASSERT_EQ(types[1]->GetEntry(3), 0x0002U); // string/str4 + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1, + Res_value::TYPE_DYNAMIC_REFERENCE, R::overlay_shared::integer::int1); + ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay_shared::string::str1); + ASSERT_TARGET_ENTRY(target_entries[2], R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay_shared::string::str3); + ASSERT_TARGET_ENTRY(target_entries[3], R::target::string::str4, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay_shared::string::str4); + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay_shared::integer::int1, + R::target::integer::int1); + ASSERT_OVERLAY_ENTRY(overlay_entries[1], R::overlay_shared::string::str1, + R::target::string::str1); + ASSERT_OVERLAY_ENTRY(overlay_entries[2], R::overlay_shared::string::str3, + R::target::string::str3); + ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay_shared::string::str4, + R::target::string::str4); } -// Overlays that are not pre-installed and are not signed with the same signature as the target -// cannot overlay packages that have not defined overlayable resources. -TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsDefaultPoliciesPublicFail) { - std::unique_ptr<const Idmap> idmap; - std::string target_apk_path = GetTestDataPath() + "/target/target-no-overlayable.apk"; - std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-no-name.apk"; - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PUBLIC, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, IsNull()); +TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) { + OverlayManifestInfo info{}; + info.target_package = "test.target"; + info.target_name = "TestResources"; + info.resource_mapping = 0x7f030001; // xml/overlays_different_packages + auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info, + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage(); + auto& data = *idmap_data; + + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 2U); + ASSERT_TARGET_ENTRY(target_entries[0], R::target::string::str1, Res_value::TYPE_REFERENCE, + 0x0104000a); // -> android:string/ok + ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay::string::str3); + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(overlay_entries.size(), 1U); + ASSERT_OVERLAY_ENTRY(overlay_entries[0], R::overlay::string::str3, R::target::string::str3); } -// Overlays that are pre-installed or are signed with the same signature as the target can overlay -// packages that have not defined overlayable resources. -TEST(IdmapOverlayableTests, CreateIdmapFromApkAssetsDefaultPolicies) { - std::unique_ptr<const Idmap> idmap; - std::string target_apk_path = GetTestDataPath() + "/target/target-no-overlayable.apk"; - std::string overlay_apk_path = - GetTestDataPath() + "/system-overlay-invalid/system-overlay-invalid.apk"; - - auto CheckEntries = [&]() -> void { - const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); - ASSERT_EQ(dataBlocks.size(), 1U); - - const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; - ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fU); - ASSERT_EQ(data->GetHeader()->GetTypeCount(), 1U); - - const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); - ASSERT_EQ(types.size(), 1U); - - ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02U); - ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01U); - ASSERT_EQ(types[0]->GetEntryCount(), 9U); - ASSERT_EQ(types[0]->GetEntryOffset(), 3U); - ASSERT_EQ(types[0]->GetEntry(0), 0x0000U); // string/not_overlayable - ASSERT_EQ(types[0]->GetEntry(1), 0x0001U); // string/policy_odm - ASSERT_EQ(types[0]->GetEntry(2), 0x0002U); // string/policy_oem - ASSERT_EQ(types[0]->GetEntry(3), 0x0003U); // string/other - ASSERT_EQ(types[0]->GetEntry(4), 0x0004U); // string/policy_product - ASSERT_EQ(types[0]->GetEntry(5), 0x0005U); // string/policy_public - ASSERT_EQ(types[0]->GetEntry(6), 0x0006U); // string/policy_signature - ASSERT_EQ(types[0]->GetEntry(7), 0x0007U); // string/policy_system - ASSERT_EQ(types[0]->GetEntry(8), 0x0008U); // string/policy_system_vendor - }; - - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_SIGNATURE, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - CheckEntries(); - - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_PRODUCT_PARTITION, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - CheckEntries(); - - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_SYSTEM_PARTITION, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - CheckEntries(); - - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_VENDOR_PARTITION, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - CheckEntries(); - - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_ODM_PARTITION, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - CheckEntries(); - - CreateIdmap(target_apk_path, overlay_apk_path, PolicyFlags::POLICY_OEM_PARTITION, - /* enforce_overlayable */ true, &idmap); - ASSERT_THAT(idmap, NotNull()); - CheckEntries(); +TEST(IdmapTests, CreateIdmapDataInlineResources) { + OverlayManifestInfo info{}; + info.target_package = "test.target"; + info.target_name = "TestResources"; + info.resource_mapping = 0x7f030002; // xml/overlays_inline + auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", info, + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage(); + auto& data = *idmap_data; + + constexpr size_t overlay_string_pool_size = 8U; + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 2U); + ASSERT_TARGET_ENTRY(target_entries[0], R::target::integer::int1, Res_value::TYPE_INT_DEC, + 73U); // -> 73 + ASSERT_TARGET_ENTRY(target_entries[1], R::target::string::str1, Res_value::TYPE_STRING, + overlay_string_pool_size + 0U); // -> "Hello World" + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(overlay_entries.size(), 0U); } TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) { @@ -480,9 +368,8 @@ TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - const auto result = - Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, - PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true); + const auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); ASSERT_FALSE(result); } @@ -497,8 +384,7 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - auto result = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, - PolicyFlags::POLICY_PUBLIC, + auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, /* enforce_overlayable */ true); ASSERT_TRUE(result); const auto idmap = std::move(*result); @@ -509,7 +395,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); - ASSERT_TRUE(header->IsUpToDate()); + ASSERT_TRUE(header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // magic: bytes (0x0, 0x03) std::string bad_magic_string(stream.str()); @@ -522,7 +409,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_magic_stream); ASSERT_THAT(bad_magic_header, NotNull()); ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic()); - ASSERT_FALSE(bad_magic_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // version: bytes (0x4, 0x07) std::string bad_version_string(stream.str()); @@ -535,7 +423,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_version_stream); ASSERT_THAT(bad_version_header, NotNull()); ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion()); - ASSERT_FALSE(bad_version_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // target crc: bytes (0x8, 0xb) std::string bad_target_crc_string(stream.str()); @@ -548,7 +437,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_target_crc_stream); ASSERT_THAT(bad_target_crc_header, NotNull()); ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc()); - ASSERT_FALSE(bad_target_crc_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // overlay crc: bytes (0xc, 0xf) std::string bad_overlay_crc_string(stream.str()); @@ -561,27 +451,55 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_overlay_crc_stream); ASSERT_THAT(bad_overlay_crc_header, NotNull()); ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc()); - ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate()); - - // target path: bytes (0x10, 0x10f) + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + + // fulfilled policy: bytes (0x10, 0x13) + std::string bad_policy_string(stream.str()); + bad_policy_string[0x10] = '.'; + bad_policy_string[0x11] = '.'; + bad_policy_string[0x12] = '.'; + bad_policy_string[0x13] = '.'; + std::stringstream bad_policy_stream(bad_policy_string); + std::unique_ptr<const IdmapHeader> bad_policy_header = + IdmapHeader::FromBinaryStream(bad_policy_stream); + ASSERT_THAT(bad_policy_header, NotNull()); + ASSERT_NE(header->GetFulfilledPolicies(), bad_policy_header->GetFulfilledPolicies()); + ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + + // enforce overlayable: bytes (0x14) + std::string bad_enforce_string(stream.str()); + bad_enforce_string[0x14] = '\0'; + std::stringstream bad_enforce_stream(bad_enforce_string); + std::unique_ptr<const IdmapHeader> bad_enforce_header = + IdmapHeader::FromBinaryStream(bad_enforce_stream); + ASSERT_THAT(bad_enforce_header, NotNull()); + ASSERT_NE(header->GetEnforceOverlayable(), bad_enforce_header->GetEnforceOverlayable()); + ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + + // target path: bytes (0x15, 0x114) std::string bad_target_path_string(stream.str()); - bad_target_path_string[0x10] = '\0'; + bad_target_path_string[0x15] = '\0'; std::stringstream bad_target_path_stream(bad_target_path_string); std::unique_ptr<const IdmapHeader> bad_target_path_header = IdmapHeader::FromBinaryStream(bad_target_path_stream); ASSERT_THAT(bad_target_path_header, NotNull()); ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath()); - ASSERT_FALSE(bad_target_path_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); - // overlay path: bytes (0x110, 0x20f) + // overlay path: bytes (0x115, 0x214) std::string bad_overlay_path_string(stream.str()); - bad_overlay_path_string[0x110] = '\0'; + bad_overlay_path_string[0x115] = '\0'; std::stringstream bad_overlay_path_stream(bad_overlay_path_string); std::unique_ptr<const IdmapHeader> bad_overlay_path_header = IdmapHeader::FromBinaryStream(bad_overlay_path_stream); ASSERT_THAT(bad_overlay_path_header, NotNull()); ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath()); - ASSERT_FALSE(bad_overlay_path_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); } class TestVisitor : public Visitor { @@ -605,10 +523,6 @@ class TestVisitor : public Visitor { stream_ << "TestVisitor::visit(IdmapData::Header)" << std::endl; } - void visit(const IdmapData::TypeEntry& idmap ATTRIBUTE_UNUSED) override { - stream_ << "TestVisitor::visit(IdmapData::TypeEntry)" << std::endl; - } - private: std::ostream& stream_; }; @@ -625,12 +539,10 @@ TEST(IdmapTests, TestVisitor) { (*idmap)->accept(&visitor); ASSERT_EQ(test_stream.str(), - "TestVisitor::visit(Idmap)\n" "TestVisitor::visit(IdmapHeader)\n" - "TestVisitor::visit(IdmapData)\n" + "TestVisitor::visit(Idmap)\n" "TestVisitor::visit(IdmapData::Header)\n" - "TestVisitor::visit(IdmapData::TypeEntry)\n" - "TestVisitor::visit(IdmapData::TypeEntry)\n"); + "TestVisitor::visit(IdmapData)\n"); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/PoliciesTests.cpp b/cmds/idmap2/tests/PoliciesTests.cpp index eca74045f428..1b2775939886 100644 --- a/cmds/idmap2/tests/PoliciesTests.cpp +++ b/cmds/idmap2/tests/PoliciesTests.cpp @@ -17,76 +17,96 @@ #include <string> #include "TestHelpers.h" +#include "androidfw/ResourceTypes.h" #include "gtest/gtest.h" -#include "idmap2/Policies.h" +#include "idmap2/PolicyUtils.h" -using android::idmap2::PolicyBitmask; -using android::idmap2::PolicyFlags; +using android::idmap2::utils::BitmaskToPolicies; +using android::idmap2::utils::PoliciesToBitmaskResult; + +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; namespace android::idmap2 { -TEST(PoliciesTests, PoliciesToBitmasks) { - const auto bitmask1 = PoliciesToBitmask({"system"}); +TEST(PoliciesTests, PoliciesToBitmaskResults) { + const auto bitmask1 = PoliciesToBitmaskResult({"system"}); ASSERT_TRUE(bitmask1); - ASSERT_EQ(*bitmask1, PolicyFlags::POLICY_SYSTEM_PARTITION); + ASSERT_EQ(*bitmask1, PolicyFlags::SYSTEM_PARTITION); - const auto bitmask2 = PoliciesToBitmask({"system", "vendor"}); + const auto bitmask2 = PoliciesToBitmaskResult({"system", "vendor"}); ASSERT_TRUE(bitmask2); - ASSERT_EQ(*bitmask2, PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION); + ASSERT_EQ(*bitmask2, PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION); - const auto bitmask3 = PoliciesToBitmask({"vendor", "system"}); + const auto bitmask3 = PoliciesToBitmaskResult({"vendor", "system"}); ASSERT_TRUE(bitmask3); - ASSERT_EQ(*bitmask3, PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION); + ASSERT_EQ(*bitmask3, PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION); - const auto bitmask4 = PoliciesToBitmask({"odm", "oem", "public", "product", "system", "vendor"}); + const auto bitmask4 = + PoliciesToBitmaskResult({"odm", "oem", "public", "product", "system", "vendor"}); ASSERT_TRUE(bitmask4); - ASSERT_EQ(*bitmask4, PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION | - PolicyFlags::POLICY_PUBLIC | PolicyFlags::POLICY_PRODUCT_PARTITION | - PolicyFlags::POLICY_SYSTEM_PARTITION | - PolicyFlags::POLICY_VENDOR_PARTITION); + ASSERT_EQ(*bitmask4, PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | + PolicyFlags::PUBLIC | PolicyFlags::PRODUCT_PARTITION | + PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION); - const auto bitmask5 = PoliciesToBitmask({"system", "system", "system"}); + const auto bitmask5 = PoliciesToBitmaskResult({"system", "system", "system"}); ASSERT_TRUE(bitmask5); - ASSERT_EQ(*bitmask5, PolicyFlags::POLICY_SYSTEM_PARTITION); + ASSERT_EQ(*bitmask5, PolicyFlags::SYSTEM_PARTITION); - const auto bitmask6 = PoliciesToBitmask({""}); + const auto bitmask6 = PoliciesToBitmaskResult({""}); ASSERT_FALSE(bitmask6); - const auto bitmask7 = PoliciesToBitmask({"foo"}); + const auto bitmask7 = PoliciesToBitmaskResult({"foo"}); ASSERT_FALSE(bitmask7); - const auto bitmask8 = PoliciesToBitmask({"system", "foo"}); + const auto bitmask8 = PoliciesToBitmaskResult({"system", "foo"}); ASSERT_FALSE(bitmask8); - const auto bitmask9 = PoliciesToBitmask({"system", ""}); + const auto bitmask9 = PoliciesToBitmaskResult({"system", ""}); ASSERT_FALSE(bitmask9); - const auto bitmask10 = PoliciesToBitmask({"system "}); + const auto bitmask10 = PoliciesToBitmaskResult({"system "}); ASSERT_FALSE(bitmask10); + + const auto bitmask11 = PoliciesToBitmaskResult({"signature"}); + ASSERT_TRUE(bitmask11); + ASSERT_EQ(*bitmask11, PolicyFlags::SIGNATURE); + + const auto bitmask12 = PoliciesToBitmaskResult({"actor"}); + ASSERT_TRUE(bitmask12); + ASSERT_EQ(*bitmask12, PolicyFlags::ACTOR_SIGNATURE); } TEST(PoliciesTests, BitmaskToPolicies) { - const auto policies1 = BitmaskToPolicies(PolicyFlags::POLICY_PUBLIC); + const auto policies1 = BitmaskToPolicies(PolicyFlags::PUBLIC); ASSERT_EQ(1, policies1.size()); ASSERT_EQ(policies1[0], "public"); - const auto policies2 = BitmaskToPolicies(PolicyFlags::POLICY_SYSTEM_PARTITION | - PolicyFlags::POLICY_VENDOR_PARTITION); + const auto policies2 = + BitmaskToPolicies(PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION); ASSERT_EQ(2, policies2.size()); ASSERT_EQ(policies2[0], "system"); ASSERT_EQ(policies2[1], "vendor"); - const auto policies3 = BitmaskToPolicies( - PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION | - PolicyFlags::POLICY_PUBLIC | PolicyFlags::POLICY_PRODUCT_PARTITION | - PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION); + const auto policies3 = + BitmaskToPolicies(PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | + PolicyFlags::PUBLIC | PolicyFlags::PRODUCT_PARTITION | + PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION); ASSERT_EQ(2, policies2.size()); ASSERT_EQ(policies3[0], "odm"); ASSERT_EQ(policies3[1], "oem"); - ASSERT_EQ(policies3[2], "public"); - ASSERT_EQ(policies3[3], "product"); + ASSERT_EQ(policies3[2], "product"); + ASSERT_EQ(policies3[3], "public"); ASSERT_EQ(policies3[4], "system"); ASSERT_EQ(policies3[5], "vendor"); + + const auto policies4 = BitmaskToPolicies(PolicyFlags::SIGNATURE); + ASSERT_EQ(1, policies4.size()); + ASSERT_EQ(policies4[0], "signature"); + + const auto policies5 = BitmaskToPolicies(PolicyFlags::ACTOR_SIGNATURE); + ASSERT_EQ(1, policies5.size()); + ASSERT_EQ(policies5[0], "actor"); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp index c41250457678..9a10079772bf 100644 --- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp @@ -18,19 +18,22 @@ #include <sstream> #include <string> +#include "R.h" #include "TestHelpers.h" #include "androidfw/ApkAssets.h" #include "androidfw/Idmap.h" +#include "androidfw/ResourceTypes.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "idmap2/Idmap.h" -#include "idmap2/Policies.h" #include "idmap2/PrettyPrintVisitor.h" using ::testing::NotNull; using android::ApkAssets; -using android::idmap2::PolicyBitmask; + +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; namespace android::idmap2 { @@ -43,9 +46,8 @@ TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - const auto idmap = - Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, - PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true); + const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); ASSERT_TRUE(idmap); std::stringstream stream; @@ -54,7 +56,8 @@ TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) { ASSERT_NE(stream.str().find("target apk path : "), std::string::npos); ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos); - ASSERT_NE(stream.str().find("0x7f010000 -> 0x7f010000 integer/int1\n"), std::string::npos); + ASSERT_NE(stream.str().find(R::target::integer::literal::int1 + " -> 0x7f010000 integer/int1\n"), + std::string::npos); } TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) { diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h new file mode 100644 index 000000000000..aed263a49aa3 --- /dev/null +++ b/cmds/idmap2/tests/R.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 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 IDMAP2_TESTS_R_H +#define IDMAP2_TESTS_R_H + +#include <idmap2/ResourceUtils.h> + +namespace android::idmap2 { + +static std::string hexify(ResourceId id) { + std::stringstream stream; + stream << std::hex << static_cast<uint32_t>(id); + return stream.str(); +} + +// clang-format off +namespace R::target { + namespace integer { + constexpr ResourceId int1 = 0x7f010000; + + namespace literal { + inline const std::string int1 = hexify(R::target::integer::int1); + } + } + + namespace string { + constexpr ResourceId not_overlayable = 0x7f020003; + constexpr ResourceId other = 0x7f020004; + constexpr ResourceId policy_actor = 0x7f020005; + constexpr ResourceId policy_odm = 0x7f020006; + constexpr ResourceId policy_oem = 0x7f020007; + constexpr ResourceId policy_product = 0x7f020008; + constexpr ResourceId policy_public = 0x7f020009; + constexpr ResourceId policy_signature = 0x7f02000a; + constexpr ResourceId policy_system = 0x7f02000b; + constexpr ResourceId policy_system_vendor = 0x7f02000c; + constexpr ResourceId str1 = 0x7f02000d; + constexpr ResourceId str3 = 0x7f02000f; + constexpr ResourceId str4 = 0x7f020010; + + namespace literal { + inline const std::string str1 = hexify(R::target::string::str1); + inline const std::string str3 = hexify(R::target::string::str3); + inline const std::string str4 = hexify(R::target::string::str4); + } + } +} + +namespace R::overlay { + namespace integer { + constexpr ResourceId int1 = 0x7f010000; + } + namespace string { + constexpr ResourceId str1 = 0x7f020000; + constexpr ResourceId str3 = 0x7f020001; + constexpr ResourceId str4 = 0x7f020002; + } +} + +namespace R::overlay_shared { + namespace integer { + constexpr ResourceId int1 = 0x00010000; + } + namespace string { + constexpr ResourceId str1 = 0x00020000; + constexpr ResourceId str3 = 0x00020001; + constexpr ResourceId str4 = 0x00020002; + } +} + +namespace R::system_overlay::string { + constexpr ResourceId policy_public = 0x7f010000; + constexpr ResourceId policy_system = 0x7f010001; + constexpr ResourceId policy_system_vendor = 0x7f010002; +} + +namespace R::system_overlay_invalid::string { + constexpr ResourceId not_overlayable = 0x7f010000; + constexpr ResourceId other = 0x7f010001; + constexpr ResourceId policy_actor = 0x7f010002; + constexpr ResourceId policy_odm = 0x7f010003; + constexpr ResourceId policy_oem = 0x7f010004; + constexpr ResourceId policy_product = 0x7f010005; + constexpr ResourceId policy_public = 0x7f010006; + constexpr ResourceId policy_signature = 0x7f010007; + constexpr ResourceId policy_system = 0x7f010008; + constexpr ResourceId policy_system_vendor = 0x7f010009; +}; +// clang-format on + +} // namespace android::idmap2 + +#endif // IDMAP2_TESTS_R_H diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp index 26951763cd66..b268d5add141 100644 --- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -16,20 +16,38 @@ #include <cstdio> // fclose #include <memory> +#include <regex> #include <sstream> #include <string> +#include "TestConstants.h" #include "TestHelpers.h" +#include "android-base/stringprintf.h" +#include "androidfw/ResourceTypes.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "idmap2/Idmap.h" #include "idmap2/RawPrintVisitor.h" +using android::base::StringPrintf; using ::testing::NotNull; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace android::idmap2 { +#define ASSERT_CONTAINS_REGEX(pattern, str) \ + do { \ + ASSERT_TRUE(std::regex_search(str, std::regex(pattern))) \ + << "pattern '" << pattern << "' not found in\n--------\n" \ + << str << "--------"; \ + } while (0) + +#define ADDRESS "[0-9a-f]{8}: " + TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { + fclose(stderr); // silence expected warnings + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); ASSERT_THAT(target_apk, NotNull()); @@ -38,21 +56,36 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); ASSERT_THAT(overlay_apk, NotNull()); - const auto idmap = - Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, - PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true); + const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); ASSERT_TRUE(idmap); std::stringstream stream; RawPrintVisitor visitor(stream); (*idmap)->accept(&visitor); - ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000008: 76a20829 target crc\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000000c: 8635c2ed overlay crc\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f010000 -> 0x7f010000 integer/int1\n"), - std::string::npos); + ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 version\n", stream.str()); + ASSERT_CONTAINS_REGEX( + StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING), + stream.str()); + ASSERT_CONTAINS_REGEX( + StringPrintf(ADDRESS "%s overlay crc\n", android::idmap2::TestConstants::OVERLAY_CRC_STRING), + stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000001 fulfilled policies: public\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 01 enforce overlayable\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000008 string pool index offset\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "000000b4 string pool byte length\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 07 type: reference \\(dynamic\\)\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 value: integer/int1\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1\n", stream.str()); } TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { @@ -68,11 +101,23 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { RawPrintVisitor visitor(stream); (*idmap)->accept(&visitor); - ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000008: 00001234 target crc\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000000c: 00005678 overlay crc\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f020000 -> 0x7f020000\n"), std::string::npos); + ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00001234 target crc\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00005678 overlay crc\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000011 fulfilled policies: public|signature\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 01 enforce overlayable\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000003 target entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000003 overlay entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 string pool index offset\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 string pool byte length\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 01 type: reference\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 value\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str()); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp new file mode 100644 index 000000000000..de039f440e33 --- /dev/null +++ b/cmds/idmap2/tests/ResourceMappingTests.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> // fclose +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "R.h" +#include "TestHelpers.h" +#include "androidfw/ResourceTypes.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "idmap2/LogInfo.h" +#include "idmap2/ResourceMapping.h" + +using android::Res_value; +using android::idmap2::utils::ExtractOverlayManifestInfo; + +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + +namespace android::idmap2 { + +#define ASSERT_RESULT(r) \ + do { \ + auto result = r; \ + ASSERT_TRUE(result) << result.GetErrorMessage(); \ + } while (0) + +Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path, + const android::StringPiece& local_overlay_apk_path, + const OverlayManifestInfo& overlay_info, + const PolicyBitmask& fulfilled_policies, + bool enforce_overlayable) { + const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data()); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + return Error(R"(Failed to load target apk "%s")", target_apk_path.data()); + } + + const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data()); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data()); + } + + LogInfo log_info; + return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, fulfilled_policies, + enforce_overlayable, log_info); +} + +Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path, + const android::StringPiece& local_overlay_apk_path, + const PolicyBitmask& fulfilled_policies, + bool enforce_overlayable) { + auto overlay_info = ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path.data()); + if (!overlay_info) { + return overlay_info.GetError(); + } + return TestGetResourceMapping(local_target_apk_path, local_overlay_apk_path, *overlay_info, + fulfilled_policies, enforce_overlayable); +} + +Result<Unit> MappingExists(const ResourceMapping& mapping, const ResourceId& target_resource, + const uint8_t type, const uint32_t value, bool rewrite) { + auto target_map = mapping.GetTargetToOverlayMap(); + auto entry_map = target_map.find(target_resource); + if (entry_map == target_map.end()) { + return Error("Failed to find mapping for target resource"); + } + + if (entry_map->second.data_type != type) { + return Error(R"(Expected type: "0x%02x" Actual type: "0x%02x")", type, + entry_map->second.data_type); + } + + if (entry_map->second.data_value != value) { + return Error(R"(Expected value: "0x%08x" Actual value: "0x%08x")", type, + entry_map->second.data_value); + } + + auto overlay_map = mapping.GetOverlayToTargetMap(); + auto overlay_iter = overlay_map.find(entry_map->second.data_value); + if ((overlay_iter != overlay_map.end()) != rewrite) { + return Error(R"(Expected rewriting: "%s")", rewrite ? "true" : "false"); + } + + return Result<Unit>({}); +} + +TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) { + OverlayManifestInfo info{}; + info.target_package = "test.target"; + info.target_name = "TestResources"; + info.resource_mapping = 0U; // no xml + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U); + ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_REFERENCE, + R::overlay::integer::int1, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, + R::overlay::string::str1, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_REFERENCE, + R::overlay::string::str3, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str4, Res_value::TYPE_REFERENCE, + R::overlay::string::str4, false /* rewrite */)); +} + +TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) { + OverlayManifestInfo info{}; + info.target_package = "test.target"; + info.target_name = "TestResources"; + info.resource_mapping = 0x7f030003; // xml/overlays_swap + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U); + ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay::string::str4, true /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay::string::str1, true /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str4, Res_value::TYPE_DYNAMIC_REFERENCE, + R::overlay::string::str3, true /* rewrite */)); +} + +TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) { + OverlayManifestInfo info{}; + info.target_package = "test.target"; + info.target_name = "TestResources"; + info.resource_mapping = 0x7f030001; // xml/overlays_different_packages + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U); + ASSERT_EQ(res.GetOverlayToTargetMap().size(), 1U); + ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x0104000a, + false /* rewrite */)); // -> android:string/ok + ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_DYNAMIC_REFERENCE, + 0x7f020001, true /* rewrite */)); +} + +TEST(ResourceMappingTests, InlineResources) { + OverlayManifestInfo info{}; + info.target_package = "test.target"; + info.target_name = "TestResources"; + info.resource_mapping = 0x7f030002; // xml/overlays_inline + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info, + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + constexpr size_t overlay_string_pool_size = 8U; + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U); + ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U); + ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_STRING, + overlay_string_pool_size + 0U, + false /* rewrite */)); // -> "Hello World" + ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 73U, + false /* rewrite */)); // -> 73 +} + +TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) { + auto resources = + TestGetResourceMapping("/target/target.apk", "/system-overlay/system-overlay.apk", + PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE, + R::system_overlay::string::policy_public, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE, + R::system_overlay::string::policy_system, false /* rewrite */)); + ASSERT_RESULT( + MappingExists(res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE, + R::system_overlay::string::policy_system_vendor, false /* rewrite */)); +} + +// Resources that are not declared as overlayable and resources that a protected by policies the +// overlay does not fulfill must not map to overlay resources. +TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) { + auto resources = TestGetResourceMapping("/target/target.apk", + "/system-overlay-invalid/system-overlay-invalid.apk", + PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_public, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system, + false /* rewrite */)); + ASSERT_RESULT( + MappingExists(res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */)); +} + +// Resources that are not declared as overlayable and resources that a protected by policies the +// overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned +// off. +TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) { + auto resources = TestGetResourceMapping("/target/target.apk", + "/system-overlay-invalid/system-overlay-invalid.apk", + PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 10U); + ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::not_overlayable, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::other, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::other, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_actor, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_odm, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_oem, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_product, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_public, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_signature, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system, + false /* rewrite */)); + ASSERT_RESULT( + MappingExists(res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */)); +} + +// Overlays that do not target an <overlayable> tag can overlay resources defined within any +// <overlayable> tag. +TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) { + auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-no-name.apk", + PolicyFlags::PUBLIC, + /* enforce_overlayable */ false); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U); + ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_REFERENCE, + R::overlay::integer::int1, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, + R::overlay::string::str1, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str3, Res_value::TYPE_REFERENCE, + R::overlay::string::str3, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::str4, Res_value::TYPE_REFERENCE, + R::overlay::string::str4, false /* rewrite */)); +} + +// Overlays that are neither pre-installed nor signed with the same signature as the target cannot +// overlay packages that have not defined overlayable resources. +TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) { + auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", + "/overlay/overlay-no-name.apk", PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U); +} + +// Overlays that are pre-installed or are signed with the same signature as the target can overlay +// packages that have not defined overlayable resources. +TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) { + auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void { + auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", + "/system-overlay-invalid/system-overlay-invalid.apk", + fulfilled_policies, + /* enforce_overlayable */ true); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 10U); + ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::not_overlayable, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::other, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::other, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_actor, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_odm, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_oem, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_product, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_public, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_signature, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system, + false /* rewrite */)); + ASSERT_RESULT(MappingExists( + res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */)); + }; + + CheckEntries(PolicyFlags::SIGNATURE); + CheckEntries(PolicyFlags::PRODUCT_PARTITION); + CheckEntries(PolicyFlags::SYSTEM_PARTITION); + CheckEntries(PolicyFlags::VENDOR_PARTITION); + CheckEntries(PolicyFlags::ODM_PARTITION); + CheckEntries(PolicyFlags::OEM_PARTITION); +} + +} // namespace android::idmap2 diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h new file mode 100644 index 000000000000..6bc924e5ac3c --- /dev/null +++ b/cmds/idmap2/tests/TestConstants.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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 IDMAP2_TESTS_TESTCONSTANTS_H +#define IDMAP2_TESTS_TESTCONSTANTS_H + +namespace android::idmap2::TestConstants { + +constexpr const auto TARGET_CRC = 0x41c60c8c; +constexpr const auto TARGET_CRC_STRING = "41c60c8c"; + +constexpr const auto OVERLAY_CRC = 0xc054fb26; +constexpr const auto OVERLAY_CRC_STRING = "c054fb26"; + +} // namespace android::idmap2::TestConstants + +#endif // IDMAP2_TESTS_TESTCONSTANTS_H diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h index adea3293534d..b599dcb0069a 100644 --- a/cmds/idmap2/tests/TestHelpers.h +++ b/cmds/idmap2/tests/TestHelpers.h @@ -30,7 +30,7 @@ const unsigned char idmap_raw_data[] = { 0x49, 0x44, 0x4d, 0x50, // 0x4: version - 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, // 0x8: target crc 0x34, 0x12, 0x00, 0x00, @@ -38,8 +38,14 @@ const unsigned char idmap_raw_data[] = { // 0xc: overlay crc 0x78, 0x56, 0x00, 0x00, - // 0x10: target path "target.apk" - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x10: fulfilled policies + 0x11, 0x00, 0x00, 0x00, + + // 0x14: enforce overlayable + 0x01, + + // 0x15: target path "targetX.apk" + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -56,8 +62,8 @@ const unsigned char idmap_raw_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x110: overlay path "overlay.apk" - 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x115: overlay path "overlayX.apk" + 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -74,56 +80,77 @@ const unsigned char idmap_raw_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // DATA HEADER - // 0x210: target package id - 0x7f, 0x00, + // 0x215: debug string + // string length, including terminating null + 0x08, 0x00, 0x00, 0x00, - // 0x212: types count - 0x02, 0x00, + // string contents "debug\0\0\0" (padded to word alignment) + 0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00, - // DATA BLOCK - // 0x214: target type - 0x02, 0x00, + // DATA HEADER + // 0x221: target_package_id + 0x7f, - // 0x216: overlay type - 0x02, 0x00, + // 0x222: overlay_package_id + 0x7f, - // 0x218: entry count - 0x01, 0x00, + // 0x223: target_entry_count + 0x03, 0x00, 0x00, 0x00, - // 0x21a: entry offset - 0x00, 0x00, + // 0x227: overlay_entry_count + 0x03, 0x00, 0x00, 0x00, - // 0x21c: entries + // 0x22b: string_pool_offset 0x00, 0x00, 0x00, 0x00, - // DATA BLOCK - // 0x220: target type - 0x03, 0x00, + // 0x22f: string_pool_byte_length + 0x00, 0x00, 0x00, 0x00, - // 0x222: overlay type - 0x03, 0x00, + // TARGET ENTRIES + // 0x233: 0x7f020000 + 0x00, 0x00, 0x02, 0x7f, - // 0x224: entry count - 0x03, 0x00, + // 0x237: TYPE_REFERENCE + 0x01, - // 0x226: entry offset - 0x03, 0x00, + // 0x238: 0x7f020000 + 0x00, 0x00, 0x02, 0x7f, - // 0x228, 0x22c, 0x230: entries - 0x00, 0x00, 0x00, 0x00, + // 0x23c: 0x7f030000 + 0x00, 0x00, 0x03, 0x7f, + + // 0x240: TYPE_REFERENCE + 0x01, + + // 0x241: 0x7f030000 + 0x00, 0x00, 0x03, 0x7f, + + // 0x245: 0x7f030002 + 0x02, 0x00, 0x03, 0x7f, + + // 0x249: TYPE_REFERENCE + 0x01, + + // 0x24a: 0x7f030001 + 0x01, 0x00, 0x03, 0x7f, + + // OVERLAY ENTRIES + // 0x24e: 0x7f020000 -> 0x7f020000 + 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f, - 0xff, 0xff, 0xff, 0xff, + // 0x256: 0x7f030000 -> 0x7f030000 + 0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f, - 0x01, 0x00, 0x00, 0x00}; + // 0x25e: 0x7f030001 -> 0x7f030002 + 0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f}; -const unsigned int idmap_raw_data_len = 565; +const unsigned int idmap_raw_data_len = 0x266; std::string GetTestDataPath(); class Idmap2Tests : public testing::Test { protected: - virtual void SetUp() { + void SetUp() override { #ifdef __ANDROID__ tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX"; #else @@ -136,7 +163,7 @@ class Idmap2Tests : public testing::Test { idmap_path_ = tmp_dir_path_ + "/a.idmap"; } - virtual void TearDown() { + void TearDown() override { EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0) << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno); } diff --git a/cmds/idmap2/tests/XmlParserTests.cpp b/cmds/idmap2/tests/XmlParserTests.cpp new file mode 100644 index 000000000000..1a7eaca4d67b --- /dev/null +++ b/cmds/idmap2/tests/XmlParserTests.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2019 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 <cstdio> // fclose +#include <memory> +#include <string> + +#include "TestHelpers.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "idmap2/XmlParser.h" +#include "idmap2/ZipFile.h" + +namespace android::idmap2 { + +Result<std::unique_ptr<const XmlParser>> CreateTestParser(const std::string& test_file) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + if (zip == nullptr) { + return Error("Failed to open zip file"); + } + + auto data = zip->Uncompress(test_file); + if (data == nullptr) { + return Error("Failed to open xml file"); + } + + return XmlParser::Create(data->buf, data->size, /* copy_data */ true); +} + +TEST(XmlParserTests, Create) { + auto xml = CreateTestParser("AndroidManifest.xml"); + ASSERT_TRUE(xml) << xml.GetErrorMessage(); + + fclose(stderr); // silence expected warnings from libandroidfw + const char* not_xml = "foo"; + auto fail = XmlParser::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml)); + ASSERT_FALSE(fail); +} + +TEST(XmlParserTests, NextChild) { + auto xml = CreateTestParser("res/xml/test.xml"); + ASSERT_TRUE(xml) << xml.GetErrorMessage(); + + auto root_iter = (*xml)->tree_iterator(); + ASSERT_EQ(root_iter->event(), XmlParser::Event::START_TAG); + ASSERT_EQ(root_iter->name(), "a"); + + auto a_iter = root_iter.begin(); + ASSERT_EQ(a_iter->event(), XmlParser::Event::START_TAG); + ASSERT_EQ(a_iter->name(), "b"); + + auto c_iter = a_iter.begin(); + ASSERT_EQ(c_iter->event(), XmlParser::Event::START_TAG); + ASSERT_EQ(c_iter->name(), "c"); + + ++c_iter; + ASSERT_EQ(c_iter->event(), XmlParser::Event::END_TAG); + ASSERT_EQ(c_iter, a_iter.end()); + + ++a_iter; + ASSERT_EQ(a_iter->event(), XmlParser::Event::START_TAG); + ASSERT_EQ(a_iter->name(), "d"); + + // Skip the <e> tag. + ++a_iter; + ASSERT_EQ(a_iter->event(), XmlParser::Event::END_TAG); + ASSERT_EQ(a_iter, root_iter.end()); +} + +TEST(XmlParserTests, AttributeValues) { + auto xml = CreateTestParser("res/xml/test.xml"); + ASSERT_TRUE(xml) << xml.GetErrorMessage(); + + // Start at the <a> tag. + auto root_iter = (*xml)->tree_iterator(); + + // Start at the <b> tag. + auto a_iter = root_iter.begin(); + auto attribute_str = a_iter->GetAttributeStringValue("type_string"); + ASSERT_TRUE(attribute_str); + ASSERT_EQ(*attribute_str, "fortytwo"); + + auto attribute_value = a_iter->GetAttributeValue("type_int_dec"); + ASSERT_TRUE(attribute_value); + ASSERT_EQ(attribute_value->data, 42); + + attribute_value = a_iter->GetAttributeValue("type_int_hex"); + ASSERT_TRUE(attribute_value); + ASSERT_EQ(attribute_value->data, 42); + + attribute_value = a_iter->GetAttributeValue("type_int_boolean"); + ASSERT_TRUE(attribute_value); + ASSERT_EQ(attribute_value->data, 0xffffffff); +} + +TEST(XmlParserTests, IteratorEquality) { + auto xml = CreateTestParser("res/xml/test.xml"); + ASSERT_TRUE(xml) << xml.GetErrorMessage(); + + // Start at the <a> tag. + auto root_iter_1 = (*xml)->tree_iterator(); + auto root_iter_2 = (*xml)->tree_iterator(); + ASSERT_EQ(root_iter_1, root_iter_2); + ASSERT_EQ(*root_iter_1, *root_iter_2); + + // Start at the <b> tag. + auto a_iter_1 = root_iter_1.begin(); + auto a_iter_2 = root_iter_2.begin(); + ASSERT_NE(a_iter_1, root_iter_1.end()); + ASSERT_NE(a_iter_2, root_iter_2.end()); + ASSERT_EQ(a_iter_1, a_iter_2); + ASSERT_EQ(*a_iter_1, *a_iter_2); + + // Move to the <d> tag. + ++a_iter_1; + ++a_iter_2; + ASSERT_NE(a_iter_1, root_iter_1.end()); + ASSERT_NE(a_iter_2, root_iter_2.end()); + ASSERT_EQ(a_iter_1, a_iter_2); + ASSERT_EQ(*a_iter_1, *a_iter_2); + + // Move to the end of the <a> tag. + ++a_iter_1; + ++a_iter_2; + ASSERT_EQ(a_iter_1, root_iter_1.end()); + ASSERT_EQ(a_iter_2, root_iter_2.end()); + ASSERT_EQ(a_iter_1, a_iter_2); + ASSERT_EQ(*a_iter_1, *a_iter_2); +} + +TEST(XmlParserTests, Backtracking) { + auto xml = CreateTestParser("res/xml/test.xml"); + ASSERT_TRUE(xml) << xml.GetErrorMessage(); + + // Start at the <a> tag. + auto root_iter_1 = (*xml)->tree_iterator(); + + // Start at the <b> tag. + auto a_iter_1 = root_iter_1.begin(); + + // Start a second iterator at the <a> tag. + auto root_iter_2 = root_iter_1; + ASSERT_EQ(root_iter_1, root_iter_2); + ASSERT_EQ(*root_iter_1, *root_iter_2); + + // Move the first iterator to the end of the <a> tag. + auto root_iter_end_1 = root_iter_1.end(); + ++root_iter_1; + ASSERT_NE(root_iter_1, root_iter_2); + ASSERT_NE(*root_iter_1, *root_iter_2); + + // Move to the <d> tag. + ++a_iter_1; + ASSERT_NE(a_iter_1, root_iter_end_1); + + // Move to the end of the <a> tag. + ++a_iter_1; + ASSERT_EQ(a_iter_1, root_iter_end_1); +} + +} // namespace android::idmap2 diff --git a/cmds/idmap2/tests/XmlTests.cpp b/cmds/idmap2/tests/XmlTests.cpp deleted file mode 100644 index df63211a9209..000000000000 --- a/cmds/idmap2/tests/XmlTests.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <cstdio> // fclose - -#include "TestHelpers.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "idmap2/Xml.h" -#include "idmap2/ZipFile.h" - -using ::testing::IsNull; -using ::testing::NotNull; - -namespace android::idmap2 { - -TEST(XmlTests, Create) { - auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); - ASSERT_THAT(zip, NotNull()); - - auto data = zip->Uncompress("AndroidManifest.xml"); - ASSERT_THAT(data, NotNull()); - - auto xml = Xml::Create(data->buf, data->size); - ASSERT_THAT(xml, NotNull()); - - fclose(stderr); // silence expected warnings from libandroidfw - const char* not_xml = "foo"; - auto fail = Xml::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml)); - ASSERT_THAT(fail, IsNull()); -} - -TEST(XmlTests, FindTag) { - auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); - ASSERT_THAT(zip, NotNull()); - - auto data = zip->Uncompress("res/xml/test.xml"); - ASSERT_THAT(data, NotNull()); - - auto xml = Xml::Create(data->buf, data->size); - ASSERT_THAT(xml, NotNull()); - - auto attrs = xml->FindTag("c"); - ASSERT_THAT(attrs, NotNull()); - ASSERT_EQ(attrs->size(), 4U); - ASSERT_EQ(attrs->at("type_string"), "fortytwo"); - ASSERT_EQ(std::stoi(attrs->at("type_int_dec")), 42); - ASSERT_EQ(std::stoi(attrs->at("type_int_hex")), 42); - ASSERT_NE(std::stoul(attrs->at("type_int_boolean")), 0U); - - auto fail = xml->FindTag("does-not-exist"); - ASSERT_THAT(fail, IsNull()); -} - -} // namespace android::idmap2 diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml index 619bb6ce0f25..cf3691c3b3cf 100644 --- a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml +++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml @@ -16,8 +16,11 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="test.overlay"> + <application android:hasCode="false"/> + <overlay android:targetPackage="test.target" - android:targetName="TestResources"/> + android:targetName="TestResources" + android:resourcesMap="@xml/overlays"/> </manifest> diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build index 68b9f507a11d..114b099598fa 100755 --- a/cmds/idmap2/tests/data/overlay/build +++ b/cmds/idmap2/tests/data/overlay/build @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar +FRAMEWORK_RES_APK=${ANDROID_PRODUCT_OUT}/system/framework/framework-res.apk aapt2 compile --dir res -o compiled.flata @@ -51,4 +51,12 @@ aapt2 link \ -o overlay-static-2.apk \ compiled.flata +aapt2 link \ + --no-resource-removal \ + --shared-lib \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifest.xml \ + -o overlay-shared.apk \ + compiled.flata + rm compiled.flata diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk Binary files differindex 18ee43dc57a4..7c25985e5a61 100644 --- a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk +++ b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk Binary files differindex 642519008b15..c75f3e1dbddf 100644 --- a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk +++ b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk Binary files differnew file mode 100644 index 000000000000..93dcc82f9358 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk Binary files differindex 642ab90d00ae..5b8a6e4a90ed 100644 --- a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk +++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk Binary files differindex 2ec56020c4aa..698a1fd6e702 100644 --- a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk +++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk Binary files differindex 5842da4f432e..1db303ff05b5 100644 --- a/cmds/idmap2/tests/data/overlay/overlay.apk +++ b/cmds/idmap2/tests/data/overlay/overlay.apk diff --git a/cmds/statsd/AndroidTest.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml index afe30a269093..edd33f7dc90d 100644 --- a/cmds/statsd/AndroidTest.xml +++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project +<!-- Copyright (C) 2019 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. @@ -13,14 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. --> -<configuration description="Config for statsd_test"> - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="cleanup" value="true" /> - <option name="push" value="statsd_test->/data/nativetest/statsd_test" /> - </target_preparer> - <option name="test-suite-tag" value="apct" /> - <test class="com.android.tradefed.testtype.GTest" > - <option name="native-test-device-path" value="/data/nativetest" /> - <option name="module-name" value="statsd_test" /> - </test> -</configuration>
\ No newline at end of file +<overlay> + <item target="string/str1" value="@string/str1"/> + <item target="string/str3" value="@string/str3" /> + <item target="string/str4" value="@string/str4" /> + <item target="integer/int1" value="@integer/int1" /> + <item target="integer/not_in_target" value="@integer/not_in_target" /> +</overlay> + diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml new file mode 100644 index 000000000000..aa7fefaa305e --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> +<overlay> + <item target="string/str1" value="@android:string/ok"/> + <item target="string/str3" value="@string/str3" /> +</overlay> + diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml new file mode 100644 index 000000000000..e12b823ff50d --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> +<overlay> + <item target="string/str1" value="Hello World"/> + <item target="integer/int1" value="73" /> +</overlay> + diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml new file mode 100644 index 000000000000..5728e672d94a --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> +<overlay> + <item target="string/str1" value="@string/str4"/> + <item target="string/str3" value="@string/str1" /> + <item target="string/str4" value="@string/str3" /> + <item target="integer/int_not_in_target" value="@integer/int1" /> +</overlay> diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml index 9ebfae41e4c5..7119d8283061 100644 --- a/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml +++ b/cmds/idmap2/tests/data/system-overlay-invalid/res/values/values.xml @@ -25,6 +25,7 @@ <string name="policy_signature">policy_signature</string> <string name="policy_odm">policy_odm</string> <string name="policy_oem">policy_oem</string> + <string name="policy_actor">policy_actor</string> <!-- Requests to overlay a resource that is not declared as overlayable. --> <string name="not_overlayable">not_overlayable</string> diff --git a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk Binary files differindex 1456e749e796..bd990983693c 100644 --- a/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk +++ b/cmds/idmap2/tests/data/system-overlay-invalid/system-overlay-invalid.apk diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml index 8389f5635e15..ad4cd4882632 100644 --- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml +++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml @@ -41,6 +41,10 @@ <item type="string" name="policy_oem" /> </policy> + <policy type="actor"> + <item type="string" name="policy_actor" /> + </policy> + <!-- Resources publicly overlayable --> <policy type="public"> <item type="string" name="policy_public" /> @@ -63,4 +67,4 @@ <item type="string" name="other" /> </policy> </overlayable> -</resources>
\ No newline at end of file +</resources> diff --git a/cmds/idmap2/tests/data/target/res/values/values.xml b/cmds/idmap2/tests/data/target/res/values/values.xml index a892c98f0fbb..5230e25e626b 100644 --- a/cmds/idmap2/tests/data/target/res/values/values.xml +++ b/cmds/idmap2/tests/data/target/res/values/values.xml @@ -36,6 +36,7 @@ <string name="policy_signature">policy_signature</string> <string name="policy_system">policy_system</string> <string name="policy_system_vendor">policy_system_vendor</string> + <string name="policy_actor">policy_actor</string> <string name="other">other</string> </resources> diff --git a/cmds/idmap2/tests/data/target/res/xml/test.xml b/cmds/idmap2/tests/data/target/res/xml/test.xml index 0fe21c6b6d0a..56a3f7f0b13a 100644 --- a/cmds/idmap2/tests/data/target/res/xml/test.xml +++ b/cmds/idmap2/tests/data/target/res/xml/test.xml @@ -14,12 +14,15 @@ limitations under the License. --> <a> - <b> - <c - type_string="fortytwo" - type_int_dec="42" - type_int_hex="0x2a" - type_int_boolean="true" - /> + <b type_string="fortytwo" + type_int_dec="42" + type_int_hex="0x2a" + type_int_boolean="true"> + + <c /> </b> -</a> + + <d> + <e /> + </d> +</a>
\ No newline at end of file diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk Binary files differindex 033305aaed4f..58504a74a83a 100644 --- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk +++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk Binary files differindex 9bcd6dcabcde..c80e5eb65ff2 100644 --- a/cmds/idmap2/tests/data/target/target.apk +++ b/cmds/idmap2/tests/data/target/target.apk diff --git a/cmds/idmap2/valgrind.sh b/cmds/idmap2/valgrind.sh new file mode 100755 index 000000000000..b4ebab0c7ffe --- /dev/null +++ b/cmds/idmap2/valgrind.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# +# Copyright (C) 2019 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. +# + +function _log() +{ + echo -e "$*" >&2 +} + +function _eval() +{ + local label="$1" + local cmd="$2" + local red="\e[31m" + local green="\e[32m" + local reset="\e[0m" + local output + + _log "${green}[ RUN ]${reset} ${label}" + output="$(eval "$cmd" 2>&1)" + if [[ $? -eq 0 ]]; then + _log "${green}[ OK ]${reset} ${label}" + return 0 + else + echo "${output}" + _log "${red}[ FAILED ]${reset} ${label}" + errors=$((errors + 1)) + return 1 + fi +} + +errors=0 +script="$(readlink -f "$BASH_SOURCE")" +prefix="$(dirname "$script")" +target_path="${prefix}/tests/data/target/target.apk" +overlay_path="${prefix}/tests/data/overlay/overlay.apk" +idmap_path="/tmp/a.idmap" +valgrind="valgrind --error-exitcode=1 -q --track-origins=yes --leak-check=full" + +_eval "idmap2 create" "$valgrind idmap2 create --policy public --target-apk-path $target_path --overlay-apk-path $overlay_path --idmap-path $idmap_path" +_eval "idmap2 dump" "$valgrind idmap2 dump --idmap-path $idmap_path" +_eval "idmap2 lookup" "$valgrind idmap2 lookup --idmap-path $idmap_path --config '' --resid test.target:string/str1" +_eval "idmap2 scan" "$valgrind idmap2 scan --input-directory ${prefix}/tests/data/overlay --recursive --target-package-name test.target --target-apk-path $target_path --output-directory /tmp --override-policy public" +_eval "idmap2 verify" "$valgrind idmap2 verify --idmap-path $idmap_path" +_eval "idmap2_tests" "$valgrind $ANDROID_HOST_OUT/nativetest64/idmap2_tests/idmap2_tests" +exit $errors diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp index 9e9dac14c802..94855aa0311f 100644 --- a/cmds/incident/Android.bp +++ b/cmds/incident/Android.bp @@ -26,7 +26,7 @@ cc_binary { "libcutils", "liblog", "libutils", - "libincident", + "libincidentpriv", ], static_libs: [ diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp index d6c6c39dd1d8..6e0bd0629274 100644 --- a/cmds/incident/main.cpp +++ b/cmds/incident/main.cpp @@ -231,6 +231,7 @@ usage(FILE* out) fprintf(out, " -l list available sections\n"); fprintf(out, " -p privacy spec, LOCAL, EXPLICIT or AUTOMATIC. Default AUTOMATIC.\n"); fprintf(out, " -r REASON human readable description of why the report is taken.\n"); + fprintf(out, " -z gzip the incident report, i.e. pipe the output through gzip.\n"); fprintf(out, "\n"); fprintf(out, "and one of these destinations:\n"); fprintf(out, " -b (default) print the report to stdout (in proto format)\n"); @@ -255,7 +256,7 @@ main(int argc, char** argv) // Parse the args int opt; - while ((opt = getopt(argc, argv, "bhdlp:r:s:u")) != -1) { + while ((opt = getopt(argc, argv, "bhdlp:r:s:uz")) != -1) { switch (opt) { case 'h': usage(stdout); @@ -302,6 +303,9 @@ main(int argc, char** argv) destination = DEST_BROADCAST; receiverArg = optarg; break; + case 'z': + args.setGzip(true); + break; default: usage(stderr); return 1; diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp index d7b6d69ade74..f07743ec2ee6 100644 --- a/cmds/incident_helper/Android.bp +++ b/cmds/incident_helper/Android.bp @@ -1,3 +1,28 @@ +// 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. + +java_binary { + name: "incident-helper-cmd", + wrapper: "incident_helper_cmd", + srcs: [ + "java/**/*.java", + ], + proto: { + plugin: "javastream", + }, +} + cc_defaults { name: "incident_helper_defaults", @@ -19,7 +44,7 @@ cc_defaults { "src/ih_util.cpp", ], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], shared_libs: [ "libbase", diff --git a/cmds/incident_helper/incident_helper_cmd b/cmds/incident_helper/incident_helper_cmd new file mode 100644 index 000000000000..d45f7df41aaf --- /dev/null +++ b/cmds/incident_helper/incident_helper_cmd @@ -0,0 +1,6 @@ +#!/system/bin/sh +# Script to start "incident_helper_cmd" on the device +# +base=/system +export CLASSPATH=$base/framework/incident-helper-cmd.jar +exec app_process $base/bin com.android.commands.incident.IncidentHelper "$@" diff --git a/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java new file mode 100644 index 000000000000..d97b17ea630b --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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 com.android.commands.incident; + +/** + * Thrown when there is an error executing a section. + */ +public class ExecutionException extends Exception { + /** + * Constructs a ExecutionException. + * + * @param msg the message + */ + public ExecutionException(String msg) { + super(msg); + } + + /** + * Constructs a ExecutionException from another exception. + * + * @param e the exception + */ + public ExecutionException(Exception e) { + super(e); + } +} diff --git a/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java new file mode 100644 index 000000000000..e5874e0a5201 --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 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 com.android.commands.incident; + +import android.util.Log; + +import com.android.commands.incident.sections.PersistLogSection; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; + +/** + * Helper command runner for incidentd to run customized command to gather data for a non-standard + * section. + */ +public class IncidentHelper { + private static final String TAG = "IncidentHelper"; + private static boolean sLog = false; + private final List<String> mArgs; + private ListIterator<String> mArgsIterator; + + private IncidentHelper(String[] args) { + mArgs = Collections.unmodifiableList(Arrays.asList(args)); + mArgsIterator = mArgs.listIterator(); + } + + private static void showUsage(PrintStream out) { + out.println("This command is not designed to be run manually."); + out.println("Usage:"); + out.println(" run [sectionName]"); + } + + private void run(String[] args) throws ExecutionException { + Section section = null; + List<String> sectionArgs = new ArrayList<>(); + while (mArgsIterator.hasNext()) { + String arg = mArgsIterator.next(); + if ("-l".equals(arg)) { + sLog = true; + Log.i(TAG, "Args: [" + String.join(",", args) + "]"); + } else if ("run".equals(arg)) { + section = getSection(nextArgRequired()); + mArgsIterator.forEachRemaining(sectionArgs::add); + break; + } else { + log(Log.WARN, TAG, "Error: Unknown argument: " + arg); + return; + } + } + section.run(System.in, System.out, sectionArgs); + } + + private static Section getSection(String name) throws IllegalArgumentException { + if ("persisted_logs".equals(name)) { + return new PersistLogSection(); + } + throw new IllegalArgumentException("Section not found: " + name); + } + + private String nextArgRequired() { + if (!mArgsIterator.hasNext()) { + throw new IllegalArgumentException( + "Arg required after \"" + mArgs.get(mArgsIterator.previousIndex()) + "\""); + } + return mArgsIterator.next(); + } + + /** + * Print the given message to stderr, also log it if asked to (set by -l cmd arg). + */ + public static void log(int priority, String tag, String msg) { + System.err.println(tag + ": " + msg); + if (sLog) { + Log.println(priority, tag, msg); + } + } + + /** + * Command-line entry point. + * + * @param args The command-line arguments + */ + public static void main(String[] args) { + if (args.length == 0) { + showUsage(System.err); + System.exit(0); + } + IncidentHelper incidentHelper = new IncidentHelper(args); + try { + incidentHelper.run(args); + } catch (IllegalArgumentException e) { + showUsage(System.err); + System.err.println(); + e.printStackTrace(System.err); + if (sLog) { + Log.e(TAG, "Error: ", e); + } + } catch (Exception e) { + e.printStackTrace(System.err); + if (sLog) { + Log.e(TAG, "Error: ", e); + } + System.exit(1); + } + } +} diff --git a/cmds/incident_helper/java/com/android/commands/incident/Section.java b/cmds/incident_helper/java/com/android/commands/incident/Section.java new file mode 100644 index 000000000000..1c8c6578fb5e --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/Section.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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 com.android.commands.incident; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +/** Section interface used by {@link IncidentHelper}. */ +public interface Section { + /** + * Writes protobuf wire format to out, optionally reads data from in, with supplied args. + */ + void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException; +} diff --git a/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java new file mode 100644 index 000000000000..f9d2e79e750a --- /dev/null +++ b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2020 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 com.android.commands.incident.sections; + +import android.util.Log; +import android.util.PersistedLogProto; +import android.util.TextLogEntry; +import android.util.proto.ProtoOutputStream; + +import com.android.commands.incident.ExecutionException; +import com.android.commands.incident.IncidentHelper; +import com.android.commands.incident.Section; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Comparator; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** PersistLogSection reads persisted logs and parses them into a PersistedLogProto. */ +public class PersistLogSection implements Section { + private static final String TAG = "IH_PersistLog"; + private static final String LOG_DIR = "/data/misc/logd/"; + // Persist log files are named logcat, logcat.001, logcat.002, logcat.003, ... + private static final Pattern LOG_FILE_RE = Pattern.compile("logcat(\\.\\d+)?"); + private static final Pattern BUFFER_BEGIN_RE = + Pattern.compile("--------- (?:beginning of|switch to) (.*)"); + private static final Map<String, Long> SECTION_NAME_TO_ID = new HashMap<>(); + private static final Map<Character, Integer> LOG_PRIORITY_MAP = new HashMap<>(); + private static final String DEFAULT_BUFFER = "main"; + + static { + SECTION_NAME_TO_ID.put("main", PersistedLogProto.MAIN_LOGS); + SECTION_NAME_TO_ID.put("radio", PersistedLogProto.RADIO_LOGS); + SECTION_NAME_TO_ID.put("events", PersistedLogProto.EVENTS_LOGS); + SECTION_NAME_TO_ID.put("system", PersistedLogProto.SYSTEM_LOGS); + SECTION_NAME_TO_ID.put("crash", PersistedLogProto.CRASH_LOGS); + SECTION_NAME_TO_ID.put("kernel", PersistedLogProto.KERNEL_LOGS); + } + + static { + LOG_PRIORITY_MAP.put('V', TextLogEntry.LOG_VERBOSE); + LOG_PRIORITY_MAP.put('D', TextLogEntry.LOG_DEBUG); + LOG_PRIORITY_MAP.put('I', TextLogEntry.LOG_INFO); + LOG_PRIORITY_MAP.put('W', TextLogEntry.LOG_WARN); + LOG_PRIORITY_MAP.put('E', TextLogEntry.LOG_ERROR); + LOG_PRIORITY_MAP.put('F', TextLogEntry.LOG_FATAL); + LOG_PRIORITY_MAP.put('S', TextLogEntry.LOG_SILENT); + } + + /** + * Caches dates at 00:00:00 to epoch second elapsed conversion. There are only a few different + * dates in persisted logs in one device, and constructing DateTime object is relatively + * expensive. + */ + private Map<Integer, Long> mEpochTimeCache = new HashMap<>(); + private ProtoOutputStream mProto; + private long mCurrFieldId; + private long mMaxBytes = Long.MAX_VALUE; + + @Override + public void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException { + parseArgs(args); + Path logDirPath = Paths.get(LOG_DIR); + if (!Files.exists(logDirPath)) { + IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " does not exist."); + return; + } + if (!Files.isReadable(logDirPath)) { + IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " is not readable."); + return; + } + mProto = new ProtoOutputStream(out); + setCurrentSection(DEFAULT_BUFFER); + final Matcher logFileRe = LOG_FILE_RE.matcher(""); + // Need to process older log files first and write logs to proto in chronological order + // But we want to process only the latest ones if there is a size limit + try (Stream<File> stream = Files.list(logDirPath).map(Path::toFile) + .filter(f -> !f.isDirectory() && match(logFileRe, f.getName()) != null) + .sorted(Comparator.comparingLong(File::lastModified).reversed())) { + Iterator<File> iter = stream.iterator(); + List<File> filesToProcess = new ArrayList<>(); + long sumBytes = 0; + while (iter.hasNext()) { + File file = iter.next(); + sumBytes += file.length(); + if (sumBytes > mMaxBytes) { + break; + } + filesToProcess.add(file); + } + IncidentHelper.log(Log.INFO, TAG, "Limit # log files to " + filesToProcess.size()); + filesToProcess.stream() + .sorted(Comparator.comparingLong(File::lastModified)) + .forEachOrdered(this::processFile); + } catch (IOException e) { + throw new ExecutionException(e); + } finally { + mProto.flush(); + } + IncidentHelper.log(Log.DEBUG, TAG, "Bytes written: " + mProto.getBytes().length); + } + + private void parseArgs(List<String> args) { + Iterator<String> iter = args.iterator(); + while (iter.hasNext()) { + String arg = iter.next(); + if ("--limit".equals(arg) && iter.hasNext()) { + String sizeStr = iter.next().toLowerCase(); + if (sizeStr.endsWith("mb")) { + mMaxBytes = Long.parseLong(sizeStr.replace("mb", "")) * 1024 * 1024; + } else if (sizeStr.endsWith("kb")) { + mMaxBytes = Long.parseLong(sizeStr.replace("kb", "")) * 1024; + } else { + mMaxBytes = Long.parseLong(sizeStr); + } + } else { + throw new IllegalArgumentException("Unknown argument: " + arg); + } + } + } + + private void processFile(File file) { + final Matcher bufferBeginRe = BUFFER_BEGIN_RE.matcher(""); + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), + StandardCharsets.UTF_8)) { + String line; + Matcher m; + while ((line = reader.readLine()) != null) { + if ((m = match(bufferBeginRe, line)) != null) { + setCurrentSection(m.group(1)); + continue; + } + parseLine(line); + } + } catch (IOException e) { + // Non-fatal error. We can skip and still process other files. + IncidentHelper.log(Log.WARN, TAG, "Error reading \"" + file + "\": " + e.getMessage()); + } + IncidentHelper.log(Log.DEBUG, TAG, "Finished reading " + file); + } + + private void setCurrentSection(String sectionName) { + Long sectionId = SECTION_NAME_TO_ID.get(sectionName); + if (sectionId == null) { + IncidentHelper.log(Log.WARN, TAG, "Section does not exist: " + sectionName); + sectionId = SECTION_NAME_TO_ID.get(DEFAULT_BUFFER); + } + mCurrFieldId = sectionId; + } + + /** + * Parse a log line in the following format: + * 01-01 15:01:47.723501 2738 2895 I Exp_TAG: example log line + * + * It does not use RegExp for performance reasons. Using this RegExp "(\\d{2})-(\\d{2})\\s + * (\\d{2}):(\\d{2}):(\\d{2}).(\\d{6})\\s+(\\d+)\\s+(\\d+)\\s+(.)\\s+(.*?):\\s(.*)" is twice as + * slow as the current approach. + */ + private void parseLine(String line) { + long token = mProto.start(mCurrFieldId); + try { + mProto.write(TextLogEntry.SEC, getEpochSec(line)); + // Nanosec is 15th to 20th digits of "10-01 02:57:27.710652" times 1000 + mProto.write(TextLogEntry.NANOSEC, parseInt(line, 15, 21) * 1000L); + + int start = nextNonBlank(line, 21); + int end = line.indexOf(' ', start + 1); + mProto.write(TextLogEntry.PID, parseInt(line, start, end)); + + start = nextNonBlank(line, end); + end = line.indexOf(' ', start + 1); + mProto.write(TextLogEntry.TID, parseInt(line, start, end)); + + start = nextNonBlank(line, end); + char priority = line.charAt(start); + mProto.write(TextLogEntry.PRIORITY, + LOG_PRIORITY_MAP.getOrDefault(priority, TextLogEntry.LOG_DEFAULT)); + + start = nextNonBlank(line, start + 1); + end = line.indexOf(": ", start); + mProto.write(TextLogEntry.TAG, line.substring(start, end).trim()); + mProto.write(TextLogEntry.LOG, line.substring(Math.min(end + 2, line.length()))); + } catch (RuntimeException e) { + // Error reporting is likely piped to /dev/null. Inserting it into the proto to make + // it more useful. + mProto.write(TextLogEntry.SEC, System.currentTimeMillis() / 1000); + mProto.write(TextLogEntry.PRIORITY, TextLogEntry.LOG_ERROR); + mProto.write(TextLogEntry.TAG, TAG); + mProto.write(TextLogEntry.LOG, + "Error parsing \"" + line + "\"" + ": " + e.getMessage()); + } + mProto.end(token); + } + + // ============== Below are util methods to parse log lines ============== + + private static int nextNonBlank(String line, int start) { + for (int i = start; i < line.length(); i++) { + if (line.charAt(i) != ' ') { + return i; + } + } + return -1; + } + + /** + * Gets the epoch second from the line string. Line starts with a fixed-length timestamp like + * "10-01 02:57:27.710652" + */ + private long getEpochSec(String line) { + int month = getDigit(line, 0) * 10 + getDigit(line, 1); + int day = getDigit(line, 3) * 10 + getDigit(line, 4); + + int mmdd = month * 100 + day; + long epochSecBase = mEpochTimeCache.computeIfAbsent(mmdd, (key) -> { + final GregorianCalendar calendar = new GregorianCalendar(); + calendar.set(Calendar.MONTH, (month + 12 - 1) % 12); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + // Date in log entries can never be in the future. If it happens, it means we are off + // by one year. + if (calendar.getTimeInMillis() > System.currentTimeMillis()) { + calendar.roll(Calendar.YEAR, /*amount=*/-1); + } + return calendar.getTimeInMillis() / 1000; + }); + + int hh = getDigit(line, 6) * 10 + getDigit(line, 7); + int mm = getDigit(line, 9) * 10 + getDigit(line, 10); + int ss = getDigit(line, 12) * 10 + getDigit(line, 13); + return epochSecBase + hh * 3600 + mm * 60 + ss; + } + + private static int parseInt(String line, /*inclusive*/ int start, /*exclusive*/ int end) { + int num = 0; + for (int i = start; i < end; i++) { + num = num * 10 + getDigit(line, i); + } + return num; + } + + private static int getDigit(String str, int pos) { + int digit = str.charAt(pos) - '0'; + if (digit < 0 || digit > 9) { + throw new NumberFormatException("'" + str.charAt(pos) + "' is not a digit."); + } + return digit; + } + + private static Matcher match(Matcher matcher, String text) { + matcher.reset(text); + return matcher.matches() ? matcher : null; + } +} diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp index 77a56e55045b..557fe9fc400f 100644 --- a/cmds/incident_helper/src/ih_util.cpp +++ b/cmds/incident_helper/src/ih_util.cpp @@ -237,33 +237,38 @@ double toDouble(const std::string& s) { Reader::Reader(const int fd) { mFile = fdopen(fd, "r"); + mBuffer = new char[1024]; mStatus = mFile == nullptr ? "Invalid fd " + std::to_string(fd) : ""; } Reader::~Reader() { if (mFile != nullptr) fclose(mFile); + delete[] mBuffer; } bool Reader::readLine(std::string* line) { if (mFile == nullptr) return false; - char* buf = nullptr; size_t len = 0; - ssize_t read = getline(&buf, &len, mFile); + ssize_t read = getline(&mBuffer, &len, mFile); if (read != -1) { - std::string s(buf); + std::string s(mBuffer); line->assign(trim(s, DEFAULT_NEWLINE)); - } else if (errno == EINVAL) { - mStatus = "Bad Argument"; + return true; } - free(buf); - return read != -1; + if (!feof(mFile)) { + mStatus = "Error reading file. Ferror: " + std::to_string(ferror(mFile)); + } + return false; } bool Reader::ok(std::string* error) { + if (mStatus.empty()) { + return true; + } error->assign(mStatus); - return mStatus.empty(); + return false; } // ============================================================================== diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h index 09dc8e6fdbfc..5812c603297e 100644 --- a/cmds/incident_helper/src/ih_util.h +++ b/cmds/incident_helper/src/ih_util.h @@ -117,6 +117,7 @@ public: private: FILE* mFile; + char* mBuffer; std::string mStatus; }; diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp index 25e0328b4f38..c47526abad53 100644 --- a/cmds/incidentd/Android.bp +++ b/cmds/incidentd/Android.bp @@ -43,7 +43,7 @@ cc_binary { ], local_include_dirs: ["src"], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], proto: { type: "lite", @@ -54,7 +54,7 @@ cc_binary { "libbinder", "libdebuggerd_client", "libdumputils", - "libincident", + "libincidentpriv", "liblog", "libprotoutil", "libservices", @@ -98,7 +98,7 @@ cc_test { ], local_include_dirs: ["src"], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], srcs: [ "tests/**/*.cpp", @@ -128,7 +128,7 @@ cc_test { "libbinder", "libdebuggerd_client", "libdumputils", - "libincident", + "libincidentpriv", "liblog", "libprotobuf-cpp-full", "libprotoutil", diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index d295b84baf67..78c322edcccc 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -17,6 +17,7 @@ #include "Log.h" #include "FdBuffer.h" +#include "incidentd_util.h" #include <log/log.h> #include <utils/SystemClock.h> @@ -31,17 +32,24 @@ namespace os { namespace incidentd { const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB -const ssize_t MAX_BUFFER_COUNT = 6144; // 96 MB max +const ssize_t MAX_BUFFER_SIZE = 96 * 1024 * 1024; // 96 MB -FdBuffer::FdBuffer() - :mBuffer(new EncodedBuffer(BUFFER_SIZE)), +FdBuffer::FdBuffer(): FdBuffer(get_buffer_from_pool(), /* isBufferPooled= */ true) { +} + +FdBuffer::FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled) + :mBuffer(buffer), mStartTime(-1), mFinishTime(-1), mTimedOut(false), - mTruncated(false) { + mTruncated(false), + mIsBufferPooled(isBufferPooled) { } FdBuffer::~FdBuffer() { + if (mIsBufferPooled) { + return_buffer_to_pool(mBuffer); + } } status_t FdBuffer::read(int fd, int64_t timeout) { @@ -51,7 +59,7 @@ status_t FdBuffer::read(int fd, int64_t timeout) { fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); while (true) { - if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_SIZE) { mTruncated = true; VLOG("Truncating data"); break; @@ -106,7 +114,7 @@ status_t FdBuffer::readFully(int fd) { mStartTime = uptimeMillis(); while (true) { - if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_SIZE) { // Don't let it get too big. mTruncated = true; VLOG("Truncating data"); @@ -156,7 +164,7 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f // This is the buffer used to store processed data while (true) { - if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_SIZE) { VLOG("Truncating data"); mTruncated = true; break; diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h index a3493604f425..9b2794d165fb 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -35,6 +35,7 @@ using namespace android::util; class FdBuffer { public: FdBuffer(); + FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled = false); ~FdBuffer(); /** @@ -114,6 +115,7 @@ private: int64_t mFinishTime; bool mTimedOut; bool mTruncated; + bool mIsBufferPooled; }; } // namespace incidentd diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index cfd77c2357cd..dc1612575f38 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -123,14 +123,17 @@ static string build_uri(const string& pkg, const string& cls, const string& id) // ================================================================================ ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory, - const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper, - const sp<Throttler>& throttler) + const sp<Broadcaster>& broadcaster, + const sp<Looper>& handlerLooper, + const sp<Throttler>& throttler, + const vector<BringYourOwnSection*>& registeredSections) :mLock(), mWorkDirectory(workDirectory), mBroadcaster(broadcaster), mHandlerLooper(handlerLooper), mBacklogDelay(DEFAULT_DELAY_NS), mThrottler(throttler), + mRegisteredSections(registeredSections), mBatch(new ReportBatch()) { } @@ -149,6 +152,7 @@ void ReportHandler::handleMessage(const Message& message) { } void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) { + unique_lock<mutex> lock(mLock); mBatch->addPersistedReport(args); mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT); mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT)); @@ -156,6 +160,7 @@ void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) { void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args, const sp<IIncidentReportStatusListener>& listener, int streamFd) { + unique_lock<mutex> lock(mLock); mBatch->addStreamingReport(args, listener, streamFd); mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT); mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT)); @@ -185,7 +190,7 @@ void ReportHandler::take_report() { return; } - sp<Reporter> reporter = new Reporter(mWorkDirectory, batch); + sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections); // Take the report, which might take a while. More requests might queue // up while we're doing this, and we'll handle them in their next batch. @@ -237,7 +242,7 @@ IncidentService::IncidentService(const sp<Looper>& handlerLooper) { mWorkDirectory = new WorkDirectory(); mBroadcaster = new Broadcaster(mWorkDirectory); mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper, - mThrottler); + mThrottler, mRegisteredSections); mBroadcaster->setHandler(mHandler); } @@ -327,6 +332,11 @@ Status IncidentService::reportIncidentToDumpstate(unique_fd stream, incidentArgs.addSection(id); } } + for (const Section* section : mRegisteredSections) { + if (!section_requires_specific_mention(section->id)) { + incidentArgs.addSection(section->id); + } + } // The ReportRequest takes ownership of the fd, so we need to dup it. int fd = dup(stream.get()); @@ -339,6 +349,46 @@ Status IncidentService::reportIncidentToDumpstate(unique_fd stream, return Status::ok(); } +Status IncidentService::registerSection(const int id, const String16& name16, + const sp<IIncidentDumpCallback>& callback) { + const String8 name = String8(name16); + const uid_t callingUid = IPCThreadState::self()->getCallingUid(); + ALOGI("Uid %d registers section %d '%s'", callingUid, id, name.c_str()); + if (callback == nullptr) { + return Status::fromExceptionCode(Status::EX_NULL_POINTER); + } + for (int i = 0; i < mRegisteredSections.size(); i++) { + if (mRegisteredSections.at(i)->id == id) { + if (mRegisteredSections.at(i)->uid != callingUid) { + ALOGW("Error registering section %d: calling uid does not match", id); + return Status::fromExceptionCode(Status::EX_SECURITY); + } + mRegisteredSections.at(i) = new BringYourOwnSection(id, name.c_str(), callingUid, callback); + return Status::ok(); + } + } + mRegisteredSections.push_back(new BringYourOwnSection(id, name.c_str(), callingUid, callback)); + return Status::ok(); +} + +Status IncidentService::unregisterSection(const int id) { + uid_t callingUid = IPCThreadState::self()->getCallingUid(); + ALOGI("Uid %d unregisters section %d", callingUid, id); + + for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) { + if ((*it)->id == id) { + if ((*it)->uid != callingUid) { + ALOGW("Error unregistering section %d: calling uid does not match", id); + return Status::fromExceptionCode(Status::EX_SECURITY); + } + mRegisteredSections.erase(it); + return Status::ok(); + } + } + ALOGW("Section %d not found", id); + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); +} + Status IncidentService::systemRunning() { if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { return Status::fromExceptionCode(Status::EX_SECURITY, diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h index b2c7f233e11b..49fc566d71af 100644 --- a/cmds/incidentd/src/IncidentService.h +++ b/cmds/incidentd/src/IncidentService.h @@ -40,12 +40,16 @@ using namespace android::base; using namespace android::binder; using namespace android::os; +class BringYourOwnSection; + // ================================================================================ class ReportHandler : public MessageHandler { public: ReportHandler(const sp<WorkDirectory>& workDirectory, - const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper, - const sp<Throttler>& throttler); + const sp<Broadcaster>& broadcaster, + const sp<Looper>& handlerLooper, + const sp<Throttler>& throttler, + const vector<BringYourOwnSection*>& registeredSections); virtual ~ReportHandler(); virtual void handleMessage(const Message& message); @@ -79,6 +83,8 @@ private: nsecs_t mBacklogDelay; sp<Throttler> mThrottler; + const vector<BringYourOwnSection*>& mRegisteredSections; + sp<ReportBatch> mBatch; /** @@ -126,6 +132,11 @@ public: virtual Status reportIncidentToDumpstate(unique_fd stream, const sp<IIncidentReportStatusListener>& listener); + virtual Status registerSection(int id, const String16& name, + const sp<IIncidentDumpCallback>& callback); + + virtual Status unregisterSection(int id); + virtual Status systemRunning(); virtual Status getIncidentReportList(const String16& pkg, const String16& cls, @@ -149,6 +160,7 @@ private: sp<Broadcaster> mBroadcaster; sp<ReportHandler> mHandler; sp<Throttler> mThrottler; + vector<BringYourOwnSection*> mRegisteredSections; /** * Commands print out help. diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp index d00ecdde5c63..0d427d1021a6 100644 --- a/cmds/incidentd/src/PrivacyFilter.cpp +++ b/cmds/incidentd/src/PrivacyFilter.cpp @@ -19,9 +19,6 @@ #include "incidentd_util.h" #include "PrivacyFilter.h" #include "proto_util.h" - -#include "incidentd_util.h" -#include "proto_util.h" #include "Section.h" #include <android-base/file.h> @@ -129,6 +126,8 @@ public: FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data, uint8_t bufferLevel); + ~FieldStripper(); + /** * Take the data that we have, and filter it down so that no fields * are more sensitive than the given privacy policy. @@ -167,6 +166,7 @@ private: */ uint8_t mCurrentLevel; + sp<EncodedBuffer> mEncodedBuffer; }; FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data, @@ -174,19 +174,25 @@ FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& :mRestrictions(restrictions), mData(data), mSize(data->size()), - mCurrentLevel(bufferLevel) { + mCurrentLevel(bufferLevel), + mEncodedBuffer(get_buffer_from_pool()) { if (mSize < 0) { ALOGW("FieldStripper constructed with a ProtoReader that doesn't support size." " Data will be missing."); } } +FieldStripper::~FieldStripper() { + return_buffer_to_pool(mEncodedBuffer); +} + status_t FieldStripper::strip(const uint8_t privacyPolicy) { // If the current strip level is less (fewer fields retained) than what's already in the // buffer, then we can skip it. if (mCurrentLevel < privacyPolicy) { PrivacySpec spec(privacyPolicy); - ProtoOutputStream proto; + mEncodedBuffer->clear(); + ProtoOutputStream proto(mEncodedBuffer); // Optimization when no strip happens. if (mRestrictions == NULL || spec.RequireAll()) { @@ -267,7 +273,7 @@ status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, // Order the writes by privacy filter, with increasing levels of filtration,k // so we can do the filter once, and then write many times. sort(mOutputs.begin(), mOutputs.end(), - [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { + [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { return a->getPrivacyPolicy() < b->getPrivacyPolicy(); }); @@ -370,7 +376,7 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel, write_field_or_skip(NULL, reader, fieldTag, true); } } - + clear_buffer_pool(); err = reader->getError(); if (err != NO_ERROR) { ALOGW("filter_and_write_report reader had an error: %s", strerror(-err)); diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 02b6bbe6c9b1..86a78f095f52 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -35,10 +35,12 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> +#include <sys/prctl.h> #include <sys/stat.h> #include <sys/types.h> #include <string> #include <time.h> +#include <wait.h> namespace android { namespace os { @@ -51,6 +53,8 @@ using namespace android::util; * frameworks/base/core/proto/android/os/incident.proto */ const int FIELD_ID_METADATA = 2; +// Args for exec gzip +static const char* GZIP[] = {"/system/bin/gzip", NULL}; IncidentMetadata_Destination privacy_policy_to_dest(uint8_t privacyPolicy) { switch (privacyPolicy) { @@ -142,7 +146,8 @@ ReportRequest::ReportRequest(const IncidentReportArgs& a, mListener(listener), mFd(fd), mIsStreaming(fd >= 0), - mStatus(NO_ERROR) { + mStatus(OK), + mZipPid(-1) { } ReportRequest::~ReportRequest() { @@ -153,7 +158,14 @@ ReportRequest::~ReportRequest() { } bool ReportRequest::ok() { - return mFd >= 0 && mStatus == NO_ERROR; + if (mStatus != OK) { + return false; + } + if (!args.gzip()) { + return mFd >= 0; + } + // Send a blank signal to check if mZipPid is alive + return mZipPid > 0 && kill(mZipPid, 0) == 0; } bool ReportRequest::containsSection(int sectionId) const { @@ -161,10 +173,45 @@ bool ReportRequest::containsSection(int sectionId) const { } void ReportRequest::closeFd() { - if (mIsStreaming && mFd >= 0) { + if (!mIsStreaming) { + return; + } + if (mFd >= 0) { close(mFd); mFd = -1; } + if (mZipPid > 0) { + mZipPipe.close(); + // Gzip may take some time. + status_t err = wait_child(mZipPid, /* timeout_ms= */ 10 * 1000); + if (err != 0) { + ALOGW("[ReportRequest] abnormal child process: %s", strerror(-err)); + } + } +} + +int ReportRequest::getFd() { + return mZipPid > 0 ? mZipPipe.writeFd().get() : mFd; +} + +status_t ReportRequest::initGzipIfNecessary() { + if (!mIsStreaming || !args.gzip()) { + return OK; + } + if (!mZipPipe.init()) { + ALOGE("[ReportRequest] Failed to setup pipe for gzip"); + mStatus = -errno; + return mStatus; + } + int status = 0; + pid_t pid = fork_execute_cmd((char* const*)GZIP, mZipPipe.readFd().release(), mFd, &status); + if (pid < 0 || status != 0) { + mStatus = status; + return mStatus; + } + mZipPid = pid; + mFd = -1; + return OK; } // ================================================================================ @@ -364,7 +411,6 @@ void ReportWriter::startSection(int sectionId) { mSectionBufferSuccess = false; mHadError = false; mSectionErrors.clear(); - } void ReportWriter::setSectionStats(const FdBuffer& buffer) { @@ -470,10 +516,13 @@ status_t ReportWriter::writeSection(const FdBuffer& buffer) { // ================================================================================ -Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch) +Reporter::Reporter(const sp<WorkDirectory>& workDirectory, + const sp<ReportBatch>& batch, + const vector<BringYourOwnSection*>& registeredSections) :mWorkDirectory(workDirectory), mWriter(batch), - mBatch(batch) { + mBatch(batch), + mRegisteredSections(registeredSections) { } Reporter::~Reporter() { @@ -560,6 +609,13 @@ void Reporter::runReport(size_t* reportByteSize) { reportId = (spec.tv_sec) * 1000 + spec.tv_nsec; } + mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) { + status_t err = request->initGzipIfNecessary(); + if (err != 0) { + ALOGW("Error forking gzip: %s", strerror(err)); + } + }); + // Write the incident report headers - each request gets its own headers. It's different // from the other top-level fields in IncidentReport that are the sections where the rest // is all shared data (although with their own individual privacy filtering). @@ -580,50 +636,15 @@ void Reporter::runReport(size_t* reportByteSize) { // For each of the report fields, see if we need it, and if so, execute the command // and report to those that care that we're doing it. for (const Section** section = SECTION_LIST; *section; section++) { - const int sectionId = (*section)->id; - - // If nobody wants this section, skip it. - if (!mBatch->containsSection(sectionId)) { - continue; + if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) { + goto DONE; } + } - ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string()); - IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections(); - - // Notify listener of starting - mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { - listener->onReportSectionStatus( - sectionId, IIncidentReportStatusListener::STATUS_STARTING); - }); - - // Go get the data and write it into the file descriptors. - mWriter.startSection(sectionId); - err = (*section)->Execute(&mWriter); - mWriter.endSection(sectionMetadata); - - // Sections returning errors are fatal. Most errors should not be fatal. - if (err != NO_ERROR) { - mWriter.error((*section), err, "Section failed. Stopping report."); + for (const Section* section : mRegisteredSections) { + if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) { goto DONE; } - - // The returned max data size is used for throttling too many incident reports. - (*reportByteSize) += sectionMetadata->report_size_bytes(); - - // For any requests that failed during this section, remove them now. We do this - // before calling back about section finished, so listeners do not erroniously get the - // impression that the section succeeded. But we do it here instead of inside - // writeSection so that the callback is done from a known context and not from the - // bowels of a section, where changing the batch could cause odd errors. - cancel_and_remove_failed_requests(); - - // Notify listener of finishing - mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { - listener->onReportSectionStatus( - sectionId, IIncidentReportStatusListener::STATUS_FINISHED); - }); - - ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string()); } DONE: @@ -677,10 +698,59 @@ DONE: listener->onReportFailed(); }); } - + clear_buffer_pool(); ALOGI("Done taking incident report err=%s", strerror(-err)); } +status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata, + size_t* reportByteSize) { + const int sectionId = section->id; + + // If nobody wants this section, skip it. + if (!mBatch->containsSection(sectionId)) { + return NO_ERROR; + } + + ALOGD("Start incident report section %d '%s'", sectionId, section->name.string()); + IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections(); + + // Notify listener of starting + mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { + listener->onReportSectionStatus( + sectionId, IIncidentReportStatusListener::STATUS_STARTING); + }); + + // Go get the data and write it into the file descriptors. + mWriter.startSection(sectionId); + status_t err = section->Execute(&mWriter); + mWriter.endSection(sectionMetadata); + + // Sections returning errors are fatal. Most errors should not be fatal. + if (err != NO_ERROR) { + mWriter.error(section, err, "Section failed. Stopping report."); + return err; + } + + // The returned max data size is used for throttling too many incident reports. + (*reportByteSize) += sectionMetadata->report_size_bytes(); + + // For any requests that failed during this section, remove them now. We do this + // before calling back about section finished, so listeners do not erroniously get the + // impression that the section succeeded. But we do it here instead of inside + // writeSection so that the callback is done from a known context and not from the + // bowels of a section, where changing the batch could cause odd errors. + cancel_and_remove_failed_requests(); + + // Notify listener of finishing + mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { + listener->onReportSectionStatus( + sectionId, IIncidentReportStatusListener::STATUS_FINISHED); + }); + + ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string()); + return NO_ERROR; +} + void Reporter::cancel_and_remove_failed_requests() { // Handle a failure in the persisted file if (mPersistedFile != nullptr) { diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h index fb3961ab8b43..bd47a23d369e 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -15,12 +15,14 @@ */ #pragma once +#include "incidentd_util.h" #include "FdBuffer.h" #include "WorkDirectory.h" #include "frameworks/base/core/proto/android/os/metadata.pb.h" #include <android/content/ComponentName.h> #include <android/os/IIncidentReportStatusListener.h> +#include <android/os/IIncidentDumpCallback.h> #include <android/os/IncidentReportArgs.h> #include <android/util/protobuf.h> @@ -39,6 +41,7 @@ using namespace std; using namespace android::content; using namespace android::os; +class BringYourOwnSection; class Section; // ================================================================================ @@ -61,10 +64,12 @@ public: sp<IIncidentReportStatusListener> getListener() { return mListener; } - int getFd() { return mFd; } + int getFd(); int setPersistedFd(int fd); + status_t initGzipIfNecessary(); + void closeFd(); private: @@ -72,6 +77,8 @@ private: int mFd; bool mIsStreaming; status_t mStatus; + pid_t mZipPid; + Fpipe mZipPipe; }; // ================================================================================ @@ -122,7 +129,7 @@ public: void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func); /** - * Call func(request) for each file descriptor that has + * Call func(request) for each file descriptor. */ void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func); @@ -251,7 +258,9 @@ private: // ================================================================================ class Reporter : public virtual RefBase { public: - Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch); + Reporter(const sp<WorkDirectory>& workDirectory, + const sp<ReportBatch>& batch, + const vector<BringYourOwnSection*>& registeredSections); virtual ~Reporter(); @@ -263,6 +272,10 @@ private: ReportWriter mWriter; sp<ReportBatch> mBatch; sp<ReportFile> mPersistedFile; + const vector<BringYourOwnSection*>& mRegisteredSections; + + status_t execute_section(const Section* section, IncidentMetadata* metadata, + size_t* reportByteSize); void cancel_and_remove_failed_requests(); }; diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 6cbfd47bb660..e56ed39ea32e 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -20,9 +20,9 @@ #include <dirent.h> #include <errno.h> - #include <mutex> #include <set> +#include <thread> #include <android-base/file.h> #include <android-base/properties.h> @@ -35,12 +35,14 @@ #include <log/log_event_list.h> #include <log/logprint.h> #include <private/android_logger.h> +#include <sys/mman.h> #include "FdBuffer.h" #include "Privacy.h" #include "frameworks/base/core/proto/android/os/backtrace.proto.h" #include "frameworks/base/core/proto/android/os/data.proto.h" #include "frameworks/base/core/proto/android/util/log.proto.h" +#include "frameworks/base/core/proto/android/util/textdump.proto.h" #include "incidentd_util.h" namespace android { @@ -104,7 +106,6 @@ status_t FileSection::Execute(ReportWriter* writer) const { return NO_ERROR; } - FdBuffer buffer; Fpipe p2cPipe; Fpipe c2pPipe; // initiate pipes to pass data to/from incident_helper @@ -120,6 +121,7 @@ status_t FileSection::Execute(ReportWriter* writer) const { } // parent process + FdBuffer buffer; status_t readStatus = buffer.readProcessedDataInStream(fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs, mIsSysfs); @@ -134,7 +136,7 @@ status_t FileSection::Execute(ReportWriter* writer) const { status_t ihStatus = wait_child(pid); if (ihStatus != NO_ERROR) { ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-ihStatus)); - return ihStatus; + return OK; // Not a fatal error. } return writer->writeSection(buffer); @@ -233,7 +235,7 @@ struct WorkerThreadData : public virtual RefBase { Fpipe pipe; // Lock protects these fields - mutex lock; + std::mutex lock; bool workerDone; status_t workerError; @@ -260,78 +262,42 @@ void sigpipe_handler(int signum) { } } -static void* worker_thread_func(void* cookie) { - // Don't crash the service if we write to a closed pipe (which can happen if - // dumping times out). - signal(SIGPIPE, sigpipe_handler); - - WorkerThreadData* data = (WorkerThreadData*)cookie; - status_t err = data->section->BlockingCall(data->pipe.writeFd().get()); - - { - unique_lock<mutex> lock(data->lock); - data->workerDone = true; - data->workerError = err; - } - - data->pipe.writeFd().reset(); - data->decStrong(data->section); - // data might be gone now. don't use it after this point in this thread. - return NULL; -} - status_t WorkerThreadSection::Execute(ReportWriter* writer) const { - status_t err = NO_ERROR; - pthread_t thread; - pthread_attr_t attr; - bool workerDone = false; - FdBuffer buffer; - - // Data shared between this thread and the worker thread. + // Create shared data and pipe. Don't put data on the stack since this thread may exit early. sp<WorkerThreadData> data = new WorkerThreadData(this); - - // Create the pipe if (!data->pipe.init()) { return -errno; } - - // Create the thread - err = pthread_attr_init(&attr); - if (err != 0) { - return -err; - } - // TODO: Do we need to tweak thread priority? - err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - if (err != 0) { - pthread_attr_destroy(&attr); - return -err; - } - - // The worker thread needs a reference and we can't let the count go to zero - // if that thread is slow to start. data->incStrong(this); - - err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get()); - pthread_attr_destroy(&attr); - if (err != 0) { + std::thread([data, this]() { + // Don't crash the service if writing to a closed pipe (may happen if dumping times out) + signal(SIGPIPE, sigpipe_handler); + status_t err = data->section->BlockingCall(data->pipe.writeFd()); + { + std::scoped_lock<std::mutex> lock(data->lock); + data->workerDone = true; + data->workerError = err; + // unique_fd is not thread safe. If we don't lock it, reset() may pause half way while + // the other thread executes to the end, calling ~Fpipe, which is a race condition. + data->pipe.writeFd().reset(); + } data->decStrong(this); - return -err; - } + }).detach(); // Loop reading until either the timeout or the worker side is done (i.e. eof). + status_t err = NO_ERROR; + bool workerDone = false; + FdBuffer buffer; err = buffer.read(data->pipe.readFd().get(), this->timeoutMs); if (err != NO_ERROR) { ALOGE("[%s] reader failed with error '%s'", this->name.string(), strerror(-err)); } - // Done with the read fd. The worker thread closes the write one so - // we never race and get here first. - data->pipe.readFd().reset(); - // If the worker side is finished, then return its error (which may overwrite // our possible error -- but it's more interesting anyway). If not, then we timed out. { - unique_lock<mutex> lock(data->lock); + std::scoped_lock<std::mutex> lock(data->lock); + data->pipe.close(); if (data->workerError != NO_ERROR) { err = data->workerError; ALOGE("[%s] worker failed with error '%s'", this->name.string(), strerror(-err)); @@ -390,7 +356,6 @@ CommandSection::CommandSection(int id, const char* command, ...) : Section(id) { CommandSection::~CommandSection() { free(mCommand); } status_t CommandSection::Execute(ReportWriter* writer) const { - FdBuffer buffer; Fpipe cmdPipe; Fpipe ihPipe; @@ -411,6 +376,7 @@ status_t CommandSection::Execute(ReportWriter* writer) const { } cmdPipe.writeFd().reset(); + FdBuffer buffer; status_t readStatus = buffer.read(ihPipe.readFd().get(), this->timeoutMs); writer->setSectionStats(buffer); if (readStatus != NO_ERROR || buffer.timedOut()) { @@ -457,7 +423,7 @@ DumpsysSection::DumpsysSection(int id, const char* service, ...) DumpsysSection::~DumpsysSection() {} -status_t DumpsysSection::BlockingCall(int pipeWriteFd) const { +status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const { // checkService won't wait for the service to show up like getService will. sp<IBinder> service = defaultServiceManager()->checkService(mService); @@ -466,19 +432,120 @@ status_t DumpsysSection::BlockingCall(int pipeWriteFd) const { return NAME_NOT_FOUND; } - service->dump(pipeWriteFd, mArgs); + service->dump(pipeWriteFd.get(), mArgs); return NO_ERROR; } // ================================================================================ +TextDumpsysSection::TextDumpsysSection(int id, const char* service, ...) + :Section(id), mService(service) { + name = "dumpsys "; + name += service; + + va_list args; + va_start(args, service); + while (true) { + const char* arg = va_arg(args, const char*); + if (arg == NULL) { + break; + } + mArgs.add(String16(arg)); + name += " "; + name += arg; + } + va_end(args); +} + +TextDumpsysSection::~TextDumpsysSection() {} + +status_t TextDumpsysSection::Execute(ReportWriter* writer) const { + // checkService won't wait for the service to show up like getService will. + sp<IBinder> service = defaultServiceManager()->checkService(mService); + if (service == NULL) { + ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).string()); + return NAME_NOT_FOUND; + } + + // Create pipe + Fpipe dumpPipe; + if (!dumpPipe.init()) { + ALOGW("[%s] failed to setup pipe", this->name.string()); + return -errno; + } + + // Run dumping thread + const uint64_t start = Nanotime(); + std::thread worker([write_fd = std::move(dumpPipe.writeFd()), service = std::move(service), + this]() mutable { + // Don't crash the service if writing to a closed pipe (may happen if dumping times out) + signal(SIGPIPE, sigpipe_handler); + status_t err = service->dump(write_fd.get(), this->mArgs); + if (err != OK) { + ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err)); + } + write_fd.reset(); + }); + + // Collect dump content + FdBuffer buffer; + ProtoOutputStream proto; + proto.write(TextDumpProto::COMMAND, std::string(name.string())); + proto.write(TextDumpProto::DUMP_DURATION_NS, int64_t(Nanotime() - start)); + buffer.write(proto.data()); + + sp<EncodedBuffer> internalBuffer = buffer.data(); + internalBuffer->writeHeader((uint32_t)TextDumpProto::CONTENT, WIRE_TYPE_LENGTH_DELIMITED); + size_t editPos = internalBuffer->wp()->pos(); + internalBuffer->wp()->move(8); // reserve 8 bytes for the varint of the data size + size_t dataBeginPos = internalBuffer->wp()->pos(); + + status_t readStatus = buffer.read(dumpPipe.readFd(), this->timeoutMs); + dumpPipe.readFd().reset(); + writer->setSectionStats(buffer); + if (readStatus != OK || buffer.timedOut()) { + ALOGW("[%s] failed to read from dumpsys: %s, timedout: %s", this->name.string(), + strerror(-readStatus), buffer.timedOut() ? "true" : "false"); + worker.detach(); + return readStatus; + } + worker.join(); // wait for worker to finish + + // Revisit the actual size from dumpsys and edit the internal buffer accordingly. + size_t dumpSize = buffer.size() - dataBeginPos; + internalBuffer->wp()->rewind()->move(editPos); + internalBuffer->writeRawVarint32(dumpSize); + internalBuffer->copy(dataBeginPos, dumpSize); + + return writer->writeSection(buffer); +} + +// ================================================================================ // initialization only once in Section.cpp. map<log_id_t, log_time> LogSection::gLastLogsRetrieved; -LogSection::LogSection(int id, log_id_t logID) : WorkerThreadSection(id), mLogID(logID) { - name = "logcat "; - name += android_log_id_to_name(logID); - switch (logID) { +LogSection::LogSection(int id, const char* logID, ...) : WorkerThreadSection(id), mLogMode(logModeBase) { + name = "logcat -b "; + name += logID; + + va_list args; + va_start(args, logID); + mLogID = android_name_to_log_id(logID); + while(true) { + const char* arg = va_arg(args, const char*); + if (arg == NULL) { + break; + } + if (!strcmp(arg, "-L")) { + // Read from last logcat buffer + mLogMode = mLogMode | ANDROID_LOG_PSTORE; + } + name += " "; + name += arg; + } + va_end(args); + + switch (mLogID) { case LOG_ID_EVENTS: case LOG_ID_STATS: case LOG_ID_SECURITY: @@ -507,42 +574,51 @@ static inline int32_t get4LE(uint8_t const* src) { return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); } -status_t LogSection::BlockingCall(int pipeWriteFd) const { +status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const { + // heap profile shows that liblog malloc & free significant amount of memory in this process. + // Hence forking a new process to prevent memory fragmentation. + pid_t pid = fork(); + if (pid < 0) { + ALOGW("[%s] failed to fork", this->name.string()); + return errno; + } + if (pid > 0) { + return wait_child(pid, this->timeoutMs); + } // Open log buffer and getting logs since last retrieved time if any. unique_ptr<logger_list, void (*)(logger_list*)> loggers( gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end() - ? android_logger_list_alloc(ANDROID_LOG_NONBLOCK, 0, 0) - : android_logger_list_alloc_time(ANDROID_LOG_NONBLOCK, - gLastLogsRetrieved[mLogID], 0), + ? android_logger_list_alloc(mLogMode, 0, 0) + : android_logger_list_alloc_time(mLogMode, gLastLogsRetrieved[mLogID], 0), android_logger_list_free); if (android_logger_open(loggers.get(), mLogID) == NULL) { ALOGE("[%s] Can't get logger.", this->name.string()); - return -1; + _exit(EXIT_FAILURE); } log_msg msg; log_time lastTimestamp(0); ProtoOutputStream proto; + status_t err = OK; while (true) { // keeps reading until logd buffer is fully read. - status_t err = android_logger_list_read(loggers.get(), &msg); - // err = 0 - no content, unexpected connection drop or EOF. - // err = +ive number - size of retrieved data from logger - // err = -ive number, OS supplied error _except_ for -EAGAIN - // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data. - if (err <= 0) { - if (err != -EAGAIN) { + status_t status = android_logger_list_read(loggers.get(), &msg); + // status = 0 - no content, unexpected connection drop or EOF. + // status = +ive number - size of retrieved data from logger + // status = -ive number, OS supplied error _except_ for -EAGAIN + // status = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end. + if (status <= 0) { + if (status != -EAGAIN) { ALOGW("[%s] fails to read a log_msg.\n", this->name.string()); + err = -status; } - // dump previous logs and don't consider this error a failure. break; } if (mBinary) { // remove the first uint32 which is tag's index in event log tags android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t)); - ; android_log_list_element elem; lastTimestamp.tv_sec = msg.entry.sec; @@ -602,9 +678,10 @@ status_t LogSection::BlockingCall(int pipeWriteFd) const { } } else { AndroidLogEntry entry; - err = android_log_processLogBuffer(&msg.entry, &entry); - if (err != NO_ERROR) { + status = android_log_processLogBuffer(&msg.entry, &entry); + if (status != OK) { ALOGW("[%s] fails to process to an entry.\n", this->name.string()); + err = status; break; } lastTimestamp.tv_sec = entry.tv_sec; @@ -623,17 +700,24 @@ status_t LogSection::BlockingCall(int pipeWriteFd) const { trimTail(entry.message, entry.messageLen)); proto.end(token); } + if (!proto.flush(pipeWriteFd.get())) { + if (errno == EPIPE) { + ALOGW("[%s] wrote to a broken pipe\n", this->name.string()); + } + err = errno; + break; + } + proto.clear(); } gLastLogsRetrieved[mLogID] = lastTimestamp; - if (!proto.flush(pipeWriteFd) && errno == EPIPE) { - ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); - return EPIPE; - } - return NO_ERROR; + _exit(err); } // ================================================================================ +const int LINK_NAME_LEN = 64; +const int EXE_NAME_LEN = 1024; + TombstoneSection::TombstoneSection(int id, const char* type, const int64_t timeoutMs) : WorkerThreadSection(id, timeoutMs), mType(type) { name = "tombstone "; @@ -642,7 +726,7 @@ TombstoneSection::TombstoneSection(int id, const char* type, const int64_t timeo TombstoneSection::~TombstoneSection() {} -status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { +status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const { std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir); if (proc.get() == nullptr) { ALOGE("opendir /proc failed: %s\n", strerror(errno)); @@ -651,25 +735,37 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { const std::set<int> hal_pids = get_interesting_hal_pids(); - ProtoOutputStream proto; + auto pooledBuffer = get_buffer_from_pool(); + ProtoOutputStream proto(pooledBuffer); + // dumpBufferSize should be a multiple of page size (4 KB) to reduce memory fragmentation + size_t dumpBufferSize = 64 * 1024; // 64 KB is enough for most tombstone dump + char* dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); struct dirent* d; + char link_name[LINK_NAME_LEN]; + char exe_name[EXE_NAME_LEN]; status_t err = NO_ERROR; while ((d = readdir(proc.get()))) { int pid = atoi(d->d_name); if (pid <= 0) { continue; } - - const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid); - std::string exe; - if (!android::base::Readlink(link_name, &exe)) { - ALOGE("Section %s: Can't read '%s': %s\n", name.string(), - link_name.c_str(), strerror(errno)); + snprintf(link_name, LINK_NAME_LEN, "/proc/%d/exe", pid); + struct stat fileStat; + if (stat(link_name, &fileStat) != OK) { continue; } + ssize_t exe_name_len = readlink(link_name, exe_name, EXE_NAME_LEN); + if (exe_name_len < 0 || exe_name_len >= EXE_NAME_LEN) { + ALOGE("[%s] Can't read '%s': %s", name.string(), link_name, strerror(errno)); + continue; + } + // readlink(2) does not put a null terminator at the end + exe_name[exe_name_len] = '\0'; bool is_java_process; - if (exe == "/system/bin/app_process32" || exe == "/system/bin/app_process64") { + if (strncmp(exe_name, "/system/bin/app_process32", LINK_NAME_LEN) == 0 || + strncmp(exe_name, "/system/bin/app_process64", LINK_NAME_LEN) == 0) { if (mType != "java") continue; // Don't bother dumping backtraces for the zygote. if (IsZygote(pid)) { @@ -678,7 +774,7 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { } is_java_process = true; - } else if (should_dump_native_traces(exe.c_str())) { + } else if (should_dump_native_traces(exe_name)) { if (mType != "native") continue; is_java_process = false; } else if (hal_pids.find(pid) != hal_pids.end()) { @@ -734,32 +830,58 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { ALOGE("[%s] child had an issue: %s\n", this->name.string(), strerror(-cStatus)); } - auto dump = std::make_unique<char[]>(buffer.size()); + // Resize dump buffer + if (dumpBufferSize < buffer.size()) { + munmap(dumpBuffer, dumpBufferSize); + while(dumpBufferSize < buffer.size()) dumpBufferSize = dumpBufferSize << 1; + dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + } sp<ProtoReader> reader = buffer.data()->read(); int i = 0; while (reader->hasNext()) { - dump[i] = reader->next(); + dumpBuffer[i] = reader->next(); i++; } uint64_t token = proto.start(android::os::BackTraceProto::TRACES); proto.write(android::os::BackTraceProto::Stack::PID, pid); - proto.write(android::os::BackTraceProto::Stack::DUMP, dump.get(), i); + proto.write(android::os::BackTraceProto::Stack::DUMP, dumpBuffer, i); proto.write(android::os::BackTraceProto::Stack::DUMP_DURATION_NS, static_cast<long long>(Nanotime() - start)); proto.end(token); dumpPipe.readFd().reset(); - } - - if (!proto.flush(pipeWriteFd) && errno == EPIPE) { - ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); - if (err != NO_ERROR) { - return EPIPE; + if (!proto.flush(pipeWriteFd.get())) { + if (errno == EPIPE) { + ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); + } + err = errno; + break; } + proto.clear(); } - + munmap(dumpBuffer, dumpBufferSize); + return_buffer_to_pool(pooledBuffer); return err; } +// ================================================================================ +BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid, + const sp<IIncidentDumpCallback>& callback) + : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) { + name = "registered "; + name += customName; +} + +BringYourOwnSection::~BringYourOwnSection() {} + +status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const { + android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd)); + if(mCallback != nullptr) { + mCallback->onDumpSection(pfd); + } + return NO_ERROR; +} + } // namespace incidentd } // namespace os } // namespace android diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index f8649f8d21f1..698cc04c160e 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -23,6 +23,7 @@ #include <stdarg.h> #include <map> +#include <android/os/IIncidentDumpCallback.h> #include <log/log_read.h> #include <utils/String16.h> #include <utils/String8.h> @@ -90,7 +91,7 @@ public: virtual status_t Execute(ReportWriter* writer) const; - virtual status_t BlockingCall(int pipeWriteFd) const = 0; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0; }; /** @@ -111,14 +112,30 @@ private: }; /** - * Section that calls dumpsys on a system service. + * Section that calls protobuf dumpsys on a system service, usually + * "dumpsys [service_name] --proto". */ class DumpsysSection : public WorkerThreadSection { public: DumpsysSection(int id, const char* service, ...); virtual ~DumpsysSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; + +private: + String16 mService; + Vector<String16> mArgs; +}; + +/** + * Section that calls text dumpsys on a system service, usually "dumpsys [service_name]". + */ +class TextDumpsysSection : public Section { +public: + TextDumpsysSection(int id, const char* service, ...); + virtual ~TextDumpsysSection(); + + virtual status_t Execute(ReportWriter* writer) const; private: String16 mService; @@ -133,7 +150,7 @@ public: SystemPropertyDumpsysSection(int id, const char* service, ...); virtual ~SystemPropertyDumpsysSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; private: String16 mService; @@ -147,15 +164,19 @@ class LogSection : public WorkerThreadSection { // global last log retrieved timestamp for each log_id_t. static map<log_id_t, log_time> gLastLogsRetrieved; + // log mode: non blocking. + const static int logModeBase = ANDROID_LOG_NONBLOCK; + public: - LogSection(int id, log_id_t logID); + LogSection(int id, const char* logID, ...); virtual ~LogSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; private: log_id_t mLogID; bool mBinary; + int mLogMode; }; /** @@ -166,12 +187,29 @@ public: TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */); virtual ~TombstoneSection(); - virtual status_t BlockingCall(int pipeWriteFd) const; + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; private: std::string mType; }; +/** + * Section that gets data from a registered dump callback. + */ +class BringYourOwnSection : public WorkerThreadSection { +public: + const uid_t uid; + + BringYourOwnSection(int id, const char* customName, const uid_t callingUid, + const sp<IIncidentDumpCallback>& callback); + virtual ~BringYourOwnSection(); + + virtual status_t BlockingCall(unique_fd& pipeWriteFd) const; + +private: + const sp<IIncidentDumpCallback> mCallback; +}; + /** * These sections will not be generated when doing an 'all' report, either diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp index 9963533c08ac..1944d6ecc720 100644 --- a/cmds/incidentd/src/WorkDirectory.cpp +++ b/cmds/incidentd/src/WorkDirectory.cpp @@ -16,10 +16,10 @@ #include "Log.h" -#include "WorkDirectory.h" - +#include "incidentd_util.h" #include "proto_util.h" #include "PrivacyFilter.h" +#include "WorkDirectory.h" #include <google/protobuf/io/zero_copy_stream_impl.h> #include <private/android_filesystem_config.h> @@ -68,6 +68,9 @@ const ComponentName DROPBOX_SENTINEL("android", "DROPBOX"); /** metadata field id in IncidentProto */ const int FIELD_ID_INCIDENT_METADATA = 2; +// Args for exec gzip +static const char* GZIP[] = {"/system/bin/gzip", NULL}; + /** * Read a protobuf from disk into the message. */ @@ -292,6 +295,7 @@ void ReportFile::addReport(const IncidentReportArgs& args) { report->set_cls(args.receiverCls()); report->set_privacy_policy(args.getPrivacyPolicy()); report->set_all_sections(args.all()); + report->set_gzip(args.gzip()); for (int section: args.sections()) { report->add_section(section); } @@ -417,6 +421,24 @@ status_t ReportFile::startFilteringData(int writeFd, const IncidentReportArgs& a return BAD_VALUE; } + pid_t zipPid = 0; + if (args.gzip()) { + Fpipe zipPipe; + if (!zipPipe.init()) { + ALOGE("[ReportFile] Failed to setup pipe for gzip"); + close(writeFd); + return -errno; + } + int status = 0; + zipPid = fork_execute_cmd((char* const*)GZIP, zipPipe.readFd().release(), writeFd, &status); + close(writeFd); + if (zipPid < 0 || status != 0) { + ALOGE("[ReportFile] Failed to fork and exec gzip"); + return status; + } + writeFd = zipPipe.writeFd().release(); + } + status_t err; for (const auto& report : mEnvelope.report()) { @@ -437,6 +459,13 @@ status_t ReportFile::startFilteringData(int writeFd, const IncidentReportArgs& a } close(writeFd); + if (zipPid > 0) { + status_t err = wait_child(zipPid, /* timeout_ms= */ 10 * 1000); + if (err != 0) { + ALOGE("[ReportFile] abnormal child process: %s", strerror(-err)); + } + return err; + } return NO_ERROR; } @@ -621,7 +650,7 @@ void WorkDirectory::commitAll(const string& pkg) { map<string,WorkDirectoryEntry> files; get_directory_contents_locked(&files, 0); - + for (map<string,WorkDirectoryEntry>::iterator it = files.begin(); it != files.end(); it++) { sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs, @@ -815,6 +844,7 @@ void get_args_from_report(IncidentReportArgs* out, const ReportFileProto_Report& out->setAll(report.all_sections()); out->setReceiverPkg(report.pkg()); out->setReceiverCls(report.cls()); + out->setGzip(report.gzip()); const int sectionCount = report.section_size(); for (int i = 0; i < sectionCount; i++) { diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp index dfaf89392f90..150ab9991a2d 100644 --- a/cmds/incidentd/src/incidentd_util.cpp +++ b/cmds/incidentd/src/incidentd_util.cpp @@ -18,6 +18,8 @@ #include "incidentd_util.h" +#include <android/util/EncodedBuffer.h> +#include <fcntl.h> #include <sys/prctl.h> #include <wait.h> @@ -27,8 +29,6 @@ namespace android { namespace os { namespace incidentd { -using namespace android::base; - const Privacy* get_privacy_of_section(int id) { int l = 0; int r = PRIVACY_POLICY_COUNT - 1; @@ -47,6 +47,30 @@ const Privacy* get_privacy_of_section(int id) { return NULL; } +std::vector<sp<EncodedBuffer>> gBufferPool; +std::mutex gBufferPoolLock; + +sp<EncodedBuffer> get_buffer_from_pool() { + std::scoped_lock<std::mutex> lock(gBufferPoolLock); + if (gBufferPool.size() == 0) { + return new EncodedBuffer(); + } + sp<EncodedBuffer> buffer = gBufferPool.back(); + gBufferPool.pop_back(); + return buffer; +} + +void return_buffer_to_pool(sp<EncodedBuffer> buffer) { + buffer->clear(); + std::scoped_lock<std::mutex> lock(gBufferPoolLock); + gBufferPool.push_back(buffer); +} + +void clear_buffer_pool() { + std::scoped_lock<std::mutex> lock(gBufferPoolLock); + gBufferPool.clear(); +} + // ================================================================================ Fpipe::Fpipe() : mRead(), mWrite() {} @@ -64,28 +88,52 @@ unique_fd& Fpipe::readFd() { return mRead; } unique_fd& Fpipe::writeFd() { return mWrite; } -pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output) { - // fork used in multithreaded environment, avoid adding unnecessary code in child process - pid_t pid = fork(); +pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status) { + int in = -1; + if (input != nullptr) { + in = input->readFd().release(); + // Auto close write end of the input pipe on exec to prevent leaking fd in child process + fcntl(input->writeFd().get(), F_SETFD, FD_CLOEXEC); + } + int out = output->writeFd().release(); + // Auto close read end of the output pipe on exec + fcntl(output->readFd().get(), F_SETFD, FD_CLOEXEC); + return fork_execute_cmd(argv, in, out, status); +} + +pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status) { + int dummy_status = 0; + if (status == nullptr) { + status = &dummy_status; + } + *status = 0; + pid_t pid = vfork(); + if (pid < 0) { + *status = -errno; + return -1; + } if (pid == 0) { - if (input != NULL && (TEMP_FAILURE_RETRY(dup2(input->readFd().get(), STDIN_FILENO)) < 0 || - !input->close())) { + // In child + if (in >= 0 && (TEMP_FAILURE_RETRY(dup2(in, STDIN_FILENO)) < 0 || close(in))) { ALOGW("Failed to dup2 stdin."); _exit(EXIT_FAILURE); } - if (TEMP_FAILURE_RETRY(dup2(output->writeFd().get(), STDOUT_FILENO)) < 0 || - !output->close()) { + if (TEMP_FAILURE_RETRY(dup2(out, STDOUT_FILENO)) < 0 || close(out)) { ALOGW("Failed to dup2 stdout."); _exit(EXIT_FAILURE); } - /* make sure the child dies when incidentd dies */ + // Make sure the child dies when incidentd dies prctl(PR_SET_PDEATHSIG, SIGKILL); execvp(argv[0], argv); _exit(errno); // always exits with failure if any } - // close the fds used in child process. - if (input != NULL) input->readFd().reset(); - output->writeFd().reset(); + // In parent + if ((in >= 0 && close(in) < 0) || close(out) < 0) { + ALOGW("Failed to close pd. Killing child process"); + *status = -errno; + kill_child(pid); + return -1; + } return pid; } @@ -120,9 +168,6 @@ uint64_t Nanotime() { } // ================================================================================ -const int WAIT_MAX = 5; -const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000}; - static status_t statusCode(int status) { if (WIFSIGNALED(status)) { VLOG("return by signal: %s", strerror(WTERMSIG(status))); @@ -134,25 +179,64 @@ static status_t statusCode(int status) { return NO_ERROR; } +static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) { + sigset_t child_mask, old_mask; + sigemptyset(&child_mask); + sigaddset(&child_mask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) { + ALOGW("sigprocmask failed: %s", strerror(errno)); + return false; + } + + timespec ts; + ts.tv_sec = timeout_ms / 1000; + ts.tv_nsec = (timeout_ms % 1000) * 1000000; + int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts)); + int saved_errno = errno; + + // Set the signals back the way they were. + if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) { + ALOGW("sigprocmask failed: %s", strerror(errno)); + if (ret == 0) { + return false; + } + } + if (ret == -1) { + errno = saved_errno; + if (errno == EAGAIN) { + errno = ETIMEDOUT; + } else { + ALOGW("sigtimedwait failed: %s", strerror(errno)); + } + return false; + } + + pid_t child_pid = waitpid(pid, status, WNOHANG); + if (child_pid == pid) { + return true; + } + if (child_pid == -1) { + ALOGW("waitpid failed: %s", strerror(errno)); + } else { + ALOGW("Waiting for pid %d, got pid %d instead", pid, child_pid); + } + return false; +} + status_t kill_child(pid_t pid) { int status; - VLOG("try to kill child process %d", pid); kill(pid, SIGKILL); if (waitpid(pid, &status, 0) == -1) return -1; return statusCode(status); } -status_t wait_child(pid_t pid) { +status_t wait_child(pid_t pid, int timeout_ms) { int status; - bool died = false; - // wait for child to report status up to 1 seconds - for (int loop = 0; !died && loop < WAIT_MAX; loop++) { - if (waitpid(pid, &status, WNOHANG) == pid) died = true; - // sleep for 0.2 second - nanosleep(&WAIT_INTERVAL_NS, NULL); - } - if (!died) return kill_child(pid); - return statusCode(status); + if (waitpid_with_timeout(pid, timeout_ms, &status)) { + return statusCode(status); + } + return kill_child(pid); } } // namespace incidentd diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h index cc30768fa704..84998892e33c 100644 --- a/cmds/incidentd/src/incidentd_util.h +++ b/cmds/incidentd/src/incidentd_util.h @@ -19,18 +19,21 @@ #define INCIDENTD_UTIL_H #include <stdarg.h> -#include <unistd.h> - -#include <android-base/unique_fd.h> #include <utils/Errors.h> #include "Privacy.h" namespace android { + +namespace util { +class EncodedBuffer; +} + namespace os { namespace incidentd { -using namespace android::base; +using android::base::unique_fd; +using android::util::EncodedBuffer; /** * Looks up Privacy of a section in the auto-gen PRIVACY_POLICY_LIST; @@ -38,6 +41,25 @@ using namespace android::base; const Privacy* get_privacy_of_section(int id); /** + * Get an EncodedBuffer from an internal pool, or create and return a new one if the pool is empty. + * The EncodedBuffer should be returned after use. + * Thread safe. + */ +sp<EncodedBuffer> get_buffer_from_pool(); + +/** + * Return the EncodedBuffer back to the pool for reuse. + * Thread safe. + */ +void return_buffer_to_pool(sp<EncodedBuffer> buffer); + +/** + * Clear the buffer pool to free memory, after taking an incident report. + * Thread safe. + */ +void clear_buffer_pool(); + +/** * This class wraps android::base::Pipe. */ class Fpipe { @@ -56,11 +78,24 @@ private: }; /** - * Forks and exec a command with two pipes, one connects stdin for input, - * one connects stdout for output. It returns the pid of the child. - * Input pipe can be NULL to indicate child process doesn't read stdin. + * Forks and exec a command with two pipes and returns the pid of the child, or -1 when it fails. + * + * input connects stdin for input. output connects stdout for output. input can be nullptr to + * indicate that child process doesn't read stdin. This function will close in and out fds upon + * success. If status is not NULL, the status information will be stored in the int to which it + * points. + */ +pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status = nullptr); + +/** + * Forks and exec a command that reads from in fd and writes to out fd and returns the pid of the + * child, or -1 when it fails. + * + * in can be -1 to indicate that child process doesn't read stdin. This function will close in and + * out fds upon success. If status is not NULL, the status information will be stored in the int + * to which it points. */ -pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output); +pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status = nullptr); /** * Grabs varargs from stack and stores them in heap with NULL-terminated array. @@ -76,7 +111,7 @@ uint64_t Nanotime(); * Methods to wait or kill child process, return exit status code. */ status_t kill_child(pid_t pid); -status_t wait_child(pid_t pid); +status_t wait_child(pid_t pid, int timeout_ms = 1000); status_t start_detached_thread(const function<void ()>& func); diff --git a/cmds/incidentd/src/report_file.proto b/cmds/incidentd/src/report_file.proto index 7563da2c2148..85fd2da6c65c 100644 --- a/cmds/incidentd/src/report_file.proto +++ b/cmds/incidentd/src/report_file.proto @@ -65,6 +65,11 @@ message ReportFileProto { * the given client. */ optional bool share_approved = 8; + + /** + * Whether the report is gzipped. + */ + optional bool gzip = 9; } /** diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java index a0777459311b..08216d9b3f1d 100644 --- a/cmds/input/src/com/android/commands/input/Input.java +++ b/cmds/input/src/com/android/commands/input/Input.java @@ -430,6 +430,6 @@ public class Input extends BaseCommand { + " (Default: touchscreen)"); out.println(" press (Default: trackball)"); out.println(" roll <dx> <dy> (Default: trackball)"); - out.println(" event <DOWN|UP|MOVE> <x> <y> (Default: touchscreen)"); + out.println(" motionevent <DOWN|UP|MOVE> <x> <y> (Default: touchscreen)"); } } diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING new file mode 100644 index 000000000000..56f5cc034f05 --- /dev/null +++ b/cmds/locksettings/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit-devicepolicy": [ + { + "name": "CtsDevicePolicyManagerTestCases", + "options": [ + { + "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + } + ] + } + ] +} diff --git a/cmds/media/Android.bp b/cmds/media/Android.bp deleted file mode 100644 index 7879c53684a7..000000000000 --- a/cmds/media/Android.bp +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2013 The Android Open Source Project -// - -java_binary { - name: "media", - wrapper: "media", - srcs: ["**/*.java"], -} diff --git a/cmds/media/MODULE_LICENSE_APACHE2 b/cmds/media/MODULE_LICENSE_APACHE2 deleted file mode 100644 index e69de29bb2d1..000000000000 --- a/cmds/media/MODULE_LICENSE_APACHE2 +++ /dev/null diff --git a/cmds/media/NOTICE b/cmds/media/NOTICE deleted file mode 100644 index c5b1efa7aac7..000000000000 --- a/cmds/media/NOTICE +++ /dev/null @@ -1,190 +0,0 @@ - - Copyright (c) 2005-2008, 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. - - 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. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - diff --git a/cmds/media/media b/cmds/media/media deleted file mode 100755 index 00c3915f2e65..000000000000 --- a/cmds/media/media +++ /dev/null @@ -1,3 +0,0 @@ -#!/system/bin/sh -export CLASSPATH=/system/framework/media.jar -exec app_process /system/bin com.android.commands.media.Media "$@" diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java deleted file mode 100644 index 1e915f8232d3..000000000000 --- a/cmds/media/src/com/android/commands/media/Media.java +++ /dev/null @@ -1,348 +0,0 @@ -/* -** -** Copyright 2013, 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 com.android.commands.media; - -import android.app.ActivityThread; -import android.content.Context; -import android.media.MediaMetadata; -import android.media.session.ISessionManager; -import android.media.session.MediaController; -import android.media.session.MediaController.PlaybackInfo; -import android.media.session.MediaSession.QueueItem; -import android.media.session.MediaSessionManager; -import android.media.session.PlaybackState; -import android.os.Bundle; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.util.AndroidException; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; - -import com.android.internal.os.BaseCommand; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.util.List; - -public class Media extends BaseCommand { - // This doesn't belongs to any package. Setting the package name to empty string. - private static final String PACKAGE_NAME = ""; - private static ActivityThread sThread; - private static MediaSessionManager sMediaSessionManager; - private ISessionManager mSessionService; - - /** - * Command-line entry point. - * - * @param args The command-line arguments - */ - public static void main(String[] args) { - (new Media()).run(args); - } - - @Override - public void onShowUsage(PrintStream out) { - out.println( - "usage: media [subcommand] [options]\n" + - " media dispatch KEY\n" + - " media list-sessions\n" + - " media monitor <tag>\n" + - " media volume [options]\n" + - "\n" + - "media dispatch: dispatch a media key to the system.\n" + - " KEY may be: play, pause, play-pause, mute, headsethook,\n" + - " stop, next, previous, rewind, record, fast-forword.\n" + - "media list-sessions: print a list of the current sessions.\n" + - "media monitor: monitor updates to the specified session.\n" + - " Use the tag from list-sessions.\n" + - "media volume: " + VolumeCtrl.USAGE - ); - } - - @Override - public void onRun() throws Exception { - if (sThread == null) { - Looper.prepareMainLooper(); - sThread = ActivityThread.systemMain(); - Context context = sThread.getSystemContext(); - sMediaSessionManager = - (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); - } - mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService( - Context.MEDIA_SESSION_SERVICE)); - if (mSessionService == null) { - System.err.println(NO_SYSTEM_ERROR_CODE); - throw new AndroidException( - "Can't connect to media session service; is the system running?"); - } - - String op = nextArgRequired(); - - if (op.equals("dispatch")) { - runDispatch(); - } else if (op.equals("list-sessions")) { - runListSessions(); - } else if (op.equals("monitor")) { - runMonitor(); - } else if (op.equals("volume")) { - runVolume(); - } else { - showError("Error: unknown command '" + op + "'"); - return; - } - } - - private void sendMediaKey(KeyEvent event) { - try { - mSessionService.dispatchMediaKeyEvent(PACKAGE_NAME, false, event, false); - } catch (RemoteException e) { - } - } - - private void runMonitor() throws Exception { - String id = nextArgRequired(); - if (id == null) { - showError("Error: must include a session id"); - return; - } - - boolean success = false; - try { - List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null); - for (MediaController controller : controllers) { - try { - if (controller != null && id.equals(controller.getTag())) { - ControllerMonitor monitor = new ControllerMonitor(controller); - monitor.run(); - success = true; - break; - } - } catch (RemoteException e) { - // ignore - } - } - } catch (Exception e) { - System.out.println("***Error monitoring session*** " + e.getMessage()); - } - if (!success) { - System.out.println("No session found with id " + id); - } - } - - private void runDispatch() throws Exception { - String cmd = nextArgRequired(); - int keycode; - if ("play".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_PLAY; - } else if ("pause".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_PAUSE; - } else if ("play-pause".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE; - } else if ("mute".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MUTE; - } else if ("headsethook".equals(cmd)) { - keycode = KeyEvent.KEYCODE_HEADSETHOOK; - } else if ("stop".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_STOP; - } else if ("next".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_NEXT; - } else if ("previous".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS; - } else if ("rewind".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_REWIND; - } else if ("record".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_RECORD; - } else if ("fast-forward".equals(cmd)) { - keycode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; - } else { - showError("Error: unknown dispatch code '" + cmd + "'"); - return; - } - final long now = SystemClock.uptimeMillis(); - sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); - sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); - } - - class ControllerCallback extends MediaController.Callback { - @Override - public void onSessionDestroyed() { - System.out.println("onSessionDestroyed. Enter q to quit."); - } - - @Override - public void onSessionEvent(String event, Bundle extras) { - System.out.println("onSessionEvent event=" + event + ", extras=" + extras); - } - - @Override - public void onPlaybackStateChanged(PlaybackState state) { - System.out.println("onPlaybackStateChanged " + state); - } - - @Override - public void onMetadataChanged(MediaMetadata metadata) { - String mmString = metadata == null ? null : "title=" + metadata - .getDescription(); - System.out.println("onMetadataChanged " + mmString); - } - - @Override - public void onQueueChanged(List<QueueItem> queue) { - System.out.println("onQueueChanged, " - + (queue == null ? "null queue" : " size=" + queue.size())); - } - - @Override - public void onQueueTitleChanged(CharSequence title) { - System.out.println("onQueueTitleChange " + title); - } - - @Override - public void onExtrasChanged(Bundle extras) { - System.out.println("onExtrasChanged " + extras); - } - - @Override - public void onAudioInfoChanged(PlaybackInfo info) { - System.out.println("onAudioInfoChanged " + info); - } - } - - private class ControllerMonitor { - private final MediaController mController; - private final ControllerCallback mControllerCallback; - - ControllerMonitor(MediaController controller) { - mController = controller; - mControllerCallback = new ControllerCallback(); - } - - void printUsageMessage() { - try { - System.out.println("V2Monitoring session " + mController.getTag() - + "... available commands: play, pause, next, previous"); - } catch (RuntimeException e) { - System.out.println("Error trying to monitor session!"); - } - System.out.println("(q)uit: finish monitoring"); - } - - void run() throws RemoteException { - printUsageMessage(); - HandlerThread cbThread = new HandlerThread("MediaCb") { - @Override - protected void onLooperPrepared() { - try { - mController.registerCallback(mControllerCallback); - } catch (RuntimeException e) { - System.out.println("Error registering monitor callback"); - } - } - }; - cbThread.start(); - - try { - InputStreamReader converter = new InputStreamReader(System.in); - BufferedReader in = new BufferedReader(converter); - String line; - - while ((line = in.readLine()) != null) { - boolean addNewline = true; - if (line.length() <= 0) { - addNewline = false; - } else if ("q".equals(line) || "quit".equals(line)) { - break; - } else if ("play".equals(line)) { - dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY); - } else if ("pause".equals(line)) { - dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE); - } else if ("next".equals(line)) { - dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT); - } else if ("previous".equals(line)) { - dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS); - } else { - System.out.println("Invalid command: " + line); - } - - synchronized (this) { - if (addNewline) { - System.out.println(""); - } - printUsageMessage(); - } - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - cbThread.getLooper().quit(); - try { - mController.unregisterCallback(mControllerCallback); - } catch (Exception e) { - // ignoring - } - } - } - - private void dispatchKeyCode(int keyCode) { - final long now = SystemClock.uptimeMillis(); - KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); - KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); - try { - mController.dispatchMediaButtonEvent(down); - mController.dispatchMediaButtonEvent(up); - } catch (RuntimeException e) { - System.out.println("Failed to dispatch " + keyCode); - } - } - } - - private void runListSessions() { - System.out.println("Sessions:"); - try { - List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null); - for (MediaController controller : controllers) { - if (controller != null) { - try { - System.out.println(" tag=" + controller.getTag() - + ", package=" + controller.getPackageName()); - } catch (RuntimeException e) { - // ignore - } - } - } - } catch (Exception e) { - System.out.println("***Error listing sessions***"); - } - } - - //================================= - // "volume" command for stream volume control - private void runVolume() throws Exception { - VolumeCtrl.run(this); - } -} diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/cmds/media/src/com/android/commands/media/VolumeCtrl.java deleted file mode 100755 index 1629c6f178f0..000000000000 --- a/cmds/media/src/com/android/commands/media/VolumeCtrl.java +++ /dev/null @@ -1,185 +0,0 @@ -/* -** -** Copyright 2016, 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 com.android.commands.media; - -import android.content.Context; -import android.media.AudioManager; -import android.media.AudioSystem; -import android.media.IAudioService; -import android.os.ServiceManager; -import android.util.AndroidException; - -import com.android.internal.os.BaseCommand; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.lang.ArrayIndexOutOfBoundsException; - -/** - * Command line tool to exercise AudioService.setStreamVolume() - * and AudioService.adjustStreamVolume() - */ -public class VolumeCtrl { - - private final static String TAG = "VolumeCtrl"; - - // --stream affects --set, --adj or --get options. - // --show affects --set and --adj options. - // --get can be used with --set, --adj or by itself. - public final static String USAGE = new String( - "the options are as follows: \n" + - "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" + - "\t\t controls AudioManager.STREAM_MUSIC if no stream is specified\n"+ - "\t\t--set INDEX sets the volume index value\n" + - "\t\t--adj DIRECTION adjusts the volume, use raise|same|lower for the direction\n" + - "\t\t--get outputs the current volume\n" + - "\t\t--show shows the UI during the volume change\n" + - "\texamples:\n" + - "\t\tadb shell media volume --show --stream 3 --set 11\n" + - "\t\tadb shell media volume --stream 0 --adj lower\n" + - "\t\tadb shell media volume --stream 3 --get\n" - ); - - private final static int VOLUME_CONTROL_MODE_SET = 1; - private final static int VOLUME_CONTROL_MODE_ADJUST = 2; - - private final static String ADJUST_LOWER = "lower"; - private final static String ADJUST_SAME = "same"; - private final static String ADJUST_RAISE = "raise"; - - public static void run(BaseCommand cmd) throws Exception { - //---------------------------------------- - // Default parameters - int stream = AudioManager.STREAM_MUSIC; - int volIndex = 5; - int mode = 0; - int adjDir = AudioManager.ADJUST_RAISE; - boolean showUi = false; - boolean doGet = false; - - //---------------------------------------- - // read options - String option; - String adjustment = null; - while ((option = cmd.nextOption()) != null) { - switch (option) { - case "--show": - showUi = true; - break; - case "--get": - doGet = true; - log(LOG_V, "will get volume"); - break; - case "--stream": - stream = Integer.decode(cmd.nextArgRequired()).intValue(); - log(LOG_V, "will control stream=" + stream + " (" + streamName(stream) + ")"); - break; - case "--set": - volIndex = Integer.decode(cmd.nextArgRequired()).intValue(); - mode = VOLUME_CONTROL_MODE_SET; - log(LOG_V, "will set volume to index=" + volIndex); - break; - case "--adj": - mode = VOLUME_CONTROL_MODE_ADJUST; - adjustment = cmd.nextArgRequired(); - log(LOG_V, "will adjust volume"); - break; - default: - throw new IllegalArgumentException("Unknown argument " + option); - } - } - - //------------------------------ - // Read options: validation - if (mode == VOLUME_CONTROL_MODE_ADJUST) { - if (adjustment == null) { - cmd.showError("Error: no valid volume adjustment (null)"); - return; - } - switch (adjustment) { - case ADJUST_RAISE: adjDir = AudioManager.ADJUST_RAISE; break; - case ADJUST_SAME: adjDir = AudioManager.ADJUST_SAME; break; - case ADJUST_LOWER: adjDir = AudioManager.ADJUST_LOWER; break; - default: - cmd.showError("Error: no valid volume adjustment, was " + adjustment - + ", expected " + ADJUST_LOWER + "|" + ADJUST_SAME + "|" - + ADJUST_RAISE); - return; - } - } - - //---------------------------------------- - // Test initialization - log(LOG_V, "Connecting to AudioService"); - IAudioService audioService = IAudioService.Stub.asInterface(ServiceManager.checkService( - Context.AUDIO_SERVICE)); - if (audioService == null) { - System.err.println(BaseCommand.NO_SYSTEM_ERROR_CODE); - throw new AndroidException( - "Can't connect to audio service; is the system running?"); - } - - if (mode == VOLUME_CONTROL_MODE_SET) { - if ((volIndex > audioService.getStreamMaxVolume(stream)) - || (volIndex < audioService.getStreamMinVolume(stream))) { - cmd.showError(String.format("Error: invalid volume index %d for stream %d " - + "(should be in [%d..%d])", volIndex, stream, - audioService.getStreamMinVolume(stream), - audioService.getStreamMaxVolume(stream))); - return; - } - } - - //---------------------------------------- - // Non-interactive test - final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0; - final String pack = cmd.getClass().getPackage().getName(); - if (mode == VOLUME_CONTROL_MODE_SET) { - audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/); - } else if (mode == VOLUME_CONTROL_MODE_ADJUST) { - audioService.adjustStreamVolume(stream, adjDir, flag, pack); - } - if (doGet) { - log(LOG_V, "volume is " + audioService.getStreamVolume(stream) + - " in range [" + audioService.getStreamMinVolume(stream) + - ".." + audioService.getStreamMaxVolume(stream) + "]"); - } - } - - //-------------------------------------------- - // Utilities - - static final String LOG_V = "[v]"; - static final String LOG_W = "[w]"; - static final String LOG_OK = "[ok]"; - - static void log(String code, String msg) { - System.out.println(code + " " + msg); - } - - static String streamName(int stream) { - try { - return AudioSystem.STREAM_NAMES[stream]; - } catch (ArrayIndexOutOfBoundsException e) { - return "invalid stream"; - } - } - -} diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 4410f1c4570c..bb32dd2fa7ad 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -105,7 +105,7 @@ static status_t notifyMediaScanner(const char* fileName) { char *cmd[] = { (char*) "am", (char*) "broadcast", - (char*) "am", + (char*) "-a", (char*) "android.intent.action.MEDIA_SCANNER_SCAN_FILE", (char*) "-d", &filePath[0], diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index e494ea3b03c2..0617eb6c0e66 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -44,19 +44,9 @@ cc_library_host_shared { cc_defaults { name: "statsd_defaults", - aidl: { - include_dirs: ["frameworks/base/core/java"], - }, srcs: [ - ":statsd_aidl", - ":ICarStatsService.aidl", "src/active_config_list.proto", - "src/statsd_config.proto", - "src/uid_data.proto", - "src/FieldValue.cpp", - "src/hash.cpp", - "src/stats_log_util.cpp", "src/anomaly/AlarmMonitor.cpp", "src/anomaly/AlarmTracker.cpp", "src/anomaly/AnomalyTracker.cpp", @@ -64,52 +54,57 @@ cc_defaults { "src/anomaly/subscriber_util.cpp", "src/condition/CombinationConditionTracker.cpp", "src/condition/condition_util.cpp", - "src/condition/SimpleConditionTracker.cpp", "src/condition/ConditionWizard.cpp", - "src/condition/StateTracker.cpp", + "src/condition/SimpleConditionTracker.cpp", "src/config/ConfigKey.cpp", "src/config/ConfigListener.cpp", "src/config/ConfigManager.cpp", - "src/external/CarStatsPuller.cpp", - "src/external/GpuStatsPuller.cpp", + "src/experiment_ids.proto", "src/external/Perfetto.cpp", - "src/external/StatsPuller.cpp", + "src/external/PullResultReceiver.cpp", + "src/external/puller_util.cpp", "src/external/StatsCallbackPuller.cpp", - "src/external/StatsCompanionServicePuller.cpp", - "src/external/SubsystemSleepStatePuller.cpp", - "src/external/PowerStatsPuller.cpp", - "src/external/ResourceHealthManagerPuller.cpp", - "src/external/TrainInfoPuller.cpp", + "src/external/StatsPuller.cpp", "src/external/StatsPullerManager.cpp", - "src/external/puller_util.cpp", + "src/external/TrainInfoPuller.cpp", + "src/FieldValue.cpp", + "src/guardrail/StatsdStats.cpp", + "src/hash.cpp", + "src/HashableDimensionKey.cpp", "src/logd/LogEvent.cpp", "src/logd/LogEventQueue.cpp", "src/matchers/CombinationLogMatchingTracker.cpp", "src/matchers/EventMatcherWizard.cpp", "src/matchers/matcher_util.cpp", "src/matchers/SimpleLogMatchingTracker.cpp", - "src/metrics/MetricProducer.cpp", - "src/metrics/EventMetricProducer.cpp", + "src/metadata_util.cpp", "src/metrics/CountMetricProducer.cpp", - "src/metrics/DurationMetricProducer.cpp", - "src/metrics/duration_helper/OringDurationTracker.cpp", "src/metrics/duration_helper/MaxDurationTracker.cpp", - "src/metrics/ValueMetricProducer.cpp", + "src/metrics/duration_helper/OringDurationTracker.cpp", + "src/metrics/DurationMetricProducer.cpp", + "src/metrics/EventMetricProducer.cpp", "src/metrics/GaugeMetricProducer.cpp", - "src/metrics/MetricsManager.cpp", + "src/metrics/MetricProducer.cpp", "src/metrics/metrics_manager_util.cpp", + "src/metrics/MetricsManager.cpp", + "src/metrics/ValueMetricProducer.cpp", "src/packages/UidMap.cpp", - "src/storage/StorageManager.cpp", + "src/shell/shell_config.proto", + "src/shell/ShellSubscriber.cpp", + "src/socket/StatsSocketListener.cpp", + "src/state/StateManager.cpp", + "src/state/StateTracker.cpp", + "src/stats_log_util.cpp", + "src/statscompanion_util.cpp", + "src/statsd_config.proto", + "src/statsd_metadata.proto", "src/StatsLogProcessor.cpp", "src/StatsService.cpp", - "src/statscompanion_util.cpp", + "src/storage/StorageManager.cpp", "src/subscriber/IncidentdReporter.cpp", "src/subscriber/SubscriberReporter.cpp", - "src/HashableDimensionKey.cpp", - "src/guardrail/StatsdStats.cpp", - "src/socket/StatsSocketListener.cpp", - "src/shell/ShellSubscriber.cpp", - "src/shell/shell_config.proto", + "src/uid_data.proto", + "src/utils/MultiConditionTrigger.cpp", ], local_include_dirs: [ @@ -117,78 +112,81 @@ cc_defaults { ], static_libs: [ - "libhealthhalutils", + "libbase", + "libcutils", + "libgtest_prod", + "libprotoutil", + "libstatslog_statsd", + "libsysutils", + "libutils", + "statsd-aidl-ndk_platform", ], - shared_libs: [ - "libbase", - "libbinder", - "libgraphicsenv", + "libbinder_ndk", "libincident", "liblog", - "libutils", - "libservices", - "libprotoutil", - "libstatslog", - "libhardware", - "libhardware_legacy", - "libhidlbase", - "android.frameworks.stats@1.0", - "android.hardware.health@2.0", - "android.hardware.power@1.0", - "android.hardware.power@1.1", - "android.hardware.power.stats@1.0", - "libpackagelistparser", - "libstatsmetadata", - "libsysutils", - "libcutils", ], } -// ================ -// libstatsmetadata -// ================ - genrule { - name: "atoms_info.h", + name: "statslog_statsd.h", tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h", + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsd.h --module statsd --namespace android,os,statsd,util", out: [ - "atoms_info.h", + "statslog_statsd.h", ], } genrule { - name: "atoms_info.cpp", + name: "statslog_statsd.cpp", tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp", + cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsd.cpp --module statsd --namespace android,os,statsd,util --importHeader statslog_statsd.h", out: [ - "atoms_info.cpp", + "statslog_statsd.cpp", ], } -cc_library_shared { - name: "libstatsmetadata", - host_supported: true, - generated_sources: [ - "atoms_info.cpp", - ], - generated_headers: [ - "atoms_info.h", - ], - cflags: [ - "-Wall", - "-Werror", +genrule { + name: "statslog_statsdtest.h", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsdtest.h --module statsdtest --namespace android,os,statsd,util", + out: [ + "statslog_statsdtest.h", ], - export_generated_headers: [ - "atoms_info.h", +} + +genrule { + name: "statslog_statsdtest.cpp", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsdtest.cpp --module statsdtest --namespace android,os,statsd,util --importHeader statslog_statsdtest.h", + out: [ + "statslog_statsdtest.cpp", ], +} + +cc_library_static { + name: "libstatslog_statsdtest", + generated_sources: ["statslog_statsdtest.cpp"], + generated_headers: ["statslog_statsdtest.h"], + export_generated_headers: ["statslog_statsdtest.h"], shared_libs: [ - "libcutils", - "libstatslog", - ], + "libstatssocket", + ] } +cc_library_static { + name: "libstatslog_statsd", + generated_sources: ["statslog_statsd.cpp"], + generated_headers: ["statslog_statsd.h"], + export_generated_headers: ["statslog_statsd.h"], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], + shared_libs: [ + "libstatssocket", + ] +} // ========= // statsd @@ -226,11 +224,18 @@ cc_binary { proto: { type: "lite", + static: true, }, + stl: "libc++_static", - shared_libs: ["libgtest_prod"], + shared_libs: [ + "libstatssocket", + ], - init_rc: ["statsd.rc"], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], } // ============== @@ -240,7 +245,20 @@ cc_binary { cc_test { name: "statsd_test", defaults: ["statsd_defaults"], - test_suites: ["device-tests"], + test_suites: ["device-tests", "mts"], + test_config: "statsd_test.xml", + + //TODO(b/153588990): Remove when the build system properly separates + //32bit and 64bit architectures. + compile_multilib: "both", + multilib: { + lib64: { + suffix: "64", + }, + lib32: { + suffix: "32", + }, + }, cflags: [ "-Wall", @@ -251,6 +269,8 @@ cc_test { "-Wno-unused-parameter", ], + require_root: true, + srcs: [ // atom_field_options.proto needs field_options.proto, but that is // not included in libprotobuf-cpp-lite, so compile it here. @@ -258,62 +278,65 @@ cc_test { "src/atom_field_options.proto", "src/atoms.proto", - "src/stats_log.proto", "src/shell/shell_data.proto", + "src/stats_log.proto", "tests/AlarmMonitor_test.cpp", "tests/anomaly/AlarmTracker_test.cpp", "tests/anomaly/AnomalyTracker_test.cpp", + "tests/condition/CombinationConditionTracker_test.cpp", + "tests/condition/ConditionTimer_test.cpp", + "tests/condition/SimpleConditionTracker_test.cpp", "tests/ConfigManager_test.cpp", + "tests/e2e/Alarm_e2e_test.cpp", + "tests/e2e/Anomaly_count_e2e_test.cpp", + "tests/e2e/Anomaly_duration_sum_e2e_test.cpp", + "tests/e2e/Attribution_e2e_test.cpp", + "tests/e2e/ConfigTtl_e2e_test.cpp", + "tests/e2e/CountMetric_e2e_test.cpp", + "tests/e2e/DurationMetric_e2e_test.cpp", + "tests/e2e/GaugeMetric_e2e_pull_test.cpp", + "tests/e2e/GaugeMetric_e2e_push_test.cpp", + "tests/e2e/MetricActivation_e2e_test.cpp", + "tests/e2e/MetricConditionLink_e2e_test.cpp", + "tests/e2e/PartialBucket_e2e_test.cpp", + "tests/e2e/ValueMetric_pull_e2e_test.cpp", + "tests/e2e/WakelockDuration_e2e_test.cpp", "tests/external/puller_util_test.cpp", - "tests/external/GpuStatsPuller_test.cpp", - "tests/external/IncidentReportArgs_test.cpp", + "tests/external/StatsCallbackPuller_test.cpp", "tests/external/StatsPuller_test.cpp", + "tests/external/StatsPullerManager_test.cpp", + "tests/FieldValue_test.cpp", + "tests/guardrail/StatsdStats_test.cpp", + "tests/HashableDimensionKey_test.cpp", "tests/indexed_priority_queue_test.cpp", + "tests/log_event/LogEventQueue_test.cpp", "tests/LogEntryMatcher_test.cpp", "tests/LogEvent_test.cpp", - "tests/log_event/LogEventQueue_test.cpp", - "tests/MetricsManager_test.cpp", - "tests/StatsLogProcessor_test.cpp", - "tests/StatsService_test.cpp", - "tests/UidMap_test.cpp", - "tests/FieldValue_test.cpp", - "tests/condition/CombinationConditionTracker_test.cpp", - "tests/condition/SimpleConditionTracker_test.cpp", - "tests/condition/StateTracker_test.cpp", - "tests/condition/ConditionTimer_test.cpp", - "tests/metrics/OringDurationTracker_test.cpp", - "tests/metrics/MaxDurationTracker_test.cpp", + "tests/metadata_util_test.cpp", "tests/metrics/CountMetricProducer_test.cpp", "tests/metrics/DurationMetricProducer_test.cpp", "tests/metrics/EventMetricProducer_test.cpp", - "tests/metrics/ValueMetricProducer_test.cpp", "tests/metrics/GaugeMetricProducer_test.cpp", - "tests/guardrail/StatsdStats_test.cpp", + "tests/metrics/MaxDurationTracker_test.cpp", "tests/metrics/metrics_test_helper.cpp", + "tests/metrics/OringDurationTracker_test.cpp", + "tests/metrics/ValueMetricProducer_test.cpp", + "tests/MetricsManager_test.cpp", + "tests/shell/ShellSubscriber_test.cpp", + "tests/state/StateTracker_test.cpp", "tests/statsd_test_util.cpp", + "tests/StatsLogProcessor_test.cpp", + "tests/StatsService_test.cpp", "tests/storage/StorageManager_test.cpp", - "tests/e2e/WakelockDuration_e2e_test.cpp", - "tests/e2e/MetricActivation_e2e_test.cpp", - "tests/e2e/MetricConditionLink_e2e_test.cpp", - "tests/e2e/Alarm_e2e_test.cpp", - "tests/e2e/Attribution_e2e_test.cpp", - "tests/e2e/GaugeMetric_e2e_push_test.cpp", - "tests/e2e/GaugeMetric_e2e_pull_test.cpp", - "tests/e2e/ValueMetric_pull_e2e_test.cpp", - "tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp", - "tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp", - "tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp", - "tests/e2e/Anomaly_count_e2e_test.cpp", - "tests/e2e/Anomaly_duration_sum_e2e_test.cpp", - "tests/e2e/ConfigTtl_e2e_test.cpp", - "tests/e2e/PartialBucket_e2e_test.cpp", - "tests/e2e/DurationMetric_e2e_test.cpp", - "tests/shell/ShellSubscriber_test.cpp", + "tests/UidMap_test.cpp", + "tests/utils/MultiConditionTrigger_test.cpp", ], static_libs: [ "libgmock", "libplatformprotos", + "libstatslog_statsdtest", + "libstatssocket_private", ], proto: { @@ -321,8 +344,6 @@ cc_test { include_dirs: ["external/protobuf/src"], }, - shared_libs: ["libprotobuf-cpp-lite"], - } //############################# @@ -338,17 +359,17 @@ cc_benchmark { // not included in libprotobuf-cpp-lite, so compile it here. ":libprotobuf-internal-protos", - "src/atom_field_options.proto", - "src/atoms.proto", - "src/stats_log.proto", - "benchmark/main.cpp", - "benchmark/hello_world_benchmark.cpp", - "benchmark/log_event_benchmark.cpp", - "benchmark/stats_write_benchmark.cpp", + "benchmark/duration_metric_benchmark.cpp", "benchmark/filter_value_benchmark.cpp", "benchmark/get_dimensions_for_condition_benchmark.cpp", + "benchmark/hello_world_benchmark.cpp", + "benchmark/log_event_benchmark.cpp", + "benchmark/main.cpp", "benchmark/metric_util.cpp", - "benchmark/duration_metric_benchmark.cpp", + "benchmark/stats_write_benchmark.cpp", + "src/atom_field_options.proto", + "src/atoms.proto", + "src/stats_log.proto", ], proto: { @@ -364,17 +385,18 @@ cc_benchmark { "-Wno-unused-function", // Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 - "-Wno-varargs" + "-Wno-varargs", ], static_libs: [ "libplatformprotos", + "libstatssocket_private", ], shared_libs: [ "libgtest_prod", - "libstatslog", "libprotobuf-cpp-lite", + "libstatslog", ], } @@ -388,11 +410,11 @@ java_library { }, srcs: [ - "src/stats_log.proto", - "src/statsd_config.proto", "src/atoms.proto", "src/shell/shell_config.proto", "src/shell/shell_data.proto", + "src/stats_log.proto", + "src/statsd_config.proto", ], static_libs: [ diff --git a/cmds/statsd/TEST_MAPPING b/cmds/statsd/TEST_MAPPING new file mode 100644 index 000000000000..8dee073aca22 --- /dev/null +++ b/cmds/statsd/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "statsd_test" + } + ] +}
\ No newline at end of file diff --git a/cmds/statsd/benchmark/duration_metric_benchmark.cpp b/cmds/statsd/benchmark/duration_metric_benchmark.cpp index 2631009c71f2..2d315d9395bc 100644 --- a/cmds/statsd/benchmark/duration_metric_benchmark.cpp +++ b/cmds/statsd/benchmark/duration_metric_benchmark.cpp @@ -137,77 +137,74 @@ static void BM_DurationMetricNoLink(benchmark::State& state) { int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - std::vector<std::unique_ptr<LogEvent>> events; - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 11)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 40)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 102)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 650)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 640)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 650)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 2)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 10)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 11, + android::view::DISPLAY_STATE_OFF)); + events.push_back( + CreateScreenStateChangedEvent(bucketStartTimeNs + 40, android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 102, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450, + android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 650, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 640, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 650, + android::view::DISPLAY_STATE_ON)); + + vector<int> attributionUids1 = {9999}; + vector<string> attributionTags1 = {""}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "job0")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "job0")); + + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids1, + attributionTags1, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids1, + attributionTags1, "job2")); + + vector<int> attributionUids2 = {8888}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2, + attributionTags1, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850, + attributionUids2, attributionTags1, "job2")); + + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 600, + attributionUids2, attributionTags1, "job1")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900, + attributionUids2, attributionTags1, "job1")); + + vector<int> attributionUids3 = {111, 222, 222}; + vector<string> attributionTags3 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10, attributionUids3, + attributionTags3, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 50, attributionUids3, attributionTags3, + "ReadEmail")); + + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200, attributionUids3, + attributionTags3, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids3, + attributionTags3, "ReadEmail")); + + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids3, + attributionTags3, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids3, + attributionTags3, "ReadDoc")); + + vector<int> attributionUids4 = {333, 222, 555}; + vector<string> attributionTags4 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 401, attributionUids4, + attributionTags4, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids4, + attributionTags4, "ReadEmail")); sortLogEventsByTimestamp(&events); while (state.KeepRunning()) { @@ -230,78 +227,75 @@ static void BM_DurationMetricLink(benchmark::State& state) { int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - std::vector<std::unique_ptr<LogEvent>> events; - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 55)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 120)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 121)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 501)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 55, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 120, + android::view::DISPLAY_STATE_ON)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 121, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450, + android::view::DISPLAY_STATE_ON)); + + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 501, + android::view::DISPLAY_STATE_OFF)); + events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_ON)); + + vector<int> attributionUids1 = {111}; + vector<string> attributionTags1 = {"App1"}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 1, attributionUids1, + attributionTags1, "job1")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "job1")); + + vector<int> attributionUids2 = {333}; + vector<string> attributionTags2 = {"App2"}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids2, + attributionTags2, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids2, + attributionTags2, "job2")); + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2, + attributionTags2, "job2")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850, + attributionUids2, attributionTags2, "job2")); + + vector<int> attributionUids3 = {444}; + vector<string> attributionTags3 = {"App3"}; + events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs - 2, + attributionUids3, attributionTags3, "job3")); + events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900, + attributionUids3, attributionTags3, "job3")); + + vector<int> attributionUids4 = {111, 222, 222}; + vector<string> attributionTags4 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids4, + attributionTags4, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 110, attributionUids4, attributionTags4, + "ReadEmail")); + + vector<int> attributionUids5 = {333, 222, 555}; + vector<string> attributionTags5 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 300, attributionUids5, + attributionTags5, "ReadEmail")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids5, + attributionTags5, "ReadEmail")); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids5, + attributionTags5, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids5, + attributionTags5, "ReadDoc")); + + vector<int> attributionUids6 = {444, 222, 555}; + vector<string> attributionTags6 = {"App3", "GMSCoreModule1", "GMSCoreModule2"}; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 550, attributionUids6, + attributionTags6, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 800, attributionUids6, attributionTags6, + "ReadDoc")); + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids6, + attributionTags6, "ReadDoc")); + events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids6, + attributionTags6, "ReadDoc")); sortLogEventsByTimestamp(&events); while (state.KeepRunning()) { diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp index cfe477d7ca8f..743ccc4ed451 100644 --- a/cmds/statsd/benchmark/filter_value_benchmark.cpp +++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp @@ -14,10 +14,13 @@ * limitations under the License. */ #include <vector> -#include "benchmark/benchmark.h" + #include "FieldValue.h" #include "HashableDimensionKey.h" +#include "benchmark/benchmark.h" #include "logd/LogEvent.h" +#include "metric_util.h" +#include "stats_event.h" #include "stats_log_util.h" namespace android { @@ -26,17 +29,20 @@ namespace statsd { using std::vector; -static void createLogEventAndMatcher(LogEvent* event, FieldMatcher *field_matcher) { - AttributionNodeInternal node; - node.set_uid(100); - node.set_tag("LOCATION"); +static void createLogEventAndMatcher(LogEvent* event, FieldMatcher* field_matcher) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 1); + AStatsEvent_overwriteTimestamp(statsEvent, 100000); + + std::vector<int> attributionUids = {100, 100}; + std::vector<string> attributionTags = {"LOCATION", "LOCATION"}; + writeAttribution(statsEvent, attributionUids, attributionTags); + + AStatsEvent_writeFloat(statsEvent, 3.2f); + AStatsEvent_writeString(statsEvent, "LOCATION"); + AStatsEvent_writeInt64(statsEvent, 990); - std::vector<AttributionNodeInternal> nodes = {node, node}; - event->write(nodes); - event->write(3.2f); - event->write("LOCATION"); - event->write((int64_t)990); - event->init(); + parseStatsEventToLogEvent(statsEvent, event); field_matcher->set_field(1); auto child = field_matcher->add_child(); @@ -46,7 +52,7 @@ static void createLogEventAndMatcher(LogEvent* event, FieldMatcher *field_matche } static void BM_FilterValue(benchmark::State& state) { - LogEvent event(1, 100000); + LogEvent event(/*uid=*/0, /*pid=*/0); FieldMatcher field_matcher; createLogEventAndMatcher(&event, &field_matcher); diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp index 2a4403ed8282..7a455650a31b 100644 --- a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp +++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp @@ -14,10 +14,13 @@ * limitations under the License. */ #include <vector> -#include "benchmark/benchmark.h" + #include "FieldValue.h" #include "HashableDimensionKey.h" +#include "benchmark/benchmark.h" #include "logd/LogEvent.h" +#include "metric_util.h" +#include "stats_event.h" #include "stats_log_util.h" namespace android { @@ -27,16 +30,19 @@ namespace statsd { using std::vector; static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) { - AttributionNodeInternal node; - node.set_uid(100); - node.set_tag("LOCATION"); + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 1); + AStatsEvent_overwriteTimestamp(statsEvent, 100000); + + std::vector<int> attributionUids = {100, 100}; + std::vector<string> attributionTags = {"LOCATION", "LOCATION"}; + writeAttribution(statsEvent, attributionUids, attributionTags); + + AStatsEvent_writeFloat(statsEvent, 3.2f); + AStatsEvent_writeString(statsEvent, "LOCATION"); + AStatsEvent_writeInt64(statsEvent, 990); - std::vector<AttributionNodeInternal> nodes = {node, node}; - event->write(nodes); - event->write(3.2f); - event->write("LOCATION"); - event->write((int64_t)990); - event->init(); + parseStatsEventToLogEvent(statsEvent, event); link->conditionId = 1; @@ -54,7 +60,7 @@ static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) { static void BM_GetDimensionInCondition(benchmark::State& state) { Metric2Condition link; - LogEvent event(1, 100000); + LogEvent event(/*uid=*/0, /*pid=*/0); createLogEventAndLink(&event, &link); while (state.KeepRunning()) { diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp index 26034695906b..057e00bde202 100644 --- a/cmds/statsd/benchmark/log_event_benchmark.cpp +++ b/cmds/statsd/benchmark/log_event_benchmark.cpp @@ -16,55 +16,31 @@ #include <vector> #include "benchmark/benchmark.h" #include "logd/LogEvent.h" +#include "stats_event.h" namespace android { namespace os { namespace statsd { -using std::vector; - -/* Special markers for android_log_list_element type */ -static const char EVENT_TYPE_LIST_STOP = '\n'; /* declare end of list */ -static const char EVENT_TYPE_UNKNOWN = '?'; /* protocol error */ - -static const char EVENT_TYPE_INT = 0; -static const char EVENT_TYPE_LONG = 1; -static const char EVENT_TYPE_STRING = 2; -static const char EVENT_TYPE_LIST = 3; -static const char EVENT_TYPE_FLOAT = 4; - -static const int kLogMsgHeaderSize = 28; - -static void write4Bytes(int val, vector<char>* buffer) { - buffer->push_back(static_cast<char>(val)); - buffer->push_back(static_cast<char>((val >> 8) & 0xFF)); - buffer->push_back(static_cast<char>((val >> 16) & 0xFF)); - buffer->push_back(static_cast<char>((val >> 24) & 0xFF)); -} - -static void getSimpleLogMsgData(log_msg* msg) { - vector<char> buffer; - // stats_log tag id - write4Bytes(1937006964, &buffer); - buffer.push_back(EVENT_TYPE_LIST); - buffer.push_back(2); // field counts; - buffer.push_back(EVENT_TYPE_INT); - write4Bytes(10 /* atom id */, &buffer); - buffer.push_back(EVENT_TYPE_INT); - write4Bytes(99 /* a value to log*/, &buffer); - buffer.push_back(EVENT_TYPE_LIST_STOP); - - msg->entry.len = buffer.size(); - msg->entry.hdr_size = kLogMsgHeaderSize; - msg->entry.sec = time(nullptr); - std::copy(buffer.begin(), buffer.end(), msg->buf + kLogMsgHeaderSize); +static size_t createAndParseStatsEvent(uint8_t* msg) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 2); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + memcpy(msg, buf, size); + return size; } static void BM_LogEventCreation(benchmark::State& state) { - log_msg msg; - getSimpleLogMsgData(&msg); + uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD]; + size_t size = createAndParseStatsEvent(msg); while (state.KeepRunning()) { - benchmark::DoNotOptimize(LogEvent(msg)); + LogEvent event(/*uid=*/ 1000, /*pid=*/ 1001); + benchmark::DoNotOptimize(event.parseBuffer(msg, size)); } } BENCHMARK(BM_LogEventCreation); diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp index cca6d525ea16..89fd3d9b29ab 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -14,6 +14,8 @@ #include "metric_util.h" +#include "stats_event.h" + namespace android { namespace os { namespace statsd { @@ -245,118 +247,105 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) return dimensions; } -std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( - const android::view::DisplayStateEnum state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs); - event->write(state); - event->init(); - return event; +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags) { + vector<const char*> cTags(attributionTags.size()); + for (int i = 0; i < cTags.size(); i++) { + cTags[i] = attributionTags[i].c_str(); + } + + AStatsEvent_writeAttributionChain(statsEvent, + reinterpret_cast<const uint32_t*>(attributionUids.data()), + cTags.data(), attributionUids.size()); } -std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent( - int level, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs); - (event->write(level)); - event->init(); - return event; +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { + AStatsEvent_build(statsEvent); -} + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + logEvent->parseBuffer(buf, size); -std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& jobName, - const ScheduledJobStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs); - event->write(attributions); - event->write(jobName); - event->write(state); - event->init(); - return event; + AStatsEvent_release(statsEvent); } -std::unique_ptr<LogEvent> CreateStartScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs) { - return CreateScheduledJobStateChangedEvent( - attributions, name, ScheduledJobStateChanged::STARTED, timestampNs); -} +std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( + uint64_t timestampNs, const android::view::DisplayStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, state); -// Create log event when scheduled job finishes. -std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs) { - return CreateScheduledJobStateChangedEvent( - attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs); + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - const WakelockStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs); - event->write(attributions); - event->write(android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); - event->write(wakelockName); - event->write(state); - event->init(); - return event; -} +std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( + const vector<int>& attributionUids, const vector<string>& attributionTags, + const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); -std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs) { - return CreateWakelockStateChangedEvent( - attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs); -} + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, jobName.c_str()); + AStatsEvent_writeInt32(statsEvent, state); -std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs) { - return CreateWakelockStateChangedEvent( - attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs); + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( - const int uid, const ActivityForegroundStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>( - android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs); - event->write(uid); - event->write("pkg_name"); - event->write("class_name"); - event->write(state); - event->init(); - return event; +std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::STARTED, timestampNs); } -std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) { - return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::BACKGROUND, timestampNs); +// Create log event when scheduled job finishes. +std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::FINISHED, timestampNs); } -std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) { - return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::FOREGROUND, timestampNs); -} +std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name, + const SyncStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + AStatsEvent_writeInt32(statsEvent, state); -std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - const SyncStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs); - event->write(attributions); - event->write(name); - event->write(state); - event->init(); - return event; + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateSyncStartEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs) { - return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs); +std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::ON); } -std::unique_ptr<LogEvent> CreateSyncEndEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs) { - return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs); +std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::OFF); } sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, @@ -373,13 +362,6 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const Stat return processor; } -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) { - AttributionNodeInternal attribution; - attribution.set_uid(uid); - attribution.set_tag(tag); - return attribution; -} - void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { std::sort(events->begin(), events->end(), [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h index 9b28d60b38c0..3efaa850a921 100644 --- a/cmds/statsd/benchmark/metric_util.h +++ b/cmds/statsd/benchmark/metric_util.h @@ -18,6 +18,7 @@ #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "src/StatsLogProcessor.h" #include "src/logd/LogEvent.h" +#include "stats_event.h" #include "statslog.h" namespace android { @@ -92,60 +93,38 @@ FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, FieldMatcher CreateAttributionUidDimensions(const int atomId, const std::vector<Position>& positions); +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags); + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); + // Create log event for screen state changed. std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( - const android::view::DisplayStateEnum state, uint64_t timestampNs); - -// Create log event for screen brightness state changed. -std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent( - int level, uint64_t timestampNs); + uint64_t timestampNs, const android::view::DisplayStateEnum state); // Create log event when scheduled job starts. -std::unique_ptr<LogEvent> CreateStartScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName); // Create log event when scheduled job finishes. -std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs); - -// Create log event for app moving to background. -std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); - -// Create log event for app moving to foreground. -std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName); // Create log event when the app sync starts. -std::unique_ptr<LogEvent> CreateSyncStartEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name); // Create log event when the app sync ends. -std::unique_ptr<LogEvent> CreateSyncEndEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs); - -// Create log event when the app sync ends. -std::unique_ptr<LogEvent> CreateAppCrashEvent( - const int uid, uint64_t timestampNs); - -// Create log event for acquiring wakelock. -std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs); - -// Create log event for releasing wakelock. -std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs); - -// Create log event for releasing wakelock. -std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent( - int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs); - -// Helper function to create an AttributionNodeInternal proto. -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag); +std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name); // Create a statsd log event processor upon the start time in seconds, config and key. sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, @@ -158,4 +137,4 @@ int64_t StringToId(const string& str); } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp index 320a32ab4648..c9ccfb93c86d 100644 --- a/cmds/statsd/src/FieldValue.cpp +++ b/cmds/statsd/src/FieldValue.cpp @@ -18,7 +18,6 @@ #include "Log.h" #include "FieldValue.h" #include "HashableDimensionKey.h" -#include "atoms_info.h" #include "math.h" namespace android { @@ -116,28 +115,13 @@ void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* ou } bool isAttributionUidField(const FieldValue& value) { - int field = value.mField.getField() & 0xff007f; - if (field == 0x10001 && value.mValue.getType() == INT) { - return true; - } - return false; + return isAttributionUidField(value.mField, value.mValue); } int32_t getUidIfExists(const FieldValue& value) { - bool isUid = false; - // the field is uid field if the field is the uid field in attribution node or marked as - // is_uid in atoms.proto - if (isAttributionUidField(value)) { - isUid = true; - } else { - auto it = android::util::AtomsInfo::kAtomsWithUidField.find(value.mField.getTag()); - if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { - int uidField = it->second; // uidField is the field number in proto - isUid = value.mField.getDepth() == 0 && value.mField.getPosAtDepth(0) == uidField && - value.mValue.getType() == INT; - } - } - + // the field is uid field if the field is the uid field in attribution node + // or annotated as such in the atom + bool isUid = isAttributionUidField(value) || isUidField(value); return isUid ? value.mValue.int_value : -1; } @@ -149,6 +133,10 @@ bool isAttributionUidField(const Field& field, const Value& value) { return false; } +bool isUidField(const FieldValue& fieldValue) { + return fieldValue.mAnnotations.isUidField(); +} + Value::Value(const Value& from) { type = from.getType(); switch (type) { @@ -438,6 +426,25 @@ bool equalDimensions(const std::vector<Matcher>& dimension_a, return eq; } +bool subsetDimensions(const std::vector<Matcher>& dimension_a, + const std::vector<Matcher>& dimension_b) { + if (dimension_a.size() > dimension_b.size()) { + return false; + } + for (size_t i = 0; i < dimension_a.size(); ++i) { + bool found = false; + for (size_t j = 0; j < dimension_b.size(); ++j) { + if (dimension_a[i] == dimension_b[j]) { + found = true; + } + } + if (!found) { + return false; + } + } + return true; +} + bool HasPositionANY(const FieldMatcher& matcher) { if (matcher.has_position() && matcher.position() == Position::ANY) { return true; @@ -464,4 +471,4 @@ bool HasPositionALL(const FieldMatcher& matcher) { } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 6729e052b5ee..fd86e3683970 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -16,6 +16,7 @@ #pragma once #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "annotations.h" namespace android { namespace os { @@ -26,7 +27,6 @@ struct Matcher; struct Field; struct FieldValue; -const int32_t kAttributionField = 1; const int32_t kMaxLogDepth = 2; const int32_t kLastBitMask = 0x80; const int32_t kClearLastBitDeco = 0x7f; @@ -180,6 +180,7 @@ public: return false; } + bool matches(const Matcher& that) const; }; @@ -261,6 +262,11 @@ inline Matcher getSimpleMatcher(int32_t tag, size_t field) { return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000); } +inline Matcher getFirstUidMatcher(int32_t atomId) { + int32_t pos[] = {1, 1, 1}; + return Matcher(Field(atomId, pos, 2), 0xff7f7f7f); +} + /** * A wrapper for a union type to contain multiple types of values. * @@ -352,6 +358,56 @@ struct Value { Value& operator=(const Value& that); }; +class Annotations { +public: + Annotations() { + setNested(true); // Nested = true by default + } + + // This enum stores where particular annotations can be found in the + // bitmask. Note that these pos do not correspond to annotation ids. + enum { + NESTED_POS = 0x0, + PRIMARY_POS = 0x1, + EXCLUSIVE_POS = 0x2, + UID_POS = 0x3 + }; + + inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); } + + inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); } + + inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); } + + inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); } + + // Default value = false + inline bool isNested() const { return getValueFromBitmask(NESTED_POS); } + + // Default value = false + inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); } + + // Default value = false + inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); } + + // Default value = false + inline bool isUidField() const { return getValueFromBitmask(UID_POS); } + +private: + inline void setBitmaskAtPos(int pos, bool value) { + mBooleanBitmask &= ~(1 << pos); // clear + mBooleanBitmask |= (value << pos); // set + } + + inline bool getValueFromBitmask(int pos) const { + return (mBooleanBitmask >> pos) & 0x1; + } + + // This is a bitmask over all annotations stored in boolean form. Because + // there are only 4 booleans, just one byte is required. + uint8_t mBooleanBitmask = 0; +}; + /** * Represents a log item, or a dimension item (They are essentially the same). */ @@ -379,6 +435,7 @@ struct FieldValue { Field mField; Value mValue; + Annotations mAnnotations; }; bool HasPositionANY(const FieldMatcher& matcher); @@ -392,9 +449,14 @@ int getUidIfExists(const FieldValue& value); void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output); bool isAttributionUidField(const Field& field, const Value& value); +bool isUidField(const FieldValue& fieldValue); bool equalDimensions(const std::vector<Matcher>& dimension_a, const std::vector<Matcher>& dimension_b); + +// Returns true if dimension_a is a subset of dimension_b. +bool subsetDimensions(const std::vector<Matcher>& dimension_a, + const std::vector<Matcher>& dimension_b); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 05acef8b219d..eba66e0cb7b0 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -25,6 +25,100 @@ namespace statsd { using std::string; using std::vector; +using android::base::StringPrintf; + +// These constants must be kept in sync with those in StatsDimensionsValue.java +const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +const static int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4; +// const static int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because +// unused -- statsd does not correctly support bool types) +const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + +/** + * Recursive helper function that populates a parent StatsDimensionsValueParcel + * with children StatsDimensionsValueParcels. + * + * \param parent parcel that will be populated with children + * \param childDepth depth of children FieldValues + * \param childPrefix expected FieldValue prefix of children + * \param dims vector of FieldValues stored by HashableDimensionKey + * \param index position in dims to start reading children from + */ +static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel& parent, + int childDepth, int childPrefix, + const vector<FieldValue>& dims, + size_t& index) { + if (childDepth > 2) { + ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel."); + return; + } + + while (index < dims.size()) { + const FieldValue& dim = dims[index]; + int fieldDepth = dim.mField.getDepth(); + int fieldPrefix = dim.mField.getPrefix(childDepth); + + StatsDimensionsValueParcel child; + child.field = dim.mField.getPosAtDepth(childDepth); + + if (fieldDepth == childDepth && fieldPrefix == childPrefix) { + switch (dim.mValue.getType()) { + case INT: + child.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; + child.intValue = dim.mValue.int_value; + break; + case LONG: + child.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE; + child.longValue = dim.mValue.long_value; + break; + case FLOAT: + child.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE; + child.floatValue = dim.mValue.float_value; + break; + case STRING: + child.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE; + child.stringValue = dim.mValue.str_value; + break; + default: + ALOGE("Encountered FieldValue with unsupported value type."); + break; + } + index++; + parent.tupleValue.push_back(child); + } else if (fieldDepth > childDepth && fieldPrefix == childPrefix) { + // This FieldValue is not a child of the current parent, but it is + // an indirect descendant. Thus, create a direct child of TUPLE_TYPE + // and recurse to parcel the indirect descendants. + child.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + populateStatsDimensionsValueParcelChildren(child, childDepth + 1, + dim.mField.getPrefix(childDepth + 1), dims, + index); + parent.tupleValue.push_back(child); + } else { + return; + } + } +} + +StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const { + StatsDimensionsValueParcel root; + if (mValues.size() == 0) { + return root; + } + + root.field = mValues[0].mField.getTag(); + root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + + // Children of the root correspond to top-level (depth = 0) FieldValues. + int childDepth = 0; + int childPrefix = 0; + size_t index = 0; + populateStatsDimensionsValueParcelChildren(root, childDepth, childPrefix, mValues, index); + + return root; +} android::hash_t hashDimension(const HashableDimensionKey& value) { android::hash_t hash = 0; @@ -57,6 +151,17 @@ android::hash_t hashDimension(const HashableDimensionKey& value) { return JenkinsHashWhiten(hash); } +bool filterValues(const Matcher& matcherField, const vector<FieldValue>& values, + FieldValue* output) { + for (const auto& value : values) { + if (value.mField.matches(matcherField)) { + (*output) = value; + return true; + } + } + return false; +} + bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values, HashableDimensionKey* output) { size_t num_matches = 0; @@ -75,6 +180,23 @@ bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue> return num_matches > 0; } +bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output) { + size_t num_matches = 0; + const int32_t simpleFieldMask = 0xff7f0000; + const int32_t attributionUidFieldMask = 0xff7f7f7f; + for (const auto& value : values) { + if (value.mAnnotations.isPrimaryField()) { + output->addValue(value); + output->mutableValue(num_matches)->mField.setTag(value.mField.getTag()); + const int32_t mask = + isAttributionUidField(value) ? attributionUidFieldMask : simpleFieldMask; + output->mutableValue(num_matches)->mField.setField(value.mField.getField() & mask); + num_matches++; + } + } + return num_matches > 0; +} + void filterGaugeValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values, std::vector<FieldValue>* output) { for (const auto& field : matcherFields) { @@ -94,18 +216,78 @@ void getDimensionForCondition(const std::vector<FieldValue>& eventValues, size_t count = conditionDimension->getValues().size(); if (count != links.conditionFields.size()) { - // ALOGE("WTF condition link is bad"); return; } for (size_t i = 0; i < count; i++) { conditionDimension->mutableValue(i)->mField.setField( - links.conditionFields[i].mMatcher.getField()); + links.conditionFields[i].mMatcher.getField()); conditionDimension->mutableValue(i)->mField.setTag( - links.conditionFields[i].mMatcher.getTag()); + links.conditionFields[i].mMatcher.getTag()); } } +void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link, + HashableDimensionKey* statePrimaryKey) { + // First, get the dimension from the event using the "what" fields from the + // MetricStateLinks. + filterValues(link.metricFields, eventValues, statePrimaryKey); + + // Then check that the statePrimaryKey size equals the number of state fields + size_t count = statePrimaryKey->getValues().size(); + if (count != link.stateFields.size()) { + return; + } + + // For each dimension Value in the statePrimaryKey, set the field and tag + // using the state atom fields from MetricStateLinks. + for (size_t i = 0; i < count; i++) { + statePrimaryKey->mutableValue(i)->mField.setField(link.stateFields[i].mMatcher.getField()); + statePrimaryKey->mutableValue(i)->mField.setTag(link.stateFields[i].mMatcher.getTag()); + } +} + +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const vector<Metric2State>& stateLinks, const int32_t stateAtomId) { + if (whatKey.getValues().size() < primaryKey.getValues().size()) { + ALOGE("Contains linked values false: whatKey is too small"); + return false; + } + + for (const auto& primaryValue : primaryKey.getValues()) { + bool found = false; + for (const auto& whatValue : whatKey.getValues()) { + if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) && + primaryValue.mValue == whatValue.mValue) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField) { + for (auto stateLink : stateLinks) { + if (stateLink.stateAtomId != stateAtomId) { + continue; + } + + for (size_t i = 0; i < stateLink.stateFields.size(); i++) { + if (stateLink.stateFields[i].mMatcher == stateField && + stateLink.metricFields[i].mMatcher == metricField) { + return true; + } + } + } + return false; +} + bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) { if (s1.size() != s2.size()) { return s1.size() < s2.size(); @@ -120,6 +302,10 @@ bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) { return false; } +bool HashableDimensionKey::operator!=(const HashableDimensionKey& that) const { + return !((*this) == that); +} + bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { if (mValues.size() != that.getValues().size()) { return false; @@ -173,11 +359,11 @@ string HashableDimensionKey::toString() const { bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const { return mDimensionKeyInWhat == that.getDimensionKeyInWhat() && - mDimensionKeyInCondition == that.getDimensionKeyInCondition(); + mStateValuesKey == that.getStateValuesKey(); }; string MetricDimensionKey::toString() const { - return mDimensionKeyInWhat.toString() + mDimensionKeyInCondition.toString(); + return mDimensionKeyInWhat.toString() + mStateValuesKey.toString(); } bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const { @@ -187,7 +373,7 @@ bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const { return false; } - return mDimensionKeyInCondition < that.getDimensionKeyInCondition(); + return mStateValuesKey < that.getStateValuesKey(); } } // namespace statsd diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 6f4941f717ee..bd011005a301 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -16,17 +16,18 @@ #pragma once +#include <aidl/android/os/StatsDimensionsValueParcel.h> #include <utils/JenkinsHash.h> #include <vector> -#include "FieldValue.h" #include "android-base/stringprintf.h" +#include "FieldValue.h" #include "logd/LogEvent.h" namespace android { namespace os { namespace statsd { -using android::base::StringPrintf; +using ::aidl::android::os::StatsDimensionsValueParcel; struct Metric2Condition { int64_t conditionId; @@ -34,6 +35,12 @@ struct Metric2Condition { std::vector<Matcher> conditionFields; }; +struct Metric2State { + int32_t stateAtomId; + std::vector<Matcher> metricFields; + std::vector<Matcher> stateFields; +}; + class HashableDimensionKey { public: explicit HashableDimensionKey(const std::vector<FieldValue>& values) { @@ -63,8 +70,12 @@ public: return nullptr; } + StatsDimensionsValueParcel toStatsDimensionsValueParcel() const; + std::string toString() const; + bool operator!=(const HashableDimensionKey& that) const; + bool operator==(const HashableDimensionKey& that) const; bool operator<(const HashableDimensionKey& that) const; @@ -76,17 +87,16 @@ private: }; class MetricDimensionKey { - public: +public: explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat, - const HashableDimensionKey& dimensionKeyInCondition) - : mDimensionKeyInWhat(dimensionKeyInWhat), - mDimensionKeyInCondition(dimensionKeyInCondition) {}; + const HashableDimensionKey& stateValuesKey) + : mDimensionKeyInWhat(dimensionKeyInWhat), mStateValuesKey(stateValuesKey){}; MetricDimensionKey(){}; MetricDimensionKey(const MetricDimensionKey& that) : mDimensionKeyInWhat(that.getDimensionKeyInWhat()), - mDimensionKeyInCondition(that.getDimensionKeyInCondition()) {}; + mStateValuesKey(that.getStateValuesKey()){}; MetricDimensionKey& operator=(const MetricDimensionKey& from) = default; @@ -96,30 +106,41 @@ class MetricDimensionKey { return mDimensionKeyInWhat; } - inline const HashableDimensionKey& getDimensionKeyInCondition() const { - return mDimensionKeyInCondition; + inline const HashableDimensionKey& getStateValuesKey() const { + return mStateValuesKey; } - inline void setDimensionKeyInCondition(const HashableDimensionKey& key) { - mDimensionKeyInCondition = key; + inline HashableDimensionKey* getMutableStateValuesKey() { + return &mStateValuesKey; } - bool hasDimensionKeyInCondition() const { - return mDimensionKeyInCondition.getValues().size() > 0; + inline void setStateValuesKey(const HashableDimensionKey& key) { + mStateValuesKey = key; + } + + bool hasStateValuesKey() const { + return mStateValuesKey.getValues().size() > 0; } bool operator==(const MetricDimensionKey& that) const; bool operator<(const MetricDimensionKey& that) const; - private: - HashableDimensionKey mDimensionKeyInWhat; - HashableDimensionKey mDimensionKeyInCondition; +private: + HashableDimensionKey mDimensionKeyInWhat; + HashableDimensionKey mStateValuesKey; }; android::hash_t hashDimension(const HashableDimensionKey& key); /** + * Returns true if a FieldValue field matches the matcher field. + * The value of the FieldValue is output. + */ +bool filterValues(const Matcher& matcherField, const std::vector<FieldValue>& values, + FieldValue* output); + +/** * Creating HashableDimensionKeys from FieldValues using matcher. * * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL @@ -133,6 +154,18 @@ bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<F HashableDimensionKey* output); /** + * Creating HashableDimensionKeys from State Primary Keys in FieldValues. + * + * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL + * in it. This is because: for example, when we create dimension from last uid in attribution chain, + * In one event, uid 1000 is at position 5 and it's the last + * In another event, uid 1000 is at position 6, and it's the last + * these 2 events should be mapped to the same dimension. So we will remove the original position + * from the dimension key for the uid field (by applying 0x80 bit mask). + */ +bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output); + +/** * Filter the values from FieldValues using the matchers. * * In contrast to the above function, this function will not do any modification to the original @@ -145,6 +178,39 @@ void getDimensionForCondition(const std::vector<FieldValue>& eventValues, const Metric2Condition& links, HashableDimensionKey* conditionDimension); +/** + * Get dimension values using metric's "what" fields and fill statePrimaryKey's + * mField information using "state" fields. + */ +void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link, + HashableDimensionKey* statePrimaryKey); + +/** + * Returns true if the primaryKey values are a subset of the whatKey values. + * The values from the primaryKey come from the state atom, so we need to + * check that a link exists between the state atom field and what atom field. + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 27, {uid: 1005}] + * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid + * + * Example: + * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}] + * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}] + * Returns false + */ +bool containsLinkedStateValues(const HashableDimensionKey& whatKey, + const HashableDimensionKey& primaryKey, + const std::vector<Metric2State>& stateLinks, + const int32_t stateAtomId); + +/** + * Returns true if there is a Metric2State link that links the stateField and + * the metricField (they are equal fields from different atoms). + */ +bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId, + const Field& stateField, const Field& metricField); } // namespace statsd } // namespace os } // namespace android @@ -165,8 +231,8 @@ template <> struct hash<MetricDimensionKey> { std::size_t operator()(const MetricDimensionKey& key) const { android::hash_t hash = hashDimension(key.getDimensionKeyInWhat()); - hash = android::JenkinsHashMix(hash, hashDimension(key.getDimensionKeyInCondition())); + hash = android::JenkinsHashMix(hash, hashDimension(key.getStateValuesKey())); return android::JenkinsHashWhiten(hash); } }; -} // namespace std
\ No newline at end of file +} // namespace std diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index a730a0de6b88..e7b32c56551a 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -20,16 +20,20 @@ #include "StatsLogProcessor.h" #include <android-base/file.h> +#include <cutils/multiuser.h> #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h> +#include <frameworks/base/cmds/statsd/src/experiment_ids.pb.h> #include "android-base/stringprintf.h" -#include "atoms_info.h" #include "external/StatsPullerManager.h" #include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" #include "metrics/CountMetricProducer.h" +#include "StatsService.h" +#include "state/StateManager.h" #include "stats_log_util.h" #include "stats_util.h" -#include "statslog.h" +#include "statslog_statsd.h" #include "storage/StorageManager.h" using namespace android; @@ -67,9 +71,14 @@ const int FIELD_ID_STRINGS = 9; // for ActiveConfigList const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1; +// for permissions checks +constexpr const char* kPermissionDump = "android.permission.DUMP"; +constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS"; + #define NS_PER_HOUR 3600 * NS_PER_SEC #define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric" +#define STATS_METADATA_DIR "/data/misc/stats-metadata" // Cool down period for writing data to disk to avoid overwriting files. #define WRITE_DATA_COOL_DOWN_SEC 5 @@ -92,6 +101,7 @@ StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, mLargestTimestampSeen(0), mLastTimestampSeen(0) { mPullerManager->ForceClearPullerCache(); + StateManager::getInstance().updateLogSources(uidMap); } StatsLogProcessor::~StatsLogProcessor() { @@ -128,38 +138,22 @@ void StatsLogProcessor::onPeriodicAlarmFired( } } -void updateUid(Value* value, int hostUid) { - int uid = value->int_value; - if (uid != hostUid) { - value->setInt(hostUid); - } -} - void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { - if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(event->GetTagId()) != - android::util::AtomsInfo::kAtomsWithAttributionChain.end()) { - for (auto& value : *(event->getMutableValues())) { - if (value.mField.getPosAtDepth(0) > kAttributionField) { - break; - } - if (isAttributionUidField(value)) { - const int hostUid = mUidMap->getHostUidOrSelf(value.mValue.int_value); - updateUid(&value.mValue, hostUid); + if (std::pair<int, int> indexRange; event->hasAttributionChain(&indexRange)) { + vector<FieldValue>* const fieldValues = event->getMutableValues(); + for (int i = indexRange.first; i <= indexRange.second; i++) { + FieldValue& fieldValue = fieldValues->at(i); + if (isAttributionUidField(fieldValue)) { + const int hostUid = mUidMap->getHostUidOrSelf(fieldValue.mValue.int_value); + fieldValue.mValue.setInt(hostUid); } } } else { - auto it = android::util::AtomsInfo::kAtomsWithUidField.find(event->GetTagId()); - if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { - int uidField = it->second; // uidField is the field number in proto, - // starting from 1 - if (uidField > 0 && (int)event->getValues().size() >= uidField && - (event->getValues())[uidField - 1].mValue.getType() == INT) { - Value& value = (*event->getMutableValues())[uidField - 1].mValue; - const int hostUid = mUidMap->getHostUidOrSelf(value.int_value); - updateUid(&value, hostUid); - } else { - ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); - } + int uidFieldIndex = event->getUidFieldIndex(); + if (uidFieldIndex != -1) { + Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; + const int hostUid = mUidMap->getHostUidOrSelf(value.int_value); + value.setInt(hostUid); } } } @@ -180,6 +174,200 @@ void StatsLogProcessor::onIsolatedUidChangedEventLocked(const LogEvent& event) { } } +void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + InstallTrainInfo trainInfo; + trainInfo.trainName = string(event->GetString(1 /*train name field id*/, &err)); + trainInfo.trainVersionCode = event->GetLong(2 /*train version field id*/, &err); + trainInfo.requiresStaging = event->GetBool(3 /*requires staging field id*/, &err); + trainInfo.rollbackEnabled = event->GetBool(4 /*rollback enabled field id*/, &err); + trainInfo.requiresLowLatencyMonitor = + event->GetBool(5 /*requires low latency monitor field id*/, &err); + trainInfo.status = int32_t(event->GetLong(6 /*state field id*/, &err)); + std::vector<uint8_t> trainExperimentIdBytes = + event->GetStorage(7 /*experiment ids field id*/, &err); + bool is_rollback = event->GetBool(10 /*is rollback field id*/, &err); + + if (err != NO_ERROR) { + ALOGE("Failed to parse fields in binary push state changed log event"); + return; + } + ExperimentIds trainExperimentIds; + if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(), + trainExperimentIdBytes.size())) { + ALOGE("Failed to parse experimentids in binary push state changed."); + return; + } + trainInfo.experimentIds = {trainExperimentIds.experiment_id().begin(), + trainExperimentIds.experiment_id().end()}; + + // Update the train info on disk and get any data the logevent is missing. + getAndUpdateTrainInfoOnDisk(is_rollback, &trainInfo); + + std::vector<uint8_t> trainExperimentIdProto; + writeExperimentIdsToProto(trainInfo.experimentIds, &trainExperimentIdProto); + int32_t userId = multiuser_get_user_id(uid); + + event->updateValue(2 /*train version field id*/, trainInfo.trainVersionCode, LONG); + event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE); + event->updateValue(8 /*user id field id*/, userId, INT); + + // If this event is a rollback event, then the following bits in the event + // are invalid and we will need to update them with the values we pulled + // from disk. + if (is_rollback) { + int bit = trainInfo.requiresStaging ? 1 : 0; + event->updateValue(3 /*requires staging field id*/, bit, INT); + bit = trainInfo.rollbackEnabled ? 1 : 0; + event->updateValue(4 /*rollback enabled field id*/, bit, INT); + bit = trainInfo.requiresLowLatencyMonitor ? 1 : 0; + event->updateValue(5 /*requires low latency monitor field id*/, bit, INT); + } +} + +void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(bool is_rollback, + InstallTrainInfo* trainInfo) { + // If the train name is empty, we don't know which train to attribute the + // event to, so return early. + if (trainInfo->trainName.empty()) { + return; + } + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo->trainName, trainInfoOnDisk); + + bool resetExperimentIds = false; + if (readTrainInfoSuccess) { + // Keep the old train version if we received an empty version. + if (trainInfo->trainVersionCode == -1) { + trainInfo->trainVersionCode = trainInfoOnDisk.trainVersionCode; + } else if (trainInfo->trainVersionCode != trainInfoOnDisk.trainVersionCode) { + // Reset experiment ids if we receive a new non-empty train version. + resetExperimentIds = true; + } + + // Reset if we received a different experiment id. + if (!trainInfo->experimentIds.empty() && + (trainInfoOnDisk.experimentIds.empty() || + trainInfo->experimentIds.at(0) != trainInfoOnDisk.experimentIds[0])) { + resetExperimentIds = true; + } + } + + // Find the right experiment IDs + if ((!resetExperimentIds || is_rollback) && readTrainInfoSuccess) { + trainInfo->experimentIds = trainInfoOnDisk.experimentIds; + } + + if (!trainInfo->experimentIds.empty()) { + int64_t firstId = trainInfo->experimentIds.at(0); + auto& ids = trainInfo->experimentIds; + switch (trainInfo->status) { + case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: + if (find(ids.begin(), ids.end(), firstId + 1) == ids.end()) { + ids.push_back(firstId + 1); + } + break; + case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: + if (find(ids.begin(), ids.end(), firstId + 2) == ids.end()) { + ids.push_back(firstId + 2); + } + break; + case android::os::statsd::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: + if (find(ids.begin(), ids.end(), firstId + 3) == ids.end()) { + ids.push_back(firstId + 3); + } + break; + } + } + + // If this event is a rollback event, the following fields are invalid and + // need to be replaced by the fields stored to disk. + if (is_rollback) { + trainInfo->requiresStaging = trainInfoOnDisk.requiresStaging; + trainInfo->rollbackEnabled = trainInfoOnDisk.rollbackEnabled; + trainInfo->requiresLowLatencyMonitor = trainInfoOnDisk.requiresLowLatencyMonitor; + } + + StorageManager::writeTrainInfo(*trainInfo); +} + +void StatsLogProcessor::onWatchdogRollbackOccurredLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + int32_t rollbackType = int32_t(event->GetInt(1 /*rollback type field id*/, &err)); + string packageName = string(event->GetString(2 /*package name field id*/, &err)); + + if (err != NO_ERROR) { + ALOGE("Failed to parse fields in watchdog rollback occurred log event"); + return; + } + + vector<int64_t> experimentIds = + processWatchdogRollbackOccurred(rollbackType, packageName); + vector<uint8_t> experimentIdProto; + writeExperimentIdsToProto(experimentIds, &experimentIdProto); + + event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STORAGE); +} + +vector<int64_t> StatsLogProcessor::processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageNameIn) { + // If the package name is empty, we can't attribute it to any train, so + // return early. + if (packageNameIn.empty()) { + return vector<int64_t>(); + } + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + // We use the package name of the event as the train name. + readTrainInfoSuccess = StorageManager::readTrainInfo(packageNameIn, trainInfoOnDisk); + + if (!readTrainInfoSuccess) { + return vector<int64_t>(); + } + + if (trainInfoOnDisk.experimentIds.empty()) { + return vector<int64_t>(); + } + + int64_t firstId = trainInfoOnDisk.experimentIds[0]; + auto& ids = trainInfoOnDisk.experimentIds; + switch (rollbackTypeIn) { + case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: + if (find(ids.begin(), ids.end(), firstId + 4) == ids.end()) { + ids.push_back(firstId + 4); + } + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + case android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: + if (find(ids.begin(), ids.end(), firstId + 5) == ids.end()) { + ids.push_back(firstId + 5); + } + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + } + + return trainInfoOnDisk.experimentIds; +} + void StatsLogProcessor::resetConfigs() { std::lock_guard<std::mutex> lock(mMetricsMutex); resetConfigsLocked(getElapsedRealtimeNs()); @@ -194,26 +382,51 @@ void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs) { } void StatsLogProcessor::OnLogEvent(LogEvent* event) { + OnLogEvent(event, getElapsedRealtimeNs()); +} + +void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { std::lock_guard<std::mutex> lock(mMetricsMutex); + // Tell StatsdStats about new event + const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs(); + int atomId = event->GetTagId(); + StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC); + if (!event->isValid()) { + StatsdStats::getInstance().noteAtomError(atomId); + return; + } + + // Hard-coded logic to update train info on disk and fill in any information + // this log event may be missing. + if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) { + onBinaryPushStateChangedEventLocked(event); + } + + // Hard-coded logic to update experiment ids on disk for certain rollback + // types and fill the rollback atom with experiment ids + if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) { + onWatchdogRollbackOccurredLocked(event); + } + #ifdef VERY_VERBOSE_PRINTING if (mPrintAllLogs) { ALOGI("%s", event->ToString().c_str()); } #endif - const int64_t currentTimestampNs = event->GetElapsedTimestampNs(); - - resetIfConfigTtlExpiredLocked(currentTimestampNs); - - StatsdStats::getInstance().noteAtomLogged( - event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC); + resetIfConfigTtlExpiredLocked(eventElapsedTimeNs); // Hard-coded logic to update the isolated uid's in the uid-map. // The field numbers need to be currently updated by hand with atoms.proto - if (event->GetTagId() == android::util::ISOLATED_UID_CHANGED) { + if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) { onIsolatedUidChangedEventLocked(*event); + } else { + // Map the isolated uid to host uid if necessary. + mapIsolatedUidToHostUidIfNecessaryLocked(event); } + StateManager::getInstance().onLogEvent(*event); + if (mMetricsManagers.empty()) { return; } @@ -224,12 +437,6 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { mLastPullerCacheClearTimeSec = curTimeSec; } - - if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) { - // Map the isolated uid to host uid if necessary. - mapIsolatedUidToHostUidIfNecessaryLocked(event); - } - std::unordered_set<int> uidsWithActiveConfigsChanged; std::unordered_map<int, std::vector<int64_t>> activeConfigsPerUid; // pass the event to metrics managers. @@ -256,15 +463,16 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { uidsWithActiveConfigsChanged.insert(uid); StatsdStats::getInstance().noteActiveStatusChanged(pair.first, isCurActive); } - flushIfNecessaryLocked(event->GetElapsedTimestampNs(), pair.first, *(pair.second)); + flushIfNecessaryLocked(pair.first, *(pair.second)); } + // Don't use the event timestamp for the guardrail. for (int uid : uidsWithActiveConfigsChanged) { // Send broadcast so that receivers can pull data. auto lastBroadcastTime = mLastActivationBroadcastTimes.find(uid); if (lastBroadcastTime != mLastActivationBroadcastTimes.end()) { - if (currentTimestampNs - lastBroadcastTime->second < - StatsdStats::kMinActivationBroadcastPeriodNs) { + if (elapsedRealtimeNs - lastBroadcastTime->second < + StatsdStats::kMinActivationBroadcastPeriodNs) { StatsdStats::getInstance().noteActivationBroadcastGuardrailHit(uid); VLOG("StatsD would've sent an activation broadcast but the rate limit stopped us."); return; @@ -274,13 +482,13 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { if (activeConfigs != activeConfigsPerUid.end()) { if (mSendActivationBroadcast(uid, activeConfigs->second)) { VLOG("StatsD sent activation notice for uid %d", uid); - mLastActivationBroadcastTimes[uid] = currentTimestampNs; + mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs; } } else { std::vector<int64_t> emptyActiveConfigs; if (mSendActivationBroadcast(uid, emptyActiveConfigs)) { VLOG("StatsD sent EMPTY activation notice for uid %d", uid); - mLastActivationBroadcastTimes[uid] = currentTimestampNs; + mLastActivationBroadcastTimes[uid] = elapsedRealtimeNs; } } } @@ -314,13 +522,16 @@ void StatsLogProcessor::OnConfigUpdatedLocked( new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor); if (newMetricsManager->isConfigValid()) { + newMetricsManager->init(); mUidMap->OnConfigUpdated(key); newMetricsManager->refreshTtl(timestampNs); mMetricsManagers[key] = newMetricsManager; VLOG("StatsdConfig valid"); } else { // If there is any error in the config, don't use it. + // Remove any existing config with the same key. ALOGE("StatsdConfig NOT valid"); + mMetricsManagers.erase(key); } } @@ -537,22 +748,23 @@ void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { } } -void StatsLogProcessor::flushIfNecessaryLocked( - int64_t timestampNs, const ConfigKey& key, MetricsManager& metricsManager) { +void StatsLogProcessor::flushIfNecessaryLocked(const ConfigKey& key, + MetricsManager& metricsManager) { + int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); auto lastCheckTime = mLastByteSizeTimes.find(key); if (lastCheckTime != mLastByteSizeTimes.end()) { - if (timestampNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) { + if (elapsedRealtimeNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) { return; } } // We suspect that the byteSize() computation is expensive, so we set a rate limit. size_t totalBytes = metricsManager.byteSize(); - mLastByteSizeTimes[key] = timestampNs; + mLastByteSizeTimes[key] = elapsedRealtimeNs; bool requestDump = false; - if (totalBytes > - StatsdStats::kMaxMetricsBytesPerConfig) { // Too late. We need to start clearing data. - metricsManager.dropData(timestampNs); + if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) { + // Too late. We need to start clearing data. + metricsManager.dropData(elapsedRealtimeNs); StatsdStats::getInstance().noteDataDropped(key, totalBytes); VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str()); } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) || @@ -567,7 +779,8 @@ void StatsLogProcessor::flushIfNecessaryLocked( // Send broadcast so that receivers can pull data. auto lastBroadcastTime = mLastBroadcastTimes.find(key); if (lastBroadcastTime != mLastBroadcastTimes.end()) { - if (timestampNs - lastBroadcastTime->second < StatsdStats::kMinBroadcastPeriodNs) { + if (elapsedRealtimeNs - lastBroadcastTime->second < + StatsdStats::kMinBroadcastPeriodNs) { VLOG("StatsD would've sent a broadcast but the rate limit stopped us."); return; } @@ -575,7 +788,7 @@ void StatsLogProcessor::flushIfNecessaryLocked( if (mSendBroadcast(key)) { mOnDiskDataConfigs.erase(key); VLOG("StatsD triggered data fetch for %s", key.ToString().c_str()); - mLastBroadcastTimes[key] = timestampNs; + mLastBroadcastTimes[key] = elapsedRealtimeNs; StatsdStats::getInstance().noteBroadcastSent(key); } } @@ -627,6 +840,110 @@ void StatsLogProcessor::SaveActiveConfigsToDisk(int64_t currentTimeNs) { proto.flush(fd.get()); } +void StatsLogProcessor::SaveMetadataToDisk(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + // Do not write to disk if we already have in the last few seconds. + if (static_cast<unsigned long long> (systemElapsedTimeNs) < + mLastMetadataWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) { + ALOGI("Statsd skipping writing metadata to disk. Already wrote data in last %d seconds", + WRITE_DATA_COOL_DOWN_SEC); + return; + } + mLastMetadataWriteNs = systemElapsedTimeNs; + + metadata::StatsMetadataList metadataList; + WriteMetadataToProtoLocked( + currentWallClockTimeNs, systemElapsedTimeNs, &metadataList); + + string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR); + StorageManager::deleteFile(file_name.c_str()); + + if (metadataList.stats_metadata_size() == 0) { + // Skip the write if we have nothing to write. + return; + } + + std::string data; + metadataList.SerializeToString(&data); + StorageManager::writeFile(file_name.c_str(), data.c_str(), data.size()); +} + +void StatsLogProcessor::WriteMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + WriteMetadataToProtoLocked(currentWallClockTimeNs, systemElapsedTimeNs, metadataList); +} + +void StatsLogProcessor::WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList) { + for (const auto& pair : mMetricsManagers) { + const sp<MetricsManager>& metricsManager = pair.second; + metadata::StatsMetadata* statsMetadata = metadataList->add_stats_metadata(); + bool metadataWritten = metricsManager->writeMetadataToProto(currentWallClockTimeNs, + systemElapsedTimeNs, statsMetadata); + if (!metadataWritten) { + metadataList->mutable_stats_metadata()->RemoveLast(); + } + } +} + +void StatsLogProcessor::LoadMetadataFromDisk(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR); + int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); + if (-1 == fd) { + VLOG("Attempt to read %s but failed", file_name.c_str()); + StorageManager::deleteFile(file_name.c_str()); + return; + } + string content; + if (!android::base::ReadFdToString(fd, &content)) { + ALOGE("Attempt to read %s but failed", file_name.c_str()); + close(fd); + StorageManager::deleteFile(file_name.c_str()); + return; + } + + close(fd); + + metadata::StatsMetadataList statsMetadataList; + if (!statsMetadataList.ParseFromString(content)) { + ALOGE("Attempt to read %s but failed; failed to metadata", file_name.c_str()); + StorageManager::deleteFile(file_name.c_str()); + return; + } + SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs); + StorageManager::deleteFile(file_name.c_str()); +} + +void StatsLogProcessor::SetMetadataState(const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs); +} + +void StatsLogProcessor::SetMetadataStateLocked( + const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + for (const metadata::StatsMetadata& metadata : statsMetadataList.stats_metadata()) { + ConfigKey key(metadata.config_key().uid(), metadata.config_key().config_id()); + auto it = mMetricsManagers.find(key); + if (it == mMetricsManagers.end()) { + ALOGE("No config found for configKey %s", key.ToString().c_str()); + continue; + } + VLOG("Setting metadata %s", key.ToString().c_str()); + it->second->loadMetadata(metadata, currentWallClockTimeNs, systemElapsedTimeNs); + } + VLOG("Successfully loaded %d metadata.", statsMetadataList.stats_metadata_size()); +} + void StatsLogProcessor::WriteActiveConfigsToProtoOutputStream( int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) { std::lock_guard<std::mutex> lock(mMetricsMutex); @@ -736,8 +1053,9 @@ int64_t StatsLogProcessor::getLastReportTimeNs(const ConfigKey& key) { void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, const int64_t version) { std::lock_guard<std::mutex> lock(mMetricsMutex); - ALOGW("Received app upgrade"); - for (auto it : mMetricsManagers) { + VLOG("Received app upgrade"); + StateManager::getInstance().notifyAppChanged(apk, mUidMap); + for (const auto& it : mMetricsManagers) { it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version); } } @@ -745,20 +1063,30 @@ void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const strin void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) { std::lock_guard<std::mutex> lock(mMetricsMutex); - ALOGW("Received app removed"); - for (auto it : mMetricsManagers) { + VLOG("Received app removed"); + StateManager::getInstance().notifyAppChanged(apk, mUidMap); + for (const auto& it : mMetricsManagers) { it.second->notifyAppRemoved(eventTimeNs, apk, uid); } } void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) { std::lock_guard<std::mutex> lock(mMetricsMutex); - ALOGW("Received uid map"); - for (auto it : mMetricsManagers) { + VLOG("Received uid map"); + StateManager::getInstance().updateLogSources(mUidMap); + for (const auto& it : mMetricsManagers) { it.second->onUidMapReceived(eventTimeNs); } } +void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + VLOG("Received boot completed signal"); + for (const auto& it : mMetricsManagers) { + it.second->onStatsdInitCompleted(elapsedTimeNs); + } +} + void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) { std::lock_guard<std::mutex> lock(mMetricsMutex); mOnDiskDataConfigs.insert(key); diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 0d2b33ee0ce1..23f2584655b0 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -18,11 +18,13 @@ #include <gtest/gtest_prod.h> #include "config/ConfigListener.h" +#include "logd/LogEvent.h" #include "metrics/MetricsManager.h" #include "packages/UidMap.h" #include "external/StatsPullerManager.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" #include <stdio.h> #include <unordered_map> @@ -88,6 +90,23 @@ public: /* Load configs containing metrics with active activations from disk. */ void LoadActiveConfigsFromDisk(); + /* Persist metadata for configs and metrics to disk. */ + void SaveMetadataToDisk(int64_t currentWallClockTimeNs, int64_t systemElapsedTimeNs); + + /* Writes the statsd metadata for all configs and metrics to StatsMetadataList. */ + void WriteMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList); + + /* Load stats metadata for configs and metrics from disk. */ + void LoadMetadataFromDisk(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + + /* Sets the metadata for all configs and metrics */ + void SetMetadataState(const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + /* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */ void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs); @@ -101,6 +120,11 @@ public: /* Notify all MetricsManagers of uid map snapshots received */ void onUidMapReceived(const int64_t& eventTimeNs) override; + /* Notify all metrics managers of boot completed + * This will force a bucket split when the boot is finished. + */ + void onStatsdInitCompleted(const int64_t& elapsedTimeNs); + // Reset all configs. void resetConfigs(); @@ -157,6 +181,8 @@ private: sp<AlarmMonitor> mPeriodicAlarmMonitor; + void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs); + void resetIfConfigTtlExpiredLocked(const int64_t timestampNs); void OnConfigUpdatedLocked( @@ -170,8 +196,17 @@ private: void SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList, int64_t currentTimeNs); + void SetMetadataStateLocked(const metadata::StatsMetadataList& statsMetadataList, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + + void WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadataList* metadataList); + void WriteDataToDiskLocked(const DumpReportReason dumpReportReason, const DumpLatency dumpLatency); + void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs, const DumpReportReason dumpReportReason, const DumpLatency dumpLatency); @@ -186,8 +221,7 @@ private: /* Check if we should send a broadcast if approaching memory limits and if we're over, we * actually delete the data. */ - void flushIfNecessaryLocked(int64_t timestampNs, const ConfigKey& key, - MetricsManager& metricsManager); + void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager); // Maps the isolated uid in the log event to host uid if the log event contains uid fields. void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const; @@ -195,6 +229,22 @@ private: // Handler over the isolated uid change event. void onIsolatedUidChangedEventLocked(const LogEvent& event); + // Handler over the binary push state changed event. + void onBinaryPushStateChangedEventLocked(LogEvent* event); + + // Handler over the watchdog rollback occurred event. + void onWatchdogRollbackOccurredLocked(LogEvent* event); + + // Updates train info on disk based on binary push state changed info and + // write disk info into parameters. + void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn); + + // Gets experiment ids on disk for associated train and updates them + // depending on rollback type. Then writes them back to disk and returns + // them. + std::vector<int64_t> processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageName); + // Reset all configs. void resetConfigsLocked(const int64_t timestampNs); // Reset the specified configs. @@ -223,6 +273,9 @@ private: // Last time we wrote active metrics to disk. int64_t mLastActiveMetricsWriteNs = 0; + //Last time we wrote metadata to disk. + int64_t mLastMetadataWriteNs = 0; + #ifdef VERY_VERBOSE_PRINTING bool mPrintAllLogs = false; #endif @@ -231,6 +284,7 @@ private: FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); @@ -254,25 +308,12 @@ private: FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); - - FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition); - - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition); - - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); @@ -285,12 +326,31 @@ private: FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(CountMetricE2eTest, TestSlicedState); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); + FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); }; } // namespace statsd diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index d21c10c6800b..322648229d0e 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -28,14 +28,11 @@ #include <android-base/file.h> #include <android-base/strings.h> -#include <binder/IPCThreadState.h> -#include <binder/IServiceManager.h> -#include <binder/PermissionController.h> #include <cutils/multiuser.h> #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h> #include <frameworks/base/cmds/statsd/src/uid_data.pb.h> #include <private/android_filesystem_config.h> -#include <statslog.h> +#include <statslog_statsd.h> #include <stdio.h> #include <stdlib.h> #include <sys/system_properties.h> @@ -48,79 +45,44 @@ using android::base::StringPrintf; using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_MESSAGE; +using Status = ::ndk::ScopedAStatus; + namespace android { namespace os { namespace statsd { constexpr const char* kPermissionDump = "android.permission.DUMP"; -constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS"; -constexpr const char* kOpUsage = "android:get_usage_stats"; +constexpr const char* kPermissionRegisterPullAtom = "android.permission.REGISTER_STATS_PULL_ATOM"; #define STATS_SERVICE_DIR "/data/misc/stats-service" // for StatsDataDumpProto const int FIELD_ID_REPORTS_LIST = 1; -static binder::Status ok() { - return binder::Status::ok(); +static Status exception(int32_t code, const std::string& msg) { + ALOGE("%s (%d)", msg.c_str(), code); + return Status::fromExceptionCodeWithMessage(code, msg.c_str()); } -static binder::Status exception(uint32_t code, const std::string& msg) { - ALOGE("%s (%d)", msg.c_str(), code); - return binder::Status::fromExceptionCode(code, String8(msg.c_str())); +static bool checkPermission(const char* permission) { + pid_t pid = AIBinder_getCallingPid(); + uid_t uid = AIBinder_getCallingUid(); + return checkPermissionForIds(permission, pid, uid); } -binder::Status checkUid(uid_t expectedUid) { - uid_t uid = IPCThreadState::self()->getCallingUid(); +Status checkUid(uid_t expectedUid) { + uid_t uid = AIBinder_getCallingUid(); if (uid == expectedUid || uid == AID_ROOT) { - return ok(); + return Status::ok(); } else { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d is not expected UID %d", uid, expectedUid)); - } -} - -binder::Status checkDumpAndUsageStats(const String16& packageName) { - pid_t pid = IPCThreadState::self()->getCallingPid(); - uid_t uid = IPCThreadState::self()->getCallingUid(); - - // Root, system, and shell always have access - if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) { - return ok(); - } - - // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionDump)); - } - if (!checkCallingPermission(String16(kPermissionUsage))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionUsage)); - } - - // Caller must also have usage stats op granted - PermissionController pc; - switch (pc.noteOp(String16(kOpUsage), uid, packageName)) { - case PermissionController::MODE_ALLOWED: - case PermissionController::MODE_DEFAULT: - return ok(); - default: - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks app-op %s", uid, pid, kOpUsage)); + return exception(EX_SECURITY, + StringPrintf("UID %d is not expected UID %d", uid, expectedUid)); } } #define ENFORCE_UID(uid) { \ - binder::Status status = checkUid((uid)); \ - if (!status.isOk()) { \ - return status; \ - } \ -} - -#define ENFORCE_DUMP_AND_USAGE_STATS(packageName) { \ - binder::Status status = checkDumpAndUsageStats(packageName); \ + Status status = checkUid((uid)); \ if (!status.isOk()) { \ return status; \ } \ @@ -129,13 +91,13 @@ binder::Status checkDumpAndUsageStats(const String16& packageName) { StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQueue> queue) : mAnomalyAlarmMonitor(new AlarmMonitor( MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, - [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) { + [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) { if (sc != nullptr) { sc->setAnomalyAlarm(timeMillis); StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); } }, - [](const sp<IStatsCompanionService>& sc) { + [](const shared_ptr<IStatsCompanionService>& sc) { if (sc != nullptr) { sc->cancelAnomalyAlarm(); StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); @@ -143,19 +105,23 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ })), mPeriodicAlarmMonitor(new AlarmMonitor( MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, - [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) { + [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) { if (sc != nullptr) { sc->setAlarmForSubscriberTriggering(timeMillis); StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); } }, - [](const sp<IStatsCompanionService>& sc) { + [](const shared_ptr<IStatsCompanionService>& sc) { if (sc != nullptr) { sc->cancelAlarmForSubscriberTriggering(); StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); } })), - mEventQueue(queue) { + mEventQueue(queue), + mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}, + [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }), + mStatsCompanionServiceDeathRecipient( + AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) { mUidMap = UidMap::getInstance(); mPullerManager = new StatsPullerManager(); StatsPuller::SetUidMap(mUidMap); @@ -164,33 +130,30 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor, getElapsedRealtimeNs(), [this](const ConfigKey& key) { - sp<IStatsCompanionService> sc = getStatsCompanionService(); - auto receiver = mConfigManager->GetConfigReceiver(key); - if (sc == nullptr) { - VLOG("Could not find StatsCompanionService"); + shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key); + if (receiver == nullptr) { + VLOG("Could not find a broadcast receiver for %s", key.ToString().c_str()); return false; - } else if (receiver == nullptr) { - VLOG("Statscompanion could not find a broadcast receiver for %s", - key.ToString().c_str()); - return false; - } else { - sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key)); + } else if (receiver->sendDataBroadcast( + mProcessor->getLastReportTimeNs(key)).isOk()) { return true; + } else { + VLOG("Failed to send a broadcast for receiver %s", key.ToString().c_str()); + return false; } }, [this](const int& uid, const vector<int64_t>& activeConfigs) { - auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid); - sp<IStatsCompanionService> sc = getStatsCompanionService(); - if (sc == nullptr) { - VLOG("Could not access statsCompanion"); - return false; - } else if (receiver == nullptr) { + shared_ptr<IPendingIntentRef> receiver = + mConfigManager->GetActiveConfigsChangedReceiver(uid); + if (receiver == nullptr) { VLOG("Could not find receiver for uid %d", uid); return false; - } else { - sc->sendActiveConfigsChangedBroadcast(receiver, activeConfigs); + } else if (receiver->sendActiveConfigsChangedBroadcast(activeConfigs).isOk()) { VLOG("StatsService::active configs broadcast succeeded for uid %d" , uid); return true; + } else { + VLOG("StatsService::active configs broadcast failed for uid %d" , uid); + return false; } }); @@ -241,36 +204,6 @@ void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, } /** - * Implement our own because the default binder implementation isn't - * properly handling SHELL_COMMAND_TRANSACTION. - */ -status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, - uint32_t flags) { - switch (code) { - case SHELL_COMMAND_TRANSACTION: { - int in = data.readFileDescriptor(); - int out = data.readFileDescriptor(); - int err = data.readFileDescriptor(); - int argc = data.readInt32(); - Vector<String8> args; - for (int i = 0; i < argc && data.dataAvail() > 0; i++) { - args.add(String8(data.readString16())); - } - sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder()); - sp<IResultReceiver> resultReceiver = - IResultReceiver::asInterface(data.readStrongBinder()); - - err = command(in, out, err, args, resultReceiver); - if (resultReceiver != nullptr) { - resultReceiver->send(err); - } - return NO_ERROR; - } - default: { return BnStatsManager::onTransact(code, data, reply, flags); } - } -} - -/** * Write data from statsd. * Format for statsdStats: adb shell dumpsys stats --metadata [-v] [--proto] * Format for data report: adb shell dumpsys stats [anything other than --metadata] [--proto] @@ -279,20 +212,21 @@ status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* rep * (bugreports call "adb shell dumpsys stats --dump-priority NORMAL -a --proto") * TODO: Come up with a more robust method of enacting <serviceutils/PriorityDumper.h>. */ -status_t StatsService::dump(int fd, const Vector<String16>& args) { - if (!checkCallingPermission(String16(kPermissionDump))) { +status_t StatsService::dump(int fd, const char** args, uint32_t numArgs) { + if (!checkPermission(kPermissionDump)) { return PERMISSION_DENIED; } - int lastArg = args.size() - 1; + + int lastArg = numArgs - 1; bool asProto = false; - if (lastArg >= 0 && !args[lastArg].compare(String16("--proto"))) { // last argument + if (lastArg >= 0 && string(args[lastArg]) == "--proto") { // last argument asProto = true; lastArg--; } - if (args.size() > 0 && !args[0].compare(String16("--metadata"))) { // first argument + if (numArgs > 0 && string(args[0]) == "--metadata") { // first argument // Request is to dump statsd stats. bool verbose = false; - if (lastArg >= 0 && !args[lastArg].compare(String16("-v"))) { + if (lastArg >= 0 && string(args[lastArg]) == "-v") { verbose = true; lastArg--; } @@ -333,8 +267,11 @@ void StatsService::dumpIncidentSection(int out) { for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) { uint64_t reportsListToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST); + // Don't include the current bucket to avoid skipping buckets. + // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS + // or other alternatives to avoid skipping buckets for pulled metrics. mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), - true /* includeCurrentBucket */, false /* erase_data */, + false /* includeCurrentBucket */, false /* erase_data */, ADB_DUMP, FAST, &proto); @@ -347,67 +284,74 @@ void StatsService::dumpIncidentSection(int out) { /** * Implementation of the adb shell cmd stats command. */ -status_t StatsService::command(int in, int out, int err, Vector<String8>& args, - sp<IResultReceiver> resultReceiver) { - uid_t uid = IPCThreadState::self()->getCallingUid(); +status_t StatsService::handleShellCommand(int in, int out, int err, const char** argv, + uint32_t argc) { + uid_t uid = AIBinder_getCallingUid(); if (uid != AID_ROOT && uid != AID_SHELL) { return PERMISSION_DENIED; } - const int argCount = args.size(); - if (argCount >= 1) { + Vector<String8> utf8Args; + utf8Args.setCapacity(argc); + for (uint32_t i = 0; i < argc; i++) { + utf8Args.push(String8(argv[i])); + } + + if (argc >= 1) { // adb shell cmd stats config ... - if (!args[0].compare(String8("config"))) { - return cmd_config(in, out, err, args); + if (!utf8Args[0].compare(String8("config"))) { + return cmd_config(in, out, err, utf8Args); } - if (!args[0].compare(String8("print-uid-map"))) { - return cmd_print_uid_map(out, args); + if (!utf8Args[0].compare(String8("print-uid-map"))) { + return cmd_print_uid_map(out, utf8Args); } - if (!args[0].compare(String8("dump-report"))) { - return cmd_dump_report(out, args); + if (!utf8Args[0].compare(String8("dump-report"))) { + return cmd_dump_report(out, utf8Args); } - if (!args[0].compare(String8("pull-source")) && args.size() > 1) { - return cmd_print_pulled_metrics(out, args); + if (!utf8Args[0].compare(String8("pull-source")) && argc > 1) { + return cmd_print_pulled_metrics(out, utf8Args); } - if (!args[0].compare(String8("send-broadcast"))) { - return cmd_trigger_broadcast(out, args); + if (!utf8Args[0].compare(String8("send-broadcast"))) { + return cmd_trigger_broadcast(out, utf8Args); } - if (!args[0].compare(String8("print-stats"))) { - return cmd_print_stats(out, args); + if (!utf8Args[0].compare(String8("print-stats"))) { + return cmd_print_stats(out, utf8Args); } - if (!args[0].compare(String8("meminfo"))) { + if (!utf8Args[0].compare(String8("meminfo"))) { return cmd_dump_memory_info(out); } - if (!args[0].compare(String8("write-to-disk"))) { + if (!utf8Args[0].compare(String8("write-to-disk"))) { return cmd_write_data_to_disk(out); } - if (!args[0].compare(String8("log-app-breadcrumb"))) { - return cmd_log_app_breadcrumb(out, args); + if (!utf8Args[0].compare(String8("log-app-breadcrumb"))) { + return cmd_log_app_breadcrumb(out, utf8Args); } - if (!args[0].compare(String8("log-binary-push"))) { - return cmd_log_binary_push(out, args); + if (!utf8Args[0].compare(String8("log-binary-push"))) { + return cmd_log_binary_push(out, utf8Args); } - if (!args[0].compare(String8("clear-puller-cache"))) { + if (!utf8Args[0].compare(String8("clear-puller-cache"))) { return cmd_clear_puller_cache(out); } - if (!args[0].compare(String8("print-logs"))) { - return cmd_print_logs(out, args); + if (!utf8Args[0].compare(String8("print-logs"))) { + return cmd_print_logs(out, utf8Args); } - if (!args[0].compare(String8("send-active-configs"))) { - return cmd_trigger_active_config_broadcast(out, args); + + if (!utf8Args[0].compare(String8("send-active-configs"))) { + return cmd_trigger_active_config_broadcast(out, utf8Args); } - if (!args[0].compare(String8("data-subscribe"))) { + + if (!utf8Args[0].compare(String8("data-subscribe"))) { { std::lock_guard<std::mutex> lock(mShellSubscriberMutex); if (mShellSubscriber == nullptr) { @@ -415,14 +359,10 @@ status_t StatsService::command(int in, int out, int err, Vector<String8>& args, } } int timeoutSec = -1; - if (argCount >= 2) { - timeoutSec = atoi(args[1].c_str()); + if (argc >= 2) { + timeoutSec = atoi(utf8Args[1].c_str()); } - if (resultReceiver == nullptr) { - ALOGI("Null resultReceiver given, no subscription will be started"); - return UNEXPECTED_NULL; - } - mShellSubscriber->startNewSubscription(in, out, resultReceiver, timeoutSec); + mShellSubscriber->startNewSubscription(in, out, timeoutSec); return NO_ERROR; } } @@ -452,9 +392,11 @@ void StatsService::print_cmd_help(int out) { dprintf(out, " PKG Optional package name to print the uids of the package\n"); dprintf(out, "\n"); dprintf(out, "\n"); - dprintf(out, "usage: adb shell cmd stats pull-source [int] \n"); + dprintf(out, "usage: adb shell cmd stats pull-source ATOM_TAG [PACKAGE] \n"); dprintf(out, "\n"); - dprintf(out, " Prints the output of a pulled metrics source (int indicates source)\n"); + dprintf(out, " Prints the output of a pulled atom\n"); + dprintf(out, " UID The atom to pull\n"); + dprintf(out, " PACKAGE The package to pull from. Default is AID_SYSTEM\n"); dprintf(out, "\n"); dprintf(out, "\n"); dprintf(out, "usage: adb shell cmd stats write-to-disk \n"); @@ -552,7 +494,7 @@ status_t StatsService::cmd_trigger_broadcast(int out, Vector<String8>& args) { const int argCount = args.size(); if (argCount == 2) { // Automatically pick the UID - uid = IPCThreadState::self()->getCallingUid(); + uid = AIBinder_getCallingUid(); name.assign(args[1].c_str(), args[1].size()); good = true; } else if (argCount == 3) { @@ -568,18 +510,17 @@ status_t StatsService::cmd_trigger_broadcast(int out, Vector<String8>& args) { return UNKNOWN_ERROR; } ConfigKey key(uid, StrToInt64(name)); - auto receiver = mConfigManager->GetConfigReceiver(key); - sp<IStatsCompanionService> sc = getStatsCompanionService(); - if (sc == nullptr) { - VLOG("Could not access statsCompanion"); - } else if (receiver == nullptr) { - VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str()) - } else { - sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key)); + shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key); + if (receiver == nullptr) { + VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str()); + return UNKNOWN_ERROR; + } else if (receiver->sendDataBroadcast(mProcessor->getLastReportTimeNs(key)).isOk()) { VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(), args[2].c_str()); + } else { + VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(), args[2].c_str()); + return UNKNOWN_ERROR; } - return NO_ERROR; } @@ -589,7 +530,7 @@ status_t StatsService::cmd_trigger_active_config_broadcast(int out, Vector<Strin vector<int64_t> configIds; if (argCount == 1) { // Automatically pick the uid and send a broadcast that has no active configs. - uid = IPCThreadState::self()->getCallingUid(); + uid = AIBinder_getCallingUid(); mProcessor->GetActiveConfigs(uid, configIds); } else { int curArg = 1; @@ -603,7 +544,7 @@ status_t StatsService::cmd_trigger_active_config_broadcast(int out, Vector<Strin } curArg++; } else { - uid = IPCThreadState::self()->getCallingUid(); + uid = AIBinder_getCallingUid(); } if (curArg == argCount || args[curArg] != "--configs") { VLOG("Reached end of args, or specify configs not set. Sending actual active configs,"); @@ -623,15 +564,15 @@ status_t StatsService::cmd_trigger_active_config_broadcast(int out, Vector<Strin } } } - auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid); - sp<IStatsCompanionService> sc = getStatsCompanionService(); - if (sc == nullptr) { - VLOG("Could not access statsCompanion"); - } else if (receiver == nullptr) { + shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid); + if (receiver == nullptr) { VLOG("Could not find receiver for uid %d", uid); - } else { - sc->sendActiveConfigsChangedBroadcast(receiver, configIds); + return UNKNOWN_ERROR; + } else if (receiver->sendActiveConfigsChangedBroadcast(configIds).isOk()) { VLOG("StatsService::trigger active configs changed broadcast succeeded for uid %d" , uid); + } else { + VLOG("StatsService::trigger active configs changed broadcast failed for uid %d", uid); + return UNKNOWN_ERROR; } return NO_ERROR; } @@ -646,7 +587,7 @@ status_t StatsService::cmd_config(int in, int out, int err, Vector<String8>& arg if (argCount == 3) { // Automatically pick the UID - uid = IPCThreadState::self()->getCallingUid(); + uid = AIBinder_getCallingUid(); name.assign(args[2].c_str(), args[2].size()); good = true; } else if (argCount == 4) { @@ -729,7 +670,7 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) { } if (argCount == 2) { // Automatically pick the UID - uid = IPCThreadState::self()->getCallingUid(); + uid = AIBinder_getCallingUid(); name.assign(args[1].c_str(), args[1].size()); good = true; } else if (argCount == 3) { @@ -821,7 +762,7 @@ status_t StatsService::cmd_log_app_breadcrumb(int out, const Vector<String8>& ar const int argCount = args.size(); if (argCount == 3) { // Automatically pick the UID - uid = IPCThreadState::self()->getCallingUid(); + uid = AIBinder_getCallingUid(); label = atoi(args[1].c_str()); state = atoi(args[2].c_str()); good = true; @@ -837,7 +778,8 @@ status_t StatsService::cmd_log_app_breadcrumb(int out, const Vector<String8>& ar } if (good) { dprintf(out, "Logging AppBreadcrumbReported(%d, %d, %d) to statslog.\n", uid, label, state); - android::util::stats_write(android::util::APP_BREADCRUMB_REPORTED, uid, label, state); + android::os::statsd::util::stats_write( + android::os::statsd::util::APP_BREADCRUMB_REPORTED, uid, label, state); } else { print_cmd_help(out); return UNKNOWN_ERROR; @@ -852,18 +794,8 @@ status_t StatsService::cmd_log_binary_push(int out, const Vector<String8>& args) dprintf(out, "Incorrect number of argument supplied\n"); return UNKNOWN_ERROR; } - android::String16 trainName = android::String16(args[1].c_str()); + string trainName = string(args[1].c_str()); int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10); - int options = 0; - if (args[3] == "1") { - options = options | IStatsManager::FLAG_REQUIRE_STAGING; - } - if (args[4] == "1") { - options = options | IStatsManager::FLAG_ROLLBACK_ENABLED; - } - if (args[5] == "1") { - options = options | IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR; - } int32_t state = atoi(args[6].c_str()); vector<int64_t> experimentIds; if (argCount == 8) { @@ -874,14 +806,30 @@ status_t StatsService::cmd_log_binary_push(int out, const Vector<String8>& args) } } dprintf(out, "Logging BinaryPushStateChanged\n"); - sendBinaryPushStateChangedAtom(trainName, trainVersion, options, state, experimentIds); + vector<uint8_t> experimentIdBytes; + writeExperimentIdsToProto(experimentIds, &experimentIdBytes); + LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0); + mProcessor->OnLogEvent(&event); return NO_ERROR; } status_t StatsService::cmd_print_pulled_metrics(int out, const Vector<String8>& args) { int s = atoi(args[1].c_str()); - vector<shared_ptr<LogEvent> > stats; - if (mPullerManager->Pull(s, &stats)) { + vector<int32_t> uids; + if (args.size() > 2) { + string package = string(args[2].c_str()); + auto it = UidMap::sAidToUidMapping.find(package); + if (it != UidMap::sAidToUidMapping.end()) { + uids.push_back(it->second); + } else { + set<int32_t> uids_set = mUidMap->getAppUid(package); + uids.insert(uids.end(), uids_set.begin(), uids_set.end()); + } + } else { + uids.push_back(AID_SYSTEM); + } + vector<shared_ptr<LogEvent>> stats; + if (mPullerManager->Pull(s, uids, getElapsedRealtimeNs(), &stats)) { for (const auto& it : stats) { dprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str()); } @@ -905,10 +853,9 @@ status_t StatsService::cmd_dump_memory_info(int out) { } status_t StatsService::cmd_clear_puller_cache(int out) { - IPCThreadState* ipc = IPCThreadState::self(); VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", - ipc->getCallingPid(), ipc->getCallingUid()); - if (checkCallingPermission(String16(kPermissionDump))) { + AIBinder_getCallingPid(), AIBinder_getCallingUid()); + if (checkPermission(kPermissionDump)) { int cleared = mPullerManager->ForceClearPullerCache(); dprintf(out, "Puller removed %d cached data!\n", cleared); return NO_ERROR; @@ -918,10 +865,9 @@ status_t StatsService::cmd_clear_puller_cache(int out) { } status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) { - IPCThreadState* ipc = IPCThreadState::self(); - VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", ipc->getCallingPid(), - ipc->getCallingUid()); - if (checkCallingPermission(String16(kPermissionDump))) { + VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", AIBinder_getCallingPid(), + AIBinder_getCallingUid()); + if (checkPermission(kPermissionDump)) { bool enabled = true; if (args.size() >= 2) { enabled = atoi(args[1].c_str()) != 0; @@ -952,24 +898,24 @@ bool StatsService::getUidFromString(const char* s, int32_t& uid) { } uid = goodUid; - int32_t callingUid = IPCThreadState::self()->getCallingUid(); + int32_t callingUid = AIBinder_getCallingUid(); return mEngBuild // UserDebug/EngBuild are allowed to impersonate uids. || (callingUid == goodUid) // Anyone can 'impersonate' themselves. || (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL. } -Status StatsService::informAllUidData(const ParcelFileDescriptor& fd) { +Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) { ENFORCE_UID(AID_SYSTEM); // Read stream into buffer. string buffer; if (!android::base::ReadFdToString(fd.get(), &buffer)) { - return exception(Status::EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe."); + return exception(EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe."); } // Parse buffer. UidData uidData; if (!uidData.ParseFromString(buffer)) { - return exception(Status::EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData."); + return exception(EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData."); } vector<String16> versionStrings; @@ -1000,24 +946,31 @@ Status StatsService::informAllUidData(const ParcelFileDescriptor& fd) { packageNames, installers); + mBootCompleteTrigger.markComplete(kUidMapReceivedTag); VLOG("StatsService::informAllUidData UidData proto parsed successfully."); return Status::ok(); } -Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version, - const String16& version_string, const String16& installer) { +Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version, + const string& versionString, const string& installer) { ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::informOnePackage was called"); - mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version, version_string, installer); + String16 utf16App = String16(app.c_str()); + String16 utf16VersionString = String16(versionString.c_str()); + String16 utf16Installer = String16(installer.c_str()); + + mUidMap->updateApp(getElapsedRealtimeNs(), utf16App, uid, version, utf16VersionString, + utf16Installer); return Status::ok(); } -Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) { +Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) { ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::informOnePackageRemoved was called"); - mUidMap->removeApp(getElapsedRealtimeNs(), app, uid); + String16 utf16App = String16(app.c_str()); + mUidMap->removeApp(getElapsedRealtimeNs(), utf16App, uid); mConfigManager->RemoveConfigs(uid); return Status::ok(); } @@ -1077,11 +1030,12 @@ Status StatsService::informDeviceShutdown() { VLOG("StatsService::informDeviceShutdown"); mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST); mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs()); + mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs()); return Status::ok(); } void StatsService::sayHiToStatsCompanion() { - sp<IStatsCompanionService> statsCompanion = getStatsCompanionService(); + shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService(); if (statsCompanion != nullptr) { VLOG("Telling statsCompanion that statsd is ready"); statsCompanion->statsdReady(); @@ -1094,24 +1048,32 @@ Status StatsService::statsCompanionReady() { ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::statsCompanionReady was called"); - sp<IStatsCompanionService> statsCompanion = getStatsCompanionService(); + shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService(); if (statsCompanion == nullptr) { - return Status::fromExceptionCode( - Status::EX_NULL_POINTER, - "statscompanion unavailable despite it contacting statsd!"); + return exception(EX_NULL_POINTER, + "StatsCompanion unavailable despite it contacting statsd."); } VLOG("StatsService::statsCompanionReady linking to statsCompanion."); - IInterface::asBinder(statsCompanion)->linkToDeath(this); + AIBinder_linkToDeath(statsCompanion->asBinder().get(), + mStatsCompanionServiceDeathRecipient.get(), this); mPullerManager->SetStatsCompanionService(statsCompanion); mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion); mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion); - SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion); + return Status::ok(); +} + +Status StatsService::bootCompleted() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::bootCompleted was called"); + mBootCompleteTrigger.markComplete(kBootCompleteTag); return Status::ok(); } void StatsService::Startup() { mConfigManager->Startup(); mProcessor->LoadActiveConfigsFromDisk(); + mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs()); } void StatsService::Terminate() { @@ -1119,6 +1081,7 @@ void StatsService::Terminate() { if (mProcessor != nullptr) { mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST); mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs()); + mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs()); } } @@ -1130,12 +1093,11 @@ void StatsService::OnLogEvent(LogEvent* event) { } } -Status StatsService::getData(int64_t key, const String16& packageName, vector<uint8_t>* output) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); +Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); - ConfigKey configKey(ipc->getCallingUid(), key); + VLOG("StatsService::getData with Uid %i", callingUid); + ConfigKey configKey(callingUid, key); // The dump latency does not matter here since we do not include the current bucket, we do not // need to pull any new data anyhow. mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/, @@ -1143,27 +1105,21 @@ Status StatsService::getData(int64_t key, const String16& packageName, vector<ui return Status::ok(); } -Status StatsService::getMetadata(const String16& packageName, vector<uint8_t>* output) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); +Status StatsService::getMetadata(vector<uint8_t>* output) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(), - ipc->getCallingUid()); StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters. return Status::ok(); } Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config, - const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - if (addConfigurationChecked(ipc->getCallingUid(), key, config)) { + if (addConfigurationChecked(callingUid, key, config)) { return Status::ok(); } else { - ALOGE("Could not parse malformatted StatsdConfig"); - return Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT, - "config does not correspond to a StatsdConfig proto"); + return exception(EX_ILLEGAL_ARGUMENT, "Could not parse malformatted StatsdConfig."); } } @@ -1179,23 +1135,21 @@ bool StatsService::addConfigurationChecked(int uid, int64_t key, const vector<ui return true; } -Status StatsService::removeDataFetchOperation(int64_t key, const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); - - IPCThreadState* ipc = IPCThreadState::self(); - ConfigKey configKey(ipc->getCallingUid(), key); +Status StatsService::removeDataFetchOperation(int64_t key, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); + ConfigKey configKey(callingUid, key); mConfigManager->RemoveConfigReceiver(configKey); return Status::ok(); } Status StatsService::setDataFetchOperation(int64_t key, - const sp<android::IBinder>& intentSender, - const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); + const shared_ptr<IPendingIntentRef>& pir, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - ConfigKey configKey(ipc->getCallingUid(), key); - mConfigManager->SetConfigReceiver(configKey, intentSender); + ConfigKey configKey(callingUid, key); + mConfigManager->SetConfigReceiver(configKey, pir); if (StorageManager::hasConfigMetricsReport(configKey)) { VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk", configKey.ToString().c_str()); @@ -1204,301 +1158,170 @@ Status StatsService::setDataFetchOperation(int64_t key, return Status::ok(); } -Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender, - const String16& packageName, +Status StatsService::setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir, + const int32_t callingUid, vector<int64_t>* output) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - int uid = ipc->getCallingUid(); - mConfigManager->SetActiveConfigsChangedReceiver(uid, intentSender); + mConfigManager->SetActiveConfigsChangedReceiver(callingUid, pir); if (output != nullptr) { - mProcessor->GetActiveConfigs(uid, *output); + mProcessor->GetActiveConfigs(callingUid, *output); } else { ALOGW("StatsService::setActiveConfigsChanged output was nullptr"); } return Status::ok(); } -Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); +Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid()); + mConfigManager->RemoveActiveConfigsChangedReceiver(callingUid); return Status::ok(); } -Status StatsService::removeConfiguration(int64_t key, const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); +Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - ConfigKey configKey(ipc->getCallingUid(), key); + ConfigKey configKey(callingUid, key); mConfigManager->RemoveConfig(configKey); - SubscriberReporter::getInstance().removeConfig(configKey); return Status::ok(); } Status StatsService::setBroadcastSubscriber(int64_t configId, int64_t subscriberId, - const sp<android::IBinder>& intentSender, - const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); + const shared_ptr<IPendingIntentRef>& pir, + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::setBroadcastSubscriber called."); - IPCThreadState* ipc = IPCThreadState::self(); - ConfigKey configKey(ipc->getCallingUid(), configId); + ConfigKey configKey(callingUid, configId); SubscriberReporter::getInstance() - .setBroadcastSubscriber(configKey, subscriberId, intentSender); + .setBroadcastSubscriber(configKey, subscriberId, pir); return Status::ok(); } Status StatsService::unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId, - const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::unsetBroadcastSubscriber called."); - IPCThreadState* ipc = IPCThreadState::self(); - ConfigKey configKey(ipc->getCallingUid(), configId); + ConfigKey configKey(callingUid, configId); SubscriberReporter::getInstance() .unsetBroadcastSubscriber(configKey, subscriberId); return Status::ok(); } -Status StatsService::sendAppBreadcrumbAtom(int32_t label, int32_t state) { - // Permission check not necessary as it's meant for applications to write to - // statsd. - android::util::stats_write(util::APP_BREADCRUMB_REPORTED, - IPCThreadState::self()->getCallingUid(), label, - state); +Status StatsService::allPullersFromBootRegistered() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::allPullersFromBootRegistered was called"); + mBootCompleteTrigger.markComplete(kAllPullersRegisteredTag); return Status::ok(); } -Status StatsService::registerPullerCallback(int32_t atomTag, - const sp<android::os::IStatsPullerCallback>& pullerCallback, - const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); - - VLOG("StatsService::registerPullerCallback called."); - mPullerManager->RegisterPullerCallback(atomTag, pullerCallback); +Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownMillis, + int64_t timeoutMillis, + const std::vector<int32_t>& additiveFields, + const shared_ptr<IPullAtomCallback>& pullerCallback) { + ENFORCE_UID(AID_SYSTEM); + VLOG("StatsService::registerPullAtomCallback called."); + mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis), + MillisToNano(timeoutMillis), additiveFields, + pullerCallback); return Status::ok(); } -Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); - - VLOG("StatsService::unregisterPullerCallback called."); - mPullerManager->UnregisterPullerCallback(atomTag); +Status StatsService::registerNativePullAtomCallback( + int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, + const std::vector<int32_t>& additiveFields, + const shared_ptr<IPullAtomCallback>& pullerCallback) { + if (!checkPermission(kPermissionRegisterPullAtom)) { + return exception( + EX_SECURITY, + StringPrintf("Uid %d does not have the %s permission when registering atom %d", + AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag)); + } + VLOG("StatsService::registerNativePullAtomCallback called."); + int32_t uid = AIBinder_getCallingUid(); + mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis), + MillisToNano(timeoutMillis), additiveFields, + pullerCallback); return Status::ok(); } -Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn, - const int64_t trainVersionCodeIn, - const int options, - const int32_t state, - const std::vector<int64_t>& experimentIdsIn) { - // Note: We skip the usage stats op check here since we do not have a package name. - // This is ok since we are overloading the usage_stats permission. - // This method only sends data, it does not receive it. - pid_t pid = IPCThreadState::self()->getCallingPid(); - uid_t uid = IPCThreadState::self()->getCallingUid(); - // Root, system, and shell always have access - if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { - // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionDump)); - } - if (!checkCallingPermission(String16(kPermissionUsage))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionUsage)); - } - } - - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); - - bool resetExperimentIds = false; - int64_t trainVersionCode = trainVersionCodeIn; - std::string trainNameUtf8 = std::string(String8(trainNameIn).string()); - if (readTrainInfoSuccess) { - // Keep the old train version if we received an empty version. - if (trainVersionCodeIn == -1) { - trainVersionCode = trainInfoOnDisk.trainVersionCode; - } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) { - // Reset experiment ids if we receive a new non-empty train version. - resetExperimentIds = true; - } - - // Keep the old train name if we received an empty train name. - if (trainNameUtf8.size() == 0) { - trainNameUtf8 = trainInfoOnDisk.trainName; - } else if (trainNameUtf8 != trainInfoOnDisk.trainName) { - // Reset experiment ids if we received a new valid train name. - resetExperimentIds = true; - } - - // Reset if we received a different experiment id. - if (!experimentIdsIn.empty() && - (trainInfoOnDisk.experimentIds.empty() || - experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) { - resetExperimentIds = true; - } - } - - // Find the right experiment IDs - std::vector<int64_t> experimentIds; - if (resetExperimentIds || !readTrainInfoSuccess) { - experimentIds = experimentIdsIn; - } else { - experimentIds = trainInfoOnDisk.experimentIds; - } - - if (!experimentIds.empty()) { - int64_t firstId = experimentIds[0]; - switch (state) { - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: - experimentIds.push_back(firstId + 1); - break; - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: - experimentIds.push_back(firstId + 2); - break; - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: - experimentIds.push_back(firstId + 3); - break; - } - } - - // Flatten the experiment IDs to proto - vector<uint8_t> experimentIdsProtoBuffer; - writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer); - StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds); - - userid_t userId = multiuser_get_user_id(uid); - bool requiresStaging = options & IStatsManager::FLAG_REQUIRE_STAGING; - bool rollbackEnabled = options & IStatsManager::FLAG_ROLLBACK_ENABLED; - bool requiresLowLatencyMonitor = options & IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR; - LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled, - requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId); - mProcessor->OnLogEvent(&event); +Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) { + ENFORCE_UID(AID_SYSTEM); + VLOG("StatsService::unregisterPullAtomCallback called."); + mPullerManager->UnregisterPullAtomCallback(uid, atomTag); return Status::ok(); } -Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn, - const android::String16& packageNameIn, - const int64_t packageVersionCodeIn, - const int32_t rollbackReasonIn, - const android::String16& - failingPackageNameIn) { - // Note: We skip the usage stats op check here since we do not have a package name. - // This is ok since we are overloading the usage_stats permission. - // This method only sends data, it does not receive it. - pid_t pid = IPCThreadState::self()->getCallingPid(); - uid_t uid = IPCThreadState::self()->getCallingUid(); - // Root, system, and shell always have access - if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { - // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionDump)); - } - if (!checkCallingPermission(String16(kPermissionUsage))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionUsage)); - } - } - - android::util::stats_write(android::util::WATCHDOG_ROLLBACK_OCCURRED, - rollbackTypeIn, String8(packageNameIn).string(), packageVersionCodeIn, - rollbackReasonIn, String8(failingPackageNameIn).string()); - - // Fast return to save disk read. - if (rollbackTypeIn != android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS - && rollbackTypeIn != - android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE) { - return Status::ok(); +Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) { + if (!checkPermission(kPermissionRegisterPullAtom)) { + return exception( + EX_SECURITY, + StringPrintf("Uid %d does not have the %s permission when unregistering atom %d", + AIBinder_getCallingUid(), kPermissionRegisterPullAtom, atomTag)); } - - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); - - if (!readTrainInfoSuccess) { - return Status::ok(); - } - std::vector<int64_t> experimentIds = trainInfoOnDisk.experimentIds; - if (experimentIds.empty()) { - return Status::ok(); - } - switch (rollbackTypeIn) { - case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: - experimentIds.push_back(experimentIds[0] + 4); - break; - case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: - experimentIds.push_back(experimentIds[0] + 5); - break; - } - StorageManager::writeTrainInfo(trainInfoOnDisk.trainVersionCode, trainInfoOnDisk.trainName, - trainInfoOnDisk.status, experimentIds); + VLOG("StatsService::unregisterNativePullAtomCallback called."); + int32_t uid = AIBinder_getCallingUid(); + mPullerManager->UnregisterPullAtomCallback(uid, atomTag); return Status::ok(); } Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { - uid_t uid = IPCThreadState::self()->getCallingUid(); - - // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d lacks permission %s", uid, kPermissionDump)); - } - if (!checkCallingPermission(String16(kPermissionUsage))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage)); - } + ENFORCE_UID(AID_SYSTEM); // TODO: add verifier permission + experimentIdsOut->clear(); // Read the latest train info - InstallTrainInfo trainInfo; - if (!StorageManager::readTrainInfo(trainInfo)) { + vector<InstallTrainInfo> trainInfoList = StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { // No train info means no experiment IDs, return an empty list - experimentIdsOut->clear(); return Status::ok(); } // Copy the experiment IDs to the out vector - experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end()); + for (InstallTrainInfo& trainInfo : trainInfoList) { + experimentIdsOut->insert(experimentIdsOut->end(), + trainInfo.experimentIds.begin(), + trainInfo.experimentIds.end()); + } return Status::ok(); } -void StatsService::binderDied(const wp <IBinder>& who) { +void StatsService::statsCompanionServiceDied(void* cookie) { + auto thiz = static_cast<StatsService*>(cookie); + thiz->statsCompanionServiceDiedImpl(); +} + +void StatsService::statsCompanionServiceDiedImpl() { ALOGW("statscompanion service died"); StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec()); if (mProcessor != nullptr) { ALOGW("Reset statsd upon system server restarts."); int64_t systemServerRestartNs = getElapsedRealtimeNs(); - ProtoOutputStream proto; + ProtoOutputStream activeConfigsProto; mProcessor->WriteActiveConfigsToProtoOutputStream(systemServerRestartNs, - STATSCOMPANION_DIED, &proto); - + STATSCOMPANION_DIED, &activeConfigsProto); + metadata::StatsMetadataList metadataList; + mProcessor->WriteMetadataToProto(getWallClockNs(), + systemServerRestartNs, &metadataList); mProcessor->WriteDataToDisk(STATSCOMPANION_DIED, FAST); mProcessor->resetConfigs(); std::string serializedActiveConfigs; - if (proto.serializeToString(&serializedActiveConfigs)) { + if (activeConfigsProto.serializeToString(&serializedActiveConfigs)) { ActiveConfigList activeConfigs; if (activeConfigs.ParseFromString(serializedActiveConfigs)) { mProcessor->SetConfigsActiveState(activeConfigs, systemServerRestartNs); } } + mProcessor->SetMetadataState(metadataList, getWallClockNs(), systemServerRestartNs); } mAnomalyAlarmMonitor->setStatsCompanionService(nullptr); mPeriodicAlarmMonitor->setStatsCompanionService(nullptr); - SubscriberReporter::getInstance().setStatsCompanionService(nullptr); mPullerManager->SetStatsCompanionService(nullptr); } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 991a205e111d..324ffbd65e51 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -17,7 +17,14 @@ #ifndef STATS_SERVICE_H #define STATS_SERVICE_H +#include <aidl/android/os/BnStatsd.h> +#include <aidl/android/os/IPendingIntentRef.h> +#include <aidl/android/os/IPullAtomCallback.h> #include <gtest/gtest_prod.h> +#include <utils/Looper.h> + +#include <mutex> + #include "StatsLogProcessor.h" #include "anomaly/AlarmMonitor.h" #include "config/ConfigManager.h" @@ -26,32 +33,25 @@ #include "packages/UidMap.h" #include "shell/ShellSubscriber.h" #include "statscompanion_util.h" - -#include <android/frameworks/stats/1.0/IStats.h> -#include <android/frameworks/stats/1.0/types.h> -#include <android/os/BnStatsManager.h> -#include <android/os/IStatsCompanionService.h> -#include <android/os/IStatsManager.h> -#include <binder/IResultReceiver.h> -#include <binder/ParcelFileDescriptor.h> -#include <utils/Looper.h> - -#include <mutex> +#include "utils/MultiConditionTrigger.h" using namespace android; -using namespace android::binder; -using namespace android::frameworks::stats::V1_0; using namespace android::os; using namespace std; +using Status = ::ndk::ScopedAStatus; +using aidl::android::os::BnStatsd; +using aidl::android::os::IPendingIntentRef; +using aidl::android::os::IPullAtomCallback; +using ::ndk::ScopedAIBinder_DeathRecipient; +using ::ndk::ScopedFileDescriptor; +using std::shared_ptr; + namespace android { namespace os { namespace statsd { -using android::hardware::Return; - -class StatsService : public BnStatsManager, - public IBinder::DeathRecipient { +class StatsService : public BnStatsd { public: StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue); virtual ~StatsService(); @@ -59,21 +59,21 @@ public: /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */ const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5; - virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); - virtual status_t dump(int fd, const Vector<String16>& args); - virtual status_t command(int inFd, int outFd, int err, Vector<String8>& args, - sp<IResultReceiver> resultReceiver); + virtual status_t dump(int fd, const char** args, uint32_t numArgs) override; + virtual status_t handleShellCommand(int in, int out, int err, const char** argv, + uint32_t argc) override; virtual Status systemRunning(); virtual Status statsCompanionReady(); + virtual Status bootCompleted(); virtual Status informAnomalyAlarmFired(); virtual Status informPollAlarmFired(); virtual Status informAlarmForSubscriberTriggeringFired(); - virtual Status informAllUidData(const ParcelFileDescriptor& fd); - virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version, - const String16& version_string, const String16& installer); - virtual Status informOnePackageRemoved(const String16& app, int32_t uid); + virtual Status informAllUidData(const ScopedFileDescriptor& fd); + virtual Status informOnePackage(const string& app, int32_t uid, int64_t version, + const string& versionString, const string& installer); + virtual Status informOnePackageRemoved(const string& app, int32_t uid); virtual Status informDeviceShutdown(); /** @@ -95,15 +95,14 @@ public: * Binder call for clients to request data for this configuration key. */ virtual Status getData(int64_t key, - const String16& packageName, + const int32_t callingUid, vector<uint8_t>* output) override; /** * Binder call for clients to get metadata across all configs in statsd. */ - virtual Status getMetadata(const String16& packageName, - vector<uint8_t>* output) override; + virtual Status getMetadata(vector<uint8_t>* output) override; /** @@ -112,103 +111,92 @@ public: */ virtual Status addConfiguration(int64_t key, const vector<uint8_t>& config, - const String16& packageName) override; + const int32_t callingUid) override; /** * Binder call to let clients register the data fetch operation for a configuration. */ virtual Status setDataFetchOperation(int64_t key, - const sp<android::IBinder>& intentSender, - const String16& packageName) override; + const shared_ptr<IPendingIntentRef>& pir, + const int32_t callingUid) override; /** * Binder call to remove the data fetch operation for the specified config key. */ virtual Status removeDataFetchOperation(int64_t key, - const String16& packageName) override; + const int32_t callingUid) override; /** * Binder call to let clients register the active configs changed operation. */ - virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender, - const String16& packageName, + virtual Status setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir, + const int32_t callingUid, vector<int64_t>* output) override; /** * Binder call to remove the active configs changed operation for the specified package.. */ - virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override; + virtual Status removeActiveConfigsChangedOperation(const int32_t callingUid) override; /** * Binder call to allow clients to remove the specified configuration. */ virtual Status removeConfiguration(int64_t key, - const String16& packageName) override; + const int32_t callingUid) override; /** - * Binder call to associate the given config's subscriberId with the given intentSender. - * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder). + * Binder call to associate the given config's subscriberId with the given pendingIntentRef. */ virtual Status setBroadcastSubscriber(int64_t configId, int64_t subscriberId, - const sp<android::IBinder>& intentSender, - const String16& packageName) override; + const shared_ptr<IPendingIntentRef>& pir, + const int32_t callingUid) override; /** - * Binder call to unassociate the given config's subscriberId with any intentSender. + * Binder call to unassociate the given config's subscriberId with any pendingIntentRef. */ virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId, - const String16& packageName) override; + const int32_t callingUid) override; /** Inform statsCompanion that statsd is ready. */ virtual void sayHiToStatsCompanion(); /** - * Binder call to get AppBreadcrumbReported atom. + * Binder call to notify statsd that all pullers from boot have been registered. */ - virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override; + virtual Status allPullersFromBootRegistered(); /** - * Binder call to register a callback function for a vendor pulled atom. - * Note: this atom must NOT have uid as a field. + * Binder call to register a callback function for a pulled atom. */ - virtual Status registerPullerCallback(int32_t atomTag, - const sp<android::os::IStatsPullerCallback>& pullerCallback, - const String16& packageName) override; + virtual Status registerPullAtomCallback( + int32_t uid, int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, + const std::vector<int32_t>& additiveFields, + const shared_ptr<IPullAtomCallback>& pullerCallback) override; /** - * Binder call to unregister any existing callback function for a vendor pulled atom. + * Binder call to register a callback function for a pulled atom. */ - virtual Status unregisterPullerCallback(int32_t atomTag, const String16& packageName) override; + virtual Status registerNativePullAtomCallback( + int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, + const std::vector<int32_t>& additiveFields, + const shared_ptr<IPullAtomCallback>& pullerCallback) override; /** - * Binder call to log BinaryPushStateChanged atom. + * Binder call to unregister any existing callback for the given uid and atom. */ - virtual Status sendBinaryPushStateChangedAtom( - const android::String16& trainNameIn, - const int64_t trainVersionCodeIn, - const int options, - const int32_t state, - const std::vector<int64_t>& experimentIdsIn) override; + virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override; /** - * Binder call to log WatchdogRollbackOccurred atom. + * Binder call to unregister any existing callback for the given atom and calling uid. */ - virtual Status sendWatchdogRollbackOccurredAtom( - const int32_t rollbackTypeIn, - const android::String16& packageNameIn, - const int64_t packageVersionCodeIn, - const int32_t rollbackReasonIn, - const android::String16& failingPackageNameIn) override; + virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override; /** * Binder call to get registered experiment IDs. */ virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut); - /** IBinder::DeathRecipient */ - virtual void binderDied(const wp<IBinder>& who) override; - private: /** * Load system properties at init. @@ -340,6 +328,17 @@ private: void set_config(int uid, const string& name, const StatsdConfig& config); /** + * Death recipient callback that is called when StatsCompanionService dies. + * The cookie is a pointer to a StatsService object. + */ + static void statsCompanionServiceDied(void* cookie); + + /** + * Implementation of statsCompanionServiceDied. + */ + void statsCompanionServiceDiedImpl(); + + /** * Tracks the uid <--> package name mapping. */ sp<UidMap> mUidMap; @@ -382,17 +381,27 @@ private: mutable mutex mShellSubscriberMutex; std::shared_ptr<LogEventQueue> mEventQueue; + MultiConditionTrigger mBootCompleteTrigger; + static const inline string kBootCompleteTag = "BOOT_COMPLETE"; + static const inline string kUidMapReceivedTag = "UID_MAP"; + static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED"; + + ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient; + FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); FRIEND_TEST(StatsServiceTest, TestAddConfig_simple); FRIEND_TEST(StatsServiceTest, TestAddConfig_empty); FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid); FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp); + FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit); + FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket); + FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket); }; diff --git a/cmds/statsd/src/external/CarStatsPuller.h b/cmds/statsd/src/annotations.h index ca0f1a9c9a17..cf7f5433663f 100644 --- a/cmds/statsd/src/external/CarStatsPuller.h +++ b/cmds/statsd/src/annotations.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 The Android Open Source Project + * Copyright (C) 2020 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. @@ -16,21 +16,18 @@ #pragma once -#include "StatsPuller.h" - namespace android { namespace os { namespace statsd { -/** - * Pull atoms from CarService. - */ -class CarStatsPuller : public StatsPuller { -public: - explicit CarStatsPuller(const int tagId); - bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override; -}; +const uint8_t ANNOTATION_ID_IS_UID = 1; +const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2; +const uint8_t ANNOTATION_ID_PRIMARY_FIELD = 3; +const uint8_t ANNOTATION_ID_EXCLUSIVE_STATE = 4; +const uint8_t ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5; +const uint8_t ANNOTATION_ID_TRIGGER_STATE_RESET = 7; +const uint8_t ANNOTATION_ID_STATE_NESTED = 8; -} // namespace statsd -} // namespace os -} // namespace android +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.cpp b/cmds/statsd/src/anomaly/AlarmMonitor.cpp index bc36dadacddb..b632d040eb43 100644 --- a/cmds/statsd/src/anomaly/AlarmMonitor.cpp +++ b/cmds/statsd/src/anomaly/AlarmMonitor.cpp @@ -26,17 +26,19 @@ namespace statsd { AlarmMonitor::AlarmMonitor( uint32_t minDiffToUpdateRegisteredAlarmTimeSec, - const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm, - const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm) - : mRegisteredAlarmTimeSec(0), mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec), + const std::function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>& updateAlarm, + const std::function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm) + : mRegisteredAlarmTimeSec(0), + mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec), mUpdateAlarm(updateAlarm), mCancelAlarm(cancelAlarm) {} AlarmMonitor::~AlarmMonitor() {} -void AlarmMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) { +void AlarmMonitor::setStatsCompanionService( + shared_ptr<IStatsCompanionService> statsCompanionService) { std::lock_guard<std::mutex> lock(mLock); - sp<IStatsCompanionService> tmpForLock = mStatsCompanionService; + shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService; mStatsCompanionService = statsCompanionService; if (statsCompanionService == nullptr) { VLOG("Erasing link to statsCompanionService"); diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.h b/cmds/statsd/src/anomaly/AlarmMonitor.h index 219695ef5e43..5c34e381ba0b 100644 --- a/cmds/statsd/src/anomaly/AlarmMonitor.h +++ b/cmds/statsd/src/anomaly/AlarmMonitor.h @@ -18,7 +18,7 @@ #include "anomaly/indexed_priority_queue.h" -#include <android/os/IStatsCompanionService.h> +#include <aidl/android/os/IStatsCompanionService.h> #include <utils/RefBase.h> #include <unordered_set> @@ -26,7 +26,9 @@ using namespace android; -using android::os::IStatsCompanionService; +using aidl::android::os::IStatsCompanionService; +using std::function; +using std::shared_ptr; using std::unordered_set; namespace android { @@ -64,8 +66,9 @@ public: * alarm. */ AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec, - const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm, - const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm); + const function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>& + updateAlarm, + const function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm); ~AlarmMonitor(); /** @@ -74,7 +77,7 @@ public: * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't * update IStatsCompanionService (until such time as it is set non-null). */ - void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService); + void setStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService); /** * Adds the given alarm (reference) to the queue. @@ -123,7 +126,7 @@ private: /** * Binder interface for communicating with StatsCompanionService. */ - sp<IStatsCompanionService> mStatsCompanionService = nullptr; + shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr; /** * Amount by which the soonest projected alarm must differ from @@ -147,10 +150,10 @@ private: int64_t secToMs(uint32_t timeSec); // Callback function to update the alarm via StatsCompanionService. - std::function<void(const sp<IStatsCompanionService>, int64_t)> mUpdateAlarm; + std::function<void(const shared_ptr<IStatsCompanionService>, int64_t)> mUpdateAlarm; // Callback function to cancel the alarm via StatsCompanionService. - std::function<void(const sp<IStatsCompanionService>)> mCancelAlarm; + std::function<void(const shared_ptr<IStatsCompanionService>)> mCancelAlarm; }; diff --git a/cmds/statsd/src/anomaly/AlarmTracker.cpp b/cmds/statsd/src/anomaly/AlarmTracker.cpp index a21327b4aae0..6d9beb8f718d 100644 --- a/cmds/statsd/src/anomaly/AlarmTracker.cpp +++ b/cmds/statsd/src/anomaly/AlarmTracker.cpp @@ -23,7 +23,6 @@ #include "stats_util.h" #include "storage/StorageManager.h" -#include <statslog.h> #include <time.h> namespace android { diff --git a/cmds/statsd/src/anomaly/AlarmTracker.h b/cmds/statsd/src/anomaly/AlarmTracker.h index 2fb3e3b3e26a..2da4a18682ae 100644 --- a/cmds/statsd/src/anomaly/AlarmTracker.h +++ b/cmds/statsd/src/anomaly/AlarmTracker.h @@ -22,12 +22,9 @@ #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alarm -#include <android/os/IStatsCompanionService.h> #include <stdlib.h> #include <utils/RefBase.h> -using android::os::IStatsCompanionService; - namespace android { namespace os { namespace statsd { diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index 7ace44eef564..619752c7c44a 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -18,14 +18,16 @@ #include "Log.h" #include "AnomalyTracker.h" -#include "subscriber_util.h" #include "external/Perfetto.h" #include "guardrail/StatsdStats.h" +#include "metadata_util.h" +#include "stats_log_util.h" +#include "subscriber_util.h" #include "subscriber/IncidentdReporter.h" #include "subscriber/SubscriberReporter.h" #include <inttypes.h> -#include <statslog.h> +#include <statslog_statsd.h> #include <time.h> namespace android { @@ -235,8 +237,8 @@ void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, int64_t metricId StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id()); // TODO(b/110564268): This should also take in the const MetricDimensionKey& key? - android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(), - mConfigKey.GetId(), mAlert.id()); + util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(), + mConfigKey.GetId(), mAlert.id()); } void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs, @@ -262,6 +264,58 @@ void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t me triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions); } +bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::AlertMetadata* alertMetadata) { + bool metadataWritten = false; + + if (mRefractoryPeriodEndsSec.empty()) { + return false; + } + + for (const auto& it: mRefractoryPeriodEndsSec) { + // Do not write the timestamp to disk if it has already expired + if (it.second < systemElapsedTimeNs / NS_PER_SEC) { + continue; + } + + metadataWritten = true; + if (alertMetadata->alert_dim_keyed_data_size() == 0) { + alertMetadata->set_alert_id(mAlert.id()); + } + + metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data(); + // We convert and write the refractory_end_sec to wall clock time because we do not know + // when statsd will start again. + int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) + + (it.second - systemElapsedTimeNs / NS_PER_SEC)); + + keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec); + writeMetricDimensionKeyToMetadataDimensionKey( + it.first, keyedData->mutable_dimension_key()); + } + + return metadataWritten; +} + +void AnomalyTracker::loadAlertMetadata( + const metadata::AlertMetadata& alertMetadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + for (const metadata::AlertDimensionKeyedData& keyedData : + alertMetadata.alert_dim_keyed_data()) { + if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) { + // Do not update the timestamp if it has already expired. + continue; + } + MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto( + keyedData.dimension_key()); + int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() - + currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC; + mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec; + } +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index 794ee988ef55..bf36a3bc8990 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -24,6 +24,7 @@ #include "AlarmMonitor.h" #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert +#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" // AlertMetadata #include "stats_util.h" // HashableDimensionKey and DimToValMap namespace android { @@ -112,6 +113,17 @@ public: return; // The base AnomalyTracker class doesn't have alarms. } + // Writes metadata of the alert (refractory_period_end_sec) to AlertMetadata. + // Returns true if at least one element is written to alertMetadata. + bool writeAlertMetadataToProto( + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, metadata::AlertMetadata* alertMetadata); + + void loadAlertMetadata( + const metadata::AlertMetadata& alertMetadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); + protected: // For testing only. // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto index 16c936c41559..ff5717e4fa78 100644 --- a/cmds/statsd/src/atom_field_options.proto +++ b/cmds/statsd/src/atom_field_options.proto @@ -23,45 +23,72 @@ option java_outer_classname = "AtomFieldOptions"; import "google/protobuf/descriptor.proto"; -enum StateField { - // Default value for fields that are not primary or exclusive state. - STATE_FIELD_UNSET = 0; - // Fields that represent the key that the state belongs to. - PRIMARY = 1; - // The field that represents the state. It's an exclusive state. - EXCLUSIVE = 2; -} - -// 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).primary_field = true]; +// optional android.app.ProcessStateEnum state = +// 2 [(state_field_option).exclusive_state = true]; // } -// 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).primary_field = true]; +// optional int32 tid = 2 [(state_field_option).primary_field = true]; +// optional int32 state = 3 [(state_field_option).exclusive_state = true]; // } // // 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).exclusive_state = true]; // } // -// 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).primary_field_first_uid = true]; +// +// 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).exclusive_state = true]; +// } message StateAtomFieldOption { - optional StateField option = 1 [default = STATE_FIELD_UNSET]; + // Fields that represent the key that the state belongs to. + // Used on simple proto fields. Do not use on attribution chains. + optional bool primary_field = 1 [default = false]; + + // The field that represents the state. It's an exclusive state. + optional bool exclusive_state = 2 [default = false]; + + // Used on an attribution chain field to indicate that the first uid is the + // primary field. + optional bool primary_field_first_uid = 3 [default = false]; + + // 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 = 4; + + // 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 trigger_state_reset_value = 5; + + // If the state change needs to count nesting. + optional bool nested = 6 [default = true]; } // Used to generate StatsLog.write APIs. @@ -83,7 +110,7 @@ extend google.protobuf.FieldOptions { optional LogMode log_mode = 50002 [default = MODE_AUTOMATIC]; - optional bool allow_from_any_uid = 50003 [default = false]; + repeated string module = 50004; - optional string log_from_module = 50004; -}
\ No newline at end of file + optional bool truncate_timestamp = 50005 [default = false]; +} diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index b8de3f0eff75..ab1d3cbd232f 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -24,6 +24,8 @@ import "frameworks/base/cmds/statsd/src/atom_field_options.proto"; import "frameworks/base/core/proto/android/app/enums.proto"; import "frameworks/base/core/proto/android/app/job/enums.proto"; import "frameworks/base/core/proto/android/app/settings_enums.proto"; +import "frameworks/base/core/proto/android/app/media_output_enum.proto"; +import "frameworks/base/core/proto/android/app/tvsettings_enums.proto"; import "frameworks/base/core/proto/android/bluetooth/a2dp/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto"; @@ -46,18 +48,22 @@ import "frameworks/base/core/proto/android/stats/dnsresolver/dns_resolver.proto" import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy.proto"; import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy_enums.proto"; import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; +import "frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; import "frameworks/base/core/proto/android/stats/intelligence/enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; import "frameworks/base/core/proto/android/stats/location/location_enums.proto"; import "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.proto"; -import "frameworks/base/core/proto/android/stats/otaupdate/updateengine_enums.proto"; +import "frameworks/base/core/proto/android/stats/mediaprovider/mediaprovider_enums.proto"; import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto"; import "frameworks/base/core/proto/android/stats/style/style_enums.proto"; +import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto"; import "frameworks/base/core/proto/android/telecomm/enums.proto"; import "frameworks/base/core/proto/android/telephony/enums.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; import "frameworks/base/core/proto/android/wifi/enums.proto"; +import "frameworks/base/core/proto/android/stats/textclassifier/textclassifier_enums.proto"; +import "frameworks/base/core/proto/android/stats/otaupdate/updateengine_enums.proto"; /** * The primary atom class. This message defines all of the available @@ -76,241 +82,258 @@ message Atom { // Pushed atoms start at 2. oneof pushed { // For StatsLog reasons, 1 is illegal and will not work. Must start at 2. - BleScanStateChanged ble_scan_state_changed = 2 [(log_from_module) = "bluetooth"]; - ProcessStateChanged process_state_changed = 3; - BleScanResultReceived ble_scan_result_received = 4 [(log_from_module) = "bluetooth"]; - SensorStateChanged sensor_state_changed = 5; - GpsScanStateChanged gps_scan_state_changed = 6; - SyncStateChanged sync_state_changed = 7; - ScheduledJobStateChanged scheduled_job_state_changed = 8; - ScreenBrightnessChanged screen_brightness_changed = 9; - WakelockStateChanged wakelock_state_changed = 10; - LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 11; - MobileRadioPowerStateChanged mobile_radio_power_state_changed = 12 [(log_from_module) = "framework"]; - WifiRadioPowerStateChanged wifi_radio_power_state_changed = 13 [(log_from_module) = "framework"]; - ActivityManagerSleepStateChanged activity_manager_sleep_state_changed = 14; - MemoryFactorStateChanged memory_factor_state_changed = 15; - ExcessiveCpuUsageReported excessive_cpu_usage_reported = 16; - CachedKillReported cached_kill_reported = 17; - ProcessMemoryStatReported process_memory_stat_reported = 18; - LauncherUIChanged launcher_event = 19; - BatterySaverModeStateChanged battery_saver_mode_state_changed = 20; - DeviceIdleModeStateChanged device_idle_mode_state_changed = 21; - DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22; - AudioStateChanged audio_state_changed = 23; - MediaCodecStateChanged media_codec_state_changed = 24; - CameraStateChanged camera_state_changed = 25; - FlashlightStateChanged flashlight_state_changed = 26; - UidProcessStateChanged uid_process_state_changed = 27; - ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28; - ScreenStateChanged screen_state_changed = 29; - BatteryLevelChanged battery_level_changed = 30; - ChargingStateChanged charging_state_changed = 31; - PluggedStateChanged plugged_state_changed = 32; - InteractiveStateChanged interactive_state_changed = 33; + BleScanStateChanged ble_scan_state_changed = 2 + [(module) = "bluetooth", (module) = "statsdtest"]; + ProcessStateChanged process_state_changed = 3 [(module) = "framework"]; + BleScanResultReceived ble_scan_result_received = 4 [(module) = "bluetooth"]; + SensorStateChanged sensor_state_changed = + 5 [(module) = "framework", (module) = "statsdtest"]; + GpsScanStateChanged gps_scan_state_changed = 6 [(module) = "framework"]; + SyncStateChanged sync_state_changed = 7 [(module) = "framework", (module) = "statsdtest"]; + ScheduledJobStateChanged scheduled_job_state_changed = + 8 [(module) = "framework", (module) = "statsdtest"]; + ScreenBrightnessChanged screen_brightness_changed = + 9 [(module) = "framework", (module) = "statsdtest"]; + WakelockStateChanged wakelock_state_changed = + 10 [(module) = "framework", (module) = "statsdtest"]; + LongPartialWakelockStateChanged long_partial_wakelock_state_changed = + 11 [(module) = "framework"]; + MobileRadioPowerStateChanged mobile_radio_power_state_changed = + 12 [(module) = "framework", (truncate_timestamp) = true]; + WifiRadioPowerStateChanged wifi_radio_power_state_changed = 13 [(module) = "framework"]; + ActivityManagerSleepStateChanged activity_manager_sleep_state_changed = + 14 [(module) = "framework"]; + MemoryFactorStateChanged memory_factor_state_changed = 15 [(module) = "framework"]; + ExcessiveCpuUsageReported excessive_cpu_usage_reported = 16 [(module) = "framework"]; + CachedKillReported cached_kill_reported = 17 [(module) = "framework"]; + ProcessMemoryStatReported process_memory_stat_reported = 18 [(module) = "framework"]; + LauncherUIChanged launcher_event = 19 [(module) = "sysui"]; + BatterySaverModeStateChanged battery_saver_mode_state_changed = + 20 [(module) = "framework", (module) = "statsdtest"]; + DeviceIdleModeStateChanged device_idle_mode_state_changed = 21 [(module) = "framework"]; + DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22 [(module) = "framework"]; + AudioStateChanged audio_state_changed = + 23 [(module) = "framework", (truncate_timestamp) = true]; + MediaCodecStateChanged media_codec_state_changed = 24 [(module) = "framework"]; + CameraStateChanged camera_state_changed = 25 [(module) = "framework"]; + FlashlightStateChanged flashlight_state_changed = 26 [(module) = "framework"]; + UidProcessStateChanged uid_process_state_changed = + 27 [(module) = "framework", (module) = "statsdtest"]; + ProcessLifeCycleStateChanged process_life_cycle_state_changed = + 28 [(module) = "framework", (module) = "statsdtest"]; + ScreenStateChanged screen_state_changed = + 29 [(module) = "framework", (module) = "statsdtest"]; + BatteryLevelChanged battery_level_changed = + 30 [(module) = "framework", (module) = "statsdtest"]; + ChargingStateChanged charging_state_changed = 31 [(module) = "framework"]; + PluggedStateChanged plugged_state_changed = 32 + [(module) = "framework", (module) = "statsdtest"]; + InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"]; TouchEventReported touch_event_reported = 34; - WakeupAlarmOccurred wakeup_alarm_occurred = 35; - KernelWakeupReported kernel_wakeup_reported = 36; - WifiLockStateChanged wifi_lock_state_changed = 37; - WifiSignalStrengthChanged wifi_signal_strength_changed = 38; - WifiScanStateChanged wifi_scan_state_changed = 39; - PhoneSignalStrengthChanged phone_signal_strength_changed = 40; - SettingChanged setting_changed = 41; - ActivityForegroundStateChanged activity_foreground_state_changed = 42; - IsolatedUidChanged isolated_uid_changed = 43; - PacketWakeupOccurred packet_wakeup_occurred = 44 [(log_from_module) = "framework"]; - WallClockTimeShifted wall_clock_time_shifted = 45; - AnomalyDetected anomaly_detected = 46; - AppBreadcrumbReported app_breadcrumb_reported = 47 [(allow_from_any_uid) = true]; - AppStartOccurred app_start_occurred = 48; - AppStartCanceled app_start_canceled = 49; - AppStartFullyDrawn app_start_fully_drawn = 50; - LmkKillOccurred lmk_kill_occurred = 51 [(log_from_module) = "lmkd"]; - PictureInPictureStateChanged picture_in_picture_state_changed = 52; - WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53; - LmkStateChanged lmk_state_changed = 54 [(log_from_module) = "lmkd"]; - AppStartMemoryStateCaptured app_start_memory_state_captured = 55; - ShutdownSequenceReported shutdown_sequence_reported = 56; + WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"]; + KernelWakeupReported kernel_wakeup_reported = 36 [(module) = "framework"]; + WifiLockStateChanged wifi_lock_state_changed = 37 [(module) = "wifi"]; + WifiSignalStrengthChanged wifi_signal_strength_changed = 38 [(module) = "wifi"]; + WifiScanStateChanged wifi_scan_state_changed = 39 [(module) = "wifi"]; + PhoneSignalStrengthChanged phone_signal_strength_changed = + 40 [(module) = "framework", (truncate_timestamp) = true]; + SettingChanged setting_changed = 41 [(module) = "framework"]; + ActivityForegroundStateChanged activity_foreground_state_changed = + 42 [(module) = "framework", (module) = "statsdtest"]; + IsolatedUidChanged isolated_uid_changed = + 43 [(module) = "framework", (module) = "statsd", (module) = "statsdtest"]; + PacketWakeupOccurred packet_wakeup_occurred = 44 [(module) = "framework"]; + WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"]; + AnomalyDetected anomaly_detected = 46 [(module) = "statsd"]; + AppBreadcrumbReported app_breadcrumb_reported = 47 [(module) = "statsd"]; + AppStartOccurred app_start_occurred = 48 [(module) = "framework", (module) = "statsdtest"]; + AppStartCanceled app_start_canceled = 49 [(module) = "framework"]; + AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"]; + LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"]; + PictureInPictureStateChanged picture_in_picture_state_changed = 52 [(module) = "framework"]; + WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"]; + LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"]; + AppStartMemoryStateCaptured app_start_memory_state_captured = 55 [(module) = "framework"]; + ShutdownSequenceReported shutdown_sequence_reported = 56 [(module) = "framework"]; BootSequenceReported boot_sequence_reported = 57; - DaveyOccurred davey_occurred = 58 [(allow_from_any_uid) = true]; - OverlayStateChanged overlay_state_changed = 59; - ForegroundServiceStateChanged foreground_service_state_changed = 60; - CallStateChanged call_state_changed = 61; - KeyguardStateChanged keyguard_state_changed = 62; - KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63; - KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64; - AppDied app_died = 65; - ResourceConfigurationChanged resource_configuration_changed = 66; - BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67; + DaveyOccurred davey_occurred = 58 [(module) = "statsd"]; + OverlayStateChanged overlay_state_changed = + 59 [(module) = "framework", (module) = "statsdtest"]; + ForegroundServiceStateChanged foreground_service_state_changed + = 60 [(module) = "framework"]; + CallStateChanged call_state_changed = + 61 [(module) = "telecom", (truncate_timestamp) = true]; + KeyguardStateChanged keyguard_state_changed = 62 [(module) = "sysui"]; + KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63 [(module) = "sysui"]; + KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64 [(module) = "sysui"]; + AppDied app_died = 65 [(module) = "framework"]; + ResourceConfigurationChanged resource_configuration_changed = 66 [(module) = "framework"]; + BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67 [(module) = "framework"]; BluetoothConnectionStateChanged bluetooth_connection_state_changed = - 68 [(log_from_module) = "bluetooth"]; - GpsSignalQualityChanged gps_signal_quality_changed = 69; - UsbConnectorStateChanged usb_connector_state_changed = 70; + 68 [(module) = "bluetooth"]; + GpsSignalQualityChanged gps_signal_quality_changed = 69 [(module) = "framework"]; + UsbConnectorStateChanged usb_connector_state_changed = 70 [(module) = "framework"]; SpeakerImpedanceReported speaker_impedance_reported = 71; HardwareFailed hardware_failed = 72; PhysicalDropDetected physical_drop_detected = 73; ChargeCyclesReported charge_cycles_reported = 74; - MobileConnectionStateChanged mobile_connection_state_changed = - 75 [(log_from_module) = "telephony"]; - MobileRadioTechnologyChanged mobile_radio_technology_changed = - 76 [(log_from_module) = "telephony"]; - UsbDeviceAttached usb_device_attached = 77; - AppCrashOccurred app_crash_occurred = 78; - ANROccurred anr_occurred = 79; - WTFOccurred wtf_occurred = 80; - LowMemReported low_mem_reported = 81; + MobileConnectionStateChanged mobile_connection_state_changed = 75 [(module) = "telephony"]; + MobileRadioTechnologyChanged mobile_radio_technology_changed = 76 [(module) = "telephony"]; + UsbDeviceAttached usb_device_attached = 77 [(module) = "framework"]; + AppCrashOccurred app_crash_occurred = 78 [(module) = "framework", (module) = "statsdtest"]; + ANROccurred anr_occurred = 79 [(module) = "framework"]; + WTFOccurred wtf_occurred = 80 [(module) = "framework"]; + LowMemReported low_mem_reported = 81 [(module) = "framework"]; GenericAtom generic_atom = 82; - KeyValuePairsAtom key_value_pairs_atom = 83 [(allow_from_any_uid) = true]; - VibratorStateChanged vibrator_state_changed = 84; - DeferredJobStatsReported deferred_job_stats_reported = 85; + KeyValuePairsAtom key_value_pairs_atom = 83 [(module) = "framework", (module) = "statsd"]; + VibratorStateChanged vibrator_state_changed = 84 [(module) = "framework"]; + DeferredJobStatsReported deferred_job_stats_reported = 85 [(module) = "framework"]; ThermalThrottlingStateChanged thermal_throttling = 86 [deprecated=true]; - BiometricAcquired biometric_acquired = 87; - BiometricAuthenticated biometric_authenticated = 88; - BiometricErrorOccurred biometric_error_occurred = 89; - // Atom number 90 is available for use. + BiometricAcquired biometric_acquired = 87 [(module) = "framework"]; + BiometricAuthenticated biometric_authenticated = 88 [(module) = "framework"]; + BiometricErrorOccurred biometric_error_occurred = 89 [(module) = "framework"]; + UiEventReported ui_event_reported = 90 [(module) = "framework", (module) = "sysui"]; BatteryHealthSnapshot battery_health_snapshot = 91; SlowIo slow_io = 92; BatteryCausedShutdown battery_caused_shutdown = 93; - PhoneServiceStateChanged phone_service_state_changed = 94; - PhoneStateChanged phone_state_changed = 95; + PhoneServiceStateChanged phone_service_state_changed = 94 [(module) = "framework"]; + PhoneStateChanged phone_state_changed = 95 [(module) = "framework"]; UserRestrictionChanged user_restriction_changed = 96; - SettingsUIChanged settings_ui_changed = 97; - ConnectivityStateChanged connectivity_state_changed = 98; + SettingsUIChanged settings_ui_changed = 97 [(module) = "settings"]; + ConnectivityStateChanged connectivity_state_changed = 98 [(module) = "framework"]; // TODO: service state change is very noisy shortly after boot, as well // as at other transitions - coming out of doze, device plugged in, etc. // Consider removing this if it becomes a problem - ServiceStateChanged service_state_changed = 99; - ServiceLaunchReported service_launch_reported = 100; - FlagFlipUpdateOccurred flag_flip_update_occurred = 101; - BinaryPushStateChanged binary_push_state_changed = 102; - DevicePolicyEvent device_policy_event = 103; - DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = - 104 [(log_from_module) = "docsui"]; - DocsUIFileOperationCopyMoveModeReported - docs_ui_file_op_copy_move_mode_reported = - 105 [(log_from_module) = "docsui"]; - DocsUIFileOperationFailureReported docs_ui_file_op_failure = - 106 [(log_from_module) = "docsui"]; - DocsUIFileOperationReported docs_ui_provider_file_op = - 107 [(log_from_module) = "docsui"]; - DocsUIInvalidScopedAccessRequestReported - docs_ui_invalid_scoped_access_request = - 108 [(log_from_module) = "docsui"]; - DocsUILaunchReported docs_ui_launch_reported = - 109 [(log_from_module) = "docsui"]; - DocsUIRootVisitedReported docs_ui_root_visited = - 110 [(log_from_module) = "docsui"]; - DocsUIStartupMsReported docs_ui_startup_ms = - 111 [(log_from_module) = "docsui"]; - DocsUIUserActionReported docs_ui_user_action_reported = - 112 [(log_from_module) = "docsui"]; - WifiEnabledStateChanged wifi_enabled_state_changed = 113; - WifiRunningStateChanged wifi_running_state_changed = 114; - AppCompacted app_compacted = 115; - NetworkDnsEventReported network_dns_event_reported = 116 [(log_from_module) = "resolv"]; + ServiceStateChanged service_state_changed = 99 [(module) = "framework"]; + ServiceLaunchReported service_launch_reported = 100 [(module) = "framework"]; + FlagFlipUpdateOccurred flag_flip_update_occurred = 101 [(module) = "framework"]; + BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "statsd"]; + DevicePolicyEvent device_policy_event = 103 [(module) = "framework"]; + DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104 [(module) = "docsui"]; + DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported = + 105 [(module) = "docsui"]; + DocsUIFileOperationFailureReported docs_ui_file_op_failure = 106 [(module) = "docsui"]; + DocsUIFileOperationReported docs_ui_provider_file_op = 107 [(module) = "docsui"]; + DocsUIInvalidScopedAccessRequestReported docs_ui_invalid_scoped_access_request = + 108 [(module) = "docsui"]; + DocsUILaunchReported docs_ui_launch_reported = 109 [(module) = "docsui"]; + DocsUIRootVisitedReported docs_ui_root_visited = 110 [(module) = "docsui"]; + DocsUIStartupMsReported docs_ui_startup_ms = 111 [(module) = "docsui"]; + DocsUIUserActionReported docs_ui_user_action_reported = 112 [(module) = "docsui"]; + WifiEnabledStateChanged wifi_enabled_state_changed = 113 [(module) = "framework"]; + WifiRunningStateChanged wifi_running_state_changed = 114 + [(module) = "framework", deprecated = true]; + AppCompacted app_compacted = 115 [(module) = "framework"]; + NetworkDnsEventReported network_dns_event_reported = 116 [(module) = "resolv"]; DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = - 117 [(log_from_module) = "docsui"]; - DocsUIPickResultReported docs_ui_pick_result_reported = - 118 [(log_from_module) = "docsui"]; - DocsUISearchModeReported docs_ui_search_mode_reported = - 119 [(log_from_module) = "docsui"]; - DocsUISearchTypeReported docs_ui_search_type_reported = - 120 [(log_from_module) = "docsui"]; - DataStallEvent data_stall_event = 121 [(log_from_module) = "network_stack"]; - RescuePartyResetReported rescue_party_reset_reported = 122; - SignedConfigReported signed_config_reported = 123; - GnssNiEventReported gnss_ni_event_reported = 124; + 117 [(module) = "docsui"]; + DocsUIPickResultReported docs_ui_pick_result_reported = 118 [(module) = "docsui"]; + DocsUISearchModeReported docs_ui_search_mode_reported = 119 [(module) = "docsui"]; + DocsUISearchTypeReported docs_ui_search_type_reported = 120 [(module) = "docsui"]; + DataStallEvent data_stall_event = 121 [(module) = "network_stack"]; + RescuePartyResetReported rescue_party_reset_reported = 122 [(module) = "framework"]; + SignedConfigReported signed_config_reported = 123 [(module) = "framework"]; + GnssNiEventReported gnss_ni_event_reported = 124 [(module) = "framework"]; BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = - 125 [(log_from_module) = "bluetooth"]; + 125 [(module) = "bluetooth"]; BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = - 126 [(log_from_module) = "bluetooth"]; + 126 [(module) = "bluetooth"]; BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = - 127 [(log_from_module) = "bluetooth"]; - AppDowngraded app_downgraded = 128; + 127 [(module) = "bluetooth"]; + AppDowngraded app_downgraded = 128 [(module) = "framework"]; AppOptimizedAfterDowngraded app_optimized_after_downgraded = 129; - LowStorageStateChanged low_storage_state_changed = 130; - GnssNfwNotificationReported gnss_nfw_notification_reported = 131; - GnssConfigurationReported gnss_configuration_reported = 132; + LowStorageStateChanged low_storage_state_changed = 130 [(module) = "framework"]; + GnssNfwNotificationReported gnss_nfw_notification_reported = 131 [(module) = "framework"]; + GnssConfigurationReported gnss_configuration_reported = 132 [(module) = "framework"]; UsbPortOverheatEvent usb_port_overheat_event_reported = 133; - NfcErrorOccurred nfc_error_occurred = 134; - NfcStateChanged nfc_state_changed = 135; - NfcBeamOccurred nfc_beam_occurred = 136; - NfcCardemulationOccurred nfc_cardemulation_occurred = 137; - NfcTagOccurred nfc_tag_occurred = 138; - NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139; - SeStateChanged se_state_changed = 140; - SeOmapiReported se_omapi_reported = 141; - BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142; - AttentionManagerServiceResultReported attention_manager_service_result_reported = 143; - AdbConnectionChanged adb_connection_changed = 144; + NfcErrorOccurred nfc_error_occurred = 134 [(module) = "nfc"]; + NfcStateChanged nfc_state_changed = 135 [(module) = "nfc"]; + NfcBeamOccurred nfc_beam_occurred = 136 [(module) = "nfc"]; + NfcCardemulationOccurred nfc_cardemulation_occurred = 137 [(module) = "nfc"]; + NfcTagOccurred nfc_tag_occurred = 138 [(module) = "nfc"]; + NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139 [(module) = "nfc"]; + SeStateChanged se_state_changed = 140 [(module) = "secure_element"]; + SeOmapiReported se_omapi_reported = 141 [(module) = "secure_element"]; + BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = + 142 [(module) = "framework"]; + AttentionManagerServiceResultReported attention_manager_service_result_reported = + 143 [(module) = "framework"]; + AdbConnectionChanged adb_connection_changed = 144 [(module) = "framework"]; SpeechDspStatReported speech_dsp_stat_reported = 145; - UsbContaminantReported usb_contaminant_reported = 146; - WatchdogRollbackOccurred watchdog_rollback_occurred = 147; - BiometricSystemHealthIssueDetected biometric_system_health_issue_detected = 148; - BubbleUIChanged bubble_ui_changed = 149; - ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150; + UsbContaminantReported usb_contaminant_reported = 146 [(module) = "framework"]; + WatchdogRollbackOccurred watchdog_rollback_occurred = + 147 [(module) = "framework", (module) = "statsd"]; + BiometricSystemHealthIssueDetected biometric_system_health_issue_detected = + 148 [(module) = "framework"]; + BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"]; + ScheduledJobConstraintChanged scheduled_job_constraint_changed = + 150 [(module) = "framework"]; BluetoothActiveDeviceChanged bluetooth_active_device_changed = - 151 [(log_from_module) = "bluetooth"]; + 151 [(module) = "bluetooth"]; BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = - 152 [(log_from_module) = "bluetooth"]; + 152 [(module) = "bluetooth"]; BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed = - 153 [(log_from_module) = "bluetooth"]; + 153 [(module) = "bluetooth"]; BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed = - 154 [(log_from_module) = "bluetooth"]; + 154 [(module) = "bluetooth"]; BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported = - 155 [(log_from_module) = "bluetooth"]; + 155 [(module) = "bluetooth"]; BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported = - 156 [(log_from_module) = "bluetooth"]; + 156 [(module) = "bluetooth"]; BluetoothDeviceRssiReported bluetooth_device_rssi_reported = - 157 [(log_from_module) = "bluetooth"]; + 157 [(module) = "bluetooth"]; BluetoothDeviceFailedContactCounterReported - bluetooth_device_failed_contact_counter_reported = 158 [(log_from_module) = "bluetooth"]; + bluetooth_device_failed_contact_counter_reported = 158 [(module) = "bluetooth"]; BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported = - 159 [(log_from_module) = "bluetooth"]; + 159 [(module) = "bluetooth"]; BluetoothHciTimeoutReported bluetooth_hci_timeout_reported = - 160 [(log_from_module) = "bluetooth"]; + 160 [(module) = "bluetooth"]; BluetoothQualityReportReported bluetooth_quality_report_reported = - 161 [(log_from_module) = "bluetooth"]; + 161 [(module) = "bluetooth"]; BluetoothDeviceInfoReported bluetooth_device_info_reported = - 162 [(log_from_module) = "bluetooth"]; + 162 [(module) = "bluetooth"]; BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported = - 163 [(log_from_module) = "bluetooth"]; + 163 [(module) = "bluetooth"]; BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported = - 164 [(log_from_module) = "bluetooth"]; + 164 [(module) = "bluetooth"]; BluetoothBondStateChanged bluetooth_bond_state_changed = - 165 [(log_from_module) = "bluetooth"]; + 165 [(module) = "bluetooth"]; BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = - 166 [(log_from_module) = "bluetooth"]; + 166 [(module) = "bluetooth"]; BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = - 167 [(log_from_module) = "bluetooth"]; - ScreenTimeoutExtensionReported screen_timeout_extension_reported = 168; - ProcessStartTime process_start_time = 169; + 167 [(module) = "bluetooth"]; + ScreenTimeoutExtensionReported screen_timeout_extension_reported = + 168 [(module) = "framework"]; + ProcessStartTime process_start_time = 169 [(module) = "framework"]; PermissionGrantRequestResultReported permission_grant_request_result_reported = - 170 [(log_from_module) = "permissioncontroller"]; + 170 [(module) = "permissioncontroller"]; BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171; - DeviceIdentifierAccessDenied device_identifier_access_denied = 172; - BubbleDeveloperErrorReported bubble_developer_error_reported = 173; - AssistGestureStageReported assist_gesture_stage_reported = 174; - AssistGestureFeedbackReported assist_gesture_feedback_reported = 175; - AssistGestureProgressReported assist_gesture_progress_reported = 176; - TouchGestureClassified touch_gesture_classified = 177; - HiddenApiUsed hidden_api_used = 178 [(allow_from_any_uid) = true]; - StyleUIChanged style_ui_changed = 179 [(log_from_module) = "style"]; + DeviceIdentifierAccessDenied device_identifier_access_denied = + 172 [(module) = "telephony_common"]; + BubbleDeveloperErrorReported bubble_developer_error_reported = 173 [(module) = "framework"]; + AssistGestureStageReported assist_gesture_stage_reported = 174 [(module) = "sysui"]; + AssistGestureFeedbackReported assist_gesture_feedback_reported = 175 [(module) = "sysui"]; + AssistGestureProgressReported assist_gesture_progress_reported = 176 [(module) = "sysui"]; + TouchGestureClassified touch_gesture_classified = 177 [(module) = "framework"]; + HiddenApiUsed hidden_api_used = 178 [(module) = "framework"]; + StyleUIChanged style_ui_changed = 179 [(module) = "sysui"]; PrivacyIndicatorsInteracted privacy_indicators_interacted = - 180 [(log_from_module) = "permissioncontroller"]; - AppInstallOnExternalStorageReported app_install_on_external_storage_reported = 181; - NetworkStackReported network_stack_reported = 182 [(log_from_module) = "network_stack"]; - AppMovedStorageReported app_moved_storage_reported = 183; - BiometricEnrolled biometric_enrolled = 184; - SystemServerWatchdogOccurred system_server_watchdog_occurred = 185; - TombStoneOccurred tomb_stone_occurred = 186; + 180 [(module) = "permissioncontroller"]; + AppInstallOnExternalStorageReported app_install_on_external_storage_reported = + 181 [(module) = "framework"]; + NetworkStackReported network_stack_reported = 182 [(module) = "network_stack"]; + AppMovedStorageReported app_moved_storage_reported = 183 [(module) = "framework"]; + BiometricEnrolled biometric_enrolled = 184 [(module) = "framework"]; + SystemServerWatchdogOccurred system_server_watchdog_occurred = 185 [(module) = "framework"]; + TombStoneOccurred tomb_stone_occurred = 186 [(module) = "framework"]; BluetoothClassOfDeviceReported bluetooth_class_of_device_reported = - 187 [(log_from_module) = "bluetooth"]; + 187 [(module) = "bluetooth"]; IntelligenceEventReported intelligence_event_reported = - 188 [(log_from_module) = "intelligence"]; - ThermalThrottlingSeverityStateChanged thermal_throttling_severity_state_changed = 189; + 188 [(module) = "intelligence"]; + ThermalThrottlingSeverityStateChanged thermal_throttling_severity_state_changed = + 189 [(module) = "framework"]; RoleRequestResultReported role_request_result_reported = - 190 [(log_from_module) = "permissioncontroller"]; + 190 [(module) = "permissioncontroller"]; MediametricsAudiopolicyReported mediametrics_audiopolicy_reported = 191; MediametricsAudiorecordReported mediametrics_audiorecord_reported = 192; MediametricsAudiothreadReported mediametrics_audiothread_reported = 193; @@ -321,131 +344,258 @@ message Atom { MediametricsMediadrmReported mediametrics_mediadrm_reported = 198; MediametricsNuPlayerReported mediametrics_nuplayer_reported = 199; MediametricsRecorderReported mediametrics_recorder_reported = 200; - CarPowerStateChanged car_power_state_changed = 203; - GarageModeInfo garage_mode_info = 204; - TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"]; - ContentCaptureCallerMismatchReported content_capture_caller_mismatch_reported = 206; - ContentCaptureServiceEvents content_capture_service_events = 207; - ContentCaptureSessionEvents content_capture_session_events = 208; - ContentCaptureFlushed content_capture_flushed = 209; - LocationManagerApiUsageReported location_manager_api_usage_reported = 210; + MediametricsDrmManagerReported mediametrics_drmmanager_reported = 201; + CarPowerStateChanged car_power_state_changed = 203 [(module) = "car"]; + GarageModeInfo garage_mode_info = 204 [(module) = "car"]; + TestAtomReported test_atom_reported = 205 [(module) = "cts"]; + ContentCaptureCallerMismatchReported content_capture_caller_mismatch_reported = + 206 [(module) = "framework"]; + ContentCaptureServiceEvents content_capture_service_events = 207 [(module) = "framework"]; + ContentCaptureSessionEvents content_capture_session_events = 208 [(module) = "framework"]; + ContentCaptureFlushed content_capture_flushed = 209 [(module) = "framework"]; + LocationManagerApiUsageReported location_manager_api_usage_reported = + 210 [(module) = "framework"]; ReviewPermissionsFragmentResultReported review_permissions_fragment_result_reported = - 211 [(log_from_module) = "permissioncontroller"]; + 211 [(module) = "permissioncontroller"]; RuntimePermissionsUpgradeResult runtime_permissions_upgrade_result = - 212 [(log_from_module) = "permissioncontroller"]; + 212 [(module) = "permissioncontroller"]; GrantPermissionsActivityButtonActions grant_permissions_activity_button_actions = - 213 [(log_from_module) = "permissioncontroller"]; + 213 [(module) = "permissioncontroller"]; LocationAccessCheckNotificationAction location_access_check_notification_action = - 214 [(log_from_module) = "permissioncontroller"]; + 214 [(module) = "permissioncontroller"]; AppPermissionFragmentActionReported app_permission_fragment_action_reported = - 215 [(log_from_module) = "permissioncontroller"]; + 215 [(module) = "permissioncontroller"]; AppPermissionFragmentViewed app_permission_fragment_viewed = - 216 [(log_from_module) = "permissioncontroller"]; + 216 [(module) = "permissioncontroller"]; AppPermissionsFragmentViewed app_permissions_fragment_viewed = - 217 [(log_from_module) = "permissioncontroller"]; + 217 [(module) = "permissioncontroller"]; PermissionAppsFragmentViewed permission_apps_fragment_viewed = - 218 [(log_from_module) = "permissioncontroller"]; - ExclusionRectStateChanged exclusion_rect_state_changed = 223; - BackGesture back_gesture_reported_reported = 224; - + 218 [(module) = "permissioncontroller"]; + TextSelectionEvent text_selection_event = 219 [(module) = "textclassifier"]; + TextLinkifyEvent text_linkify_event = 220 [(module) = "textclassifier"]; + ConversationActionsEvent conversation_actions_event = 221 [(module) = "textclassifier"]; + LanguageDetectionEvent language_detection_event = 222 [(module) = "textclassifier"]; + ExclusionRectStateChanged exclusion_rect_state_changed = 223 [(module) = "framework"]; + BackGesture back_gesture_reported_reported = 224 [(module) = "sysui"]; UpdateEngineUpdateAttemptReported update_engine_update_attempt_reported = 225; UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226; + CameraActionEvent camera_action_event = 227 [(module) = "framework"]; AppCompatibilityChangeReported app_compatibility_change_reported = - 228 [(allow_from_any_uid) = true]; - PerfettoUploaded perfetto_uploaded = - 229 [(log_from_module) = "perfetto"]; - VmsClientConnectionStateChanged vms_client_connection_state_changed = 230; - BootTimeEventDuration boot_time_event_duration_reported = 239; - BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240; + 228 [(module) = "framework"]; + PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"]; + VmsClientConnectionStateChanged vms_client_connection_state_changed = + 230 [(module) = "car"]; + MediaProviderScanOccurred media_provider_scan_occurred = 233 [(module) = "mediaprovider"]; + MediaContentDeleted media_content_deleted = 234 [(module) = "mediaprovider"]; + MediaProviderPermissionRequested media_provider_permission_requested = + 235 [(module) = "mediaprovider"]; + MediaProviderSchemaChanged media_provider_schema_changed = 236 [(module) = "mediaprovider"]; + MediaProviderIdleMaintenanceFinished media_provider_idle_maintenance_finished = + 237 [(module) = "mediaprovider"]; + RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238 [(module) = "framework"]; + BootTimeEventDuration boot_time_event_duration_reported = 239 [(module) = "framework"]; + BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = + 240 [(module) = "framework"]; BootTimeEventUtcTime boot_time_event_utc_time_reported = 241; - BootTimeEventErrorCode boot_time_event_error_code_reported = 242; - UserspaceRebootReported userspace_reboot_reported = 243 [(log_from_module) = "framework"]; + BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"]; + UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"]; + NotificationReported notification_reported = 244 [(module) = "framework"]; + NotificationPanelReported notification_panel_reported = 245 [(module) = "sysui"]; + NotificationChannelModified notification_channel_modified = 246 [(module) = "framework"]; + IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"]; + IntegrityRulesPushed integrity_rules_pushed = 248 [(module) = "framework"]; + CellBroadcastMessageReported cb_message_reported = + 249 [(module) = "cellbroadcast"]; + CellBroadcastMessageError cb_message_error = + 250 [(module) = "cellbroadcast"]; + WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"]; + WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"]; + WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"]; + AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"]; SnapshotMergeReported snapshot_merge_reported = 255; - NetworkIpProvisioningReported network_ip_provisioning_reported = 290 [(log_from_module) = "network_stack"]; - NetworkDhcpRenewReported network_dhcp_renew_reported = 291 [(log_from_module) = "network_stack"]; - NetworkValidationReported network_validation_reported = 292 [(log_from_module) = "network_stack"]; - NetworkStackQuirkReported network_stack_quirk_reported = 293 [(log_from_module) = "network_stack"]; + ForegroundServiceAppOpSessionEnded foreground_service_app_op_session_ended = + 256 [(module) = "framework"]; + DisplayJankReported display_jank_reported = 257; + AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"]; + SharesheetStarted sharesheet_started = 259 [(module) = "framework"]; + RankingSelected ranking_selected = 260 [(module) = "framework", (module) = "sysui"]; + TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"]; + LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"]; + PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"]; + UserLifecycleJourneyReported user_lifecycle_journey_reported = 264 [(module) = "framework"]; + UserLifecycleEventOccurred user_lifecycle_event_occurred = 265 [(module) = "framework"]; + AccessibilityShortcutReported accessibility_shortcut_reported = + 266 [(module) = "framework"]; + AccessibilityServiceReported accessibility_service_reported = 267 [(module) = "settings"]; + DocsUIDragAndDropReported docs_ui_drag_and_drop_reported = 268 [(module) = "docsui"]; + AppUsageEventOccurred app_usage_event_occurred = 269 [(module) = "framework"]; + AutoRevokeNotificationClicked auto_revoke_notification_clicked = + 270 [(module) = "permissioncontroller"]; + AutoRevokeFragmentAppViewed auto_revoke_fragment_app_viewed = + 271 [(module) = "permissioncontroller"]; + AutoRevokedAppInteraction auto_revoked_app_interaction = + 272 [(module) = "permissioncontroller", (module) = "settings"]; + AppPermissionGroupsFragmentAutoRevokeAction + app_permission_groups_fragment_auto_revoke_action = + 273 [(module) = "permissioncontroller"]; + EvsUsageStatsReported evs_usage_stats_reported = 274 [(module) = "evs"]; + AudioPowerUsageDataReported audio_power_usage_data_reported = 275; + TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"]; + MediaOutputOpSwitchReported mediaoutput_op_switch_reported = + 277 [(module) = "settings"]; + CellBroadcastMessageFiltered cb_message_filtered = + 278 [(module) = "cellbroadcast"]; + TvTunerDvrStatus tv_tuner_dvr_status = 279 [(module) = "framework"]; + TvCasSessionOpenStatus tv_cas_session_open_status = + 280 [(module) = "framework"]; + AssistantInvocationReported assistant_invocation_reported = 281 [(module) = "framework"]; + DisplayWakeReported display_wake_reported = 282 [(module) = "framework"]; + CarUserHalModifyUserRequestReported car_user_hal_modify_user_request_reported = + 283 [(module) = "car"]; + CarUserHalModifyUserResponseReported car_user_hal_modify_user_response_reported = + 284 [(module) = "car"]; + CarUserHalPostSwitchResponseReported car_user_hal_post_switch_response_reported = + 285 [(module) = "car"]; + CarUserHalInitialUserInfoRequestReported car_user_hal_initial_user_info_request_reported = + 286 [(module) = "car"]; + CarUserHalInitialUserInfoResponseReported car_user_hal_initial_user_info_response_reported = + 287 [(module) = "car"]; + CarUserHalUserAssociationRequestReported car_user_hal_user_association_request_reported = + 288 [(module) = "car"]; + CarUserHalSetUserAssociationResponseReported car_user_hal_set_user_association_response_reported = + 289 [(module) = "car"]; + NetworkIpProvisioningReported network_ip_provisioning_reported = + 290 [(module) = "network_stack"]; + NetworkDhcpRenewReported network_dhcp_renew_reported = 291 [(module) = "network_stack"]; + NetworkValidationReported network_validation_reported = 292 [(module) = "network_stack"]; + NetworkStackQuirkReported network_stack_quirk_reported = 293 [(module) = "network_stack"]; + MediametricsAudioRecordDeviceUsageReported mediametrics_audiorecorddeviceusage_reported = + 294; + MediametricsAudioThreadDeviceUsageReported mediametrics_audiothreaddeviceusage_reported = + 295; + MediametricsAudioTrackDeviceUsageReported mediametrics_audiotrackdeviceusage_reported = + 296; + MediametricsAudioDeviceConnectionReported mediametrics_audiodeviceconnection_reported = + 297; + BlobCommitted blob_committed = 298 [(module) = "framework"]; + BlobLeased blob_leased = 299 [(module) = "framework"]; + BlobOpened blob_opened = 300 [(module) = "framework"]; + ContactsProviderStatusReported contacts_provider_status_reported = 301; KeystoreKeyEventReported keystore_key_event_reported = 302; - NetworkTetheringReported network_tethering_reported = 303 [(log_from_module) = "network_tethering"]; + NetworkTetheringReported network_tethering_reported = + 303 [(module) = "network_tethering"]; + ImeTouchReported ime_touch_reported = 304 [(module) = "sysui"]; + + // StatsdStats tracks platform atoms with ids upto 500. + // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. } // Pulled events will start at field 10000. - // Next: 10080 + // Next: 10084 oneof pulled { - WifiBytesTransfer wifi_bytes_transfer = 10000; - WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; - MobileBytesTransfer mobile_bytes_transfer = 10002; - MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = 10003; - BluetoothBytesTransfer bluetooth_bytes_transfer = 10006; - KernelWakelock kernel_wakelock = 10004; - SubsystemSleepState subsystem_sleep_state = 10005; - CpuTimePerFreq cpu_time_per_freq = 10008; - CpuTimePerUid cpu_time_per_uid = 10009; - CpuTimePerUidFreq cpu_time_per_uid_freq = 10010; - WifiActivityInfo wifi_activity_info = 10011; - ModemActivityInfo modem_activity_info = 10012; - BluetoothActivityInfo bluetooth_activity_info = 10007; - ProcessMemoryState process_memory_state = 10013; - SystemElapsedRealtime system_elapsed_realtime = 10014; - SystemUptime system_uptime = 10015; - CpuActiveTime cpu_active_time = 10016; - CpuClusterTime cpu_cluster_time = 10017; - DiskSpace disk_space = 10018 [deprecated=true]; - RemainingBatteryCapacity remaining_battery_capacity = 10019; - FullBatteryCapacity full_battery_capacity = 10020; - Temperature temperature = 10021; - BinderCalls binder_calls = 10022; - BinderCallsExceptions binder_calls_exceptions = 10023; - LooperStats looper_stats = 10024; - DiskStats disk_stats = 10025; - DirectoryUsage directory_usage = 10026; - AppSize app_size = 10027; - CategorySize category_size = 10028; - ProcStats proc_stats = 10029; - BatteryVoltage battery_voltage = 10030; - NumFingerprintsEnrolled num_fingerprints_enrolled = 10031; - DiskIo disk_io = 10032; - PowerProfile power_profile = 10033; - ProcStatsPkgProc proc_stats_pkg_proc = 10034; - ProcessCpuTime process_cpu_time = 10035; - NativeProcessMemoryState native_process_memory_state = 10036; - CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037; + WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"]; + WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"]; + MobileBytesTransfer mobile_bytes_transfer = + 10002 [(module) = "framework", (truncate_timestamp) = true]; + MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = + 10003 [(module) = "framework", (truncate_timestamp) = true]; + BluetoothBytesTransfer bluetooth_bytes_transfer = 10006 [(module) = "framework"]; + KernelWakelock kernel_wakelock = 10004 [(module) = "framework"]; + SubsystemSleepState subsystem_sleep_state = 10005 [(module) = "statsdtest"]; + CpuTimePerFreq cpu_time_per_freq = 10008 [(module) = "framework"]; + CpuTimePerUid cpu_time_per_uid = 10009 [(module) = "framework", (module) = "statsdtest"]; + CpuTimePerUidFreq cpu_time_per_uid_freq = + 10010 [(module) = "framework", (module) = "statsd"]; + WifiActivityInfo wifi_activity_info = 10011 [(module) = "framework"]; + ModemActivityInfo modem_activity_info = 10012 [(module) = "framework"]; + BluetoothActivityInfo bluetooth_activity_info = 10007 [(module) = "framework"]; + ProcessMemoryState process_memory_state = 10013 [(module) = "framework"]; + SystemElapsedRealtime system_elapsed_realtime = 10014 [(module) = "framework"]; + SystemUptime system_uptime = 10015 [(module) = "framework"]; + CpuActiveTime cpu_active_time = 10016 [(module) = "framework", (module) = "statsdtest"]; + CpuClusterTime cpu_cluster_time = 10017 [(module) = "framework"]; + DiskSpace disk_space = 10018 [deprecated=true, (module) = "statsdtest"]; + RemainingBatteryCapacity remaining_battery_capacity = 10019 [(module) = "framework"]; + FullBatteryCapacity full_battery_capacity = 10020 [(module) = "framework"]; + Temperature temperature = 10021 [(module) = "framework", (module) = "statsdtest"]; + BinderCalls binder_calls = 10022 [(module) = "framework", (module) = "statsd"]; + BinderCallsExceptions binder_calls_exceptions = 10023 [(module) = "framework"]; + LooperStats looper_stats = 10024 [(module) = "framework", (module) = "statsd"]; + DiskStats disk_stats = 10025 [(module) = "framework"]; + DirectoryUsage directory_usage = 10026 [(module) = "framework"]; + AppSize app_size = 10027 [(module) = "framework"]; + CategorySize category_size = 10028 [(module) = "framework"]; + ProcStats proc_stats = 10029 [(module) = "framework"]; + BatteryVoltage battery_voltage = 10030 [(module) = "framework"]; + NumFingerprintsEnrolled num_fingerprints_enrolled = 10031 [(module) = "framework"]; + DiskIo disk_io = 10032 [(module) = "framework"]; + PowerProfile power_profile = 10033 [(module) = "framework"]; + ProcStatsPkgProc proc_stats_pkg_proc = 10034 [(module) = "framework"]; + ProcessCpuTime process_cpu_time = 10035 [(module) = "framework"]; + CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037 [(module) = "framework"]; OnDevicePowerMeasurement on_device_power_measurement = 10038; - DeviceCalculatedPowerUse device_calculated_power_use = 10039; - DeviceCalculatedPowerBlameUid device_calculated_power_blame_uid = 10040; - DeviceCalculatedPowerBlameOther device_calculated_power_blame_other = 10041; - ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042; - BatteryLevel battery_level = 10043; - BuildInformation build_information = 10044; - BatteryCycleCount battery_cycle_count = 10045; - DebugElapsedClock debug_elapsed_clock = 10046; - DebugFailingElapsedClock debug_failing_elapsed_clock = 10047; - NumFacesEnrolled num_faces_enrolled = 10048; - RoleHolder role_holder = 10049; - DangerousPermissionState dangerous_permission_state = 10050; - TrainInfo train_info = 10051; - TimeZoneDataInfo time_zone_data_info = 10052; - ExternalStorageInfo external_storage_info = 10053; + DeviceCalculatedPowerUse device_calculated_power_use = 10039 [(module) = "framework"]; + DeviceCalculatedPowerBlameUid device_calculated_power_blame_uid = + 10040 [(module) = "framework"]; + DeviceCalculatedPowerBlameOther device_calculated_power_blame_other = + 10041 [(module) = "framework"]; + ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042 [(module) = "framework"]; + BatteryLevel battery_level = 10043 [(module) = "framework"]; + BuildInformation build_information = 10044 [(module) = "framework"]; + BatteryCycleCount battery_cycle_count = 10045 [(module) = "framework"]; + DebugElapsedClock debug_elapsed_clock = 10046 [(module) = "framework"]; + DebugFailingElapsedClock debug_failing_elapsed_clock = 10047 [(module) = "framework"]; + NumFacesEnrolled num_faces_enrolled = 10048 [(module) = "framework"]; + RoleHolder role_holder = 10049 [(module) = "framework"]; + DangerousPermissionState dangerous_permission_state = 10050 [(module) = "framework"]; + TrainInfo train_info = 10051 [(module) = "statsd"]; + TimeZoneDataInfo time_zone_data_info = 10052 [(module) = "framework"]; + ExternalStorageInfo external_storage_info = 10053 [(module) = "framework"]; GpuStatsGlobalInfo gpu_stats_global_info = 10054; GpuStatsAppInfo gpu_stats_app_info = 10055; - SystemIonHeapSize system_ion_heap_size = 10056; - AppsOnExternalStorageInfo apps_on_external_storage_info = 10057; - FaceSettings face_settings = 10058; - CoolingDevice cooling_device = 10059; - AppOps app_ops = 10060; - ProcessSystemIonHeapSize process_system_ion_heap_size = 10061; - VmsClientStats vms_client_stats = 10065; - NotificationRemoteViews notification_remote_views = 10066; - VoiceCallSession voice_call_session = 10076 [(log_from_module) = "telephony"]; - VoiceCallRatUsage voice_call_rat_usage = 10077 [(log_from_module) = "telephony"]; - SimSlotState sim_slot_state = 10078 [(log_from_module) = "telephony"]; - SupportedRadioAccessFamily supported_radio_access_family = - 10079 [(log_from_module) = "telephony"]; + SystemIonHeapSize system_ion_heap_size = 10056 [deprecated = true, (module) = "framework"]; + AppsOnExternalStorageInfo apps_on_external_storage_info = 10057 [(module) = "framework"]; + FaceSettings face_settings = 10058 [(module) = "framework"]; + CoolingDevice cooling_device = 10059 [(module) = "framework"]; + AppOps app_ops = 10060 [(module) = "framework"]; + ProcessSystemIonHeapSize process_system_ion_heap_size = 10061 [(module) = "framework"]; + SurfaceflingerStatsGlobalInfo surfaceflinger_stats_global_info = 10062; + SurfaceflingerStatsLayerInfo surfaceflinger_stats_layer_info = 10063; + ProcessMemorySnapshot process_memory_snapshot = 10064 [(module) = "framework"]; + VmsClientStats vms_client_stats = 10065 [(module) = "car"]; + NotificationRemoteViews notification_remote_views = 10066 [(module) = "framework"]; + DangerousPermissionStateSampled dangerous_permission_state_sampled = + 10067 [(module) = "framework"]; + GraphicsStats graphics_stats = 10068; + RuntimeAppOpAccess runtime_app_op_access = 10069 [(module) = "framework"]; + IonHeapSize ion_heap_size = 10070 [(module) = "framework"]; + PackageNotificationPreferences package_notification_preferences = + 10071 [(module) = "framework"]; + PackageNotificationChannelPreferences package_notification_channel_preferences = + 10072 [(module) = "framework"]; + PackageNotificationChannelGroupPreferences package_notification_channel_group_preferences = + 10073 [(module) = "framework"]; + GnssStats gnss_stats = 10074 [(module) = "framework"]; + AttributedAppOps attributed_app_ops = 10075 [(module) = "framework"]; + VoiceCallSession voice_call_session = 10076 [(module) = "telephony"]; + VoiceCallRatUsage voice_call_rat_usage = 10077 [(module) = "telephony"]; + SimSlotState sim_slot_state = 10078 [(module) = "telephony"]; + SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"]; + SettingSnapshot setting_snapshot = 10080 [(module) = "framework"]; + BlobInfo blob_info = 10081 [(module) = "framework"]; + DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"]; + BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered = + 10083 [(module) = "framework"]; + DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"]; + GeneralExternalStorageAccessStats general_external_storage_access_stats = + 10085 [(module) = "mediaprovider"]; } // DO NOT USE field numbers above 100,000 in AOSP. // Field numbers 100,000 - 199,999 are reserved for non-AOSP (e.g. OEMs) to use. // Field numbers 200,000 and above are reserved for future use; do not use them at all. + + reserved 10036; } /** @@ -534,7 +684,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).exclusive_state = true, (state_field_option).nested = false]; } /** @@ -545,10 +696,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).primary_field = true, (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).exclusive_state = true, (state_field_option).nested = false]; } /** @@ -580,7 +732,8 @@ message ActivityManagerSleepStateChanged { ASLEEP = 1; AWAKE = 2; } - optional State state = 1 [(state_field_option).option = EXCLUSIVE]; + optional State state = 1 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; } /** @@ -599,7 +752,7 @@ message MemoryFactorStateChanged { CRITICAL = 4; // critical memory. } - optional State factor = 1 [(state_field_option).option = EXCLUSIVE]; + optional State factor = 1 [(state_field_option).exclusive_state = true]; } /** @@ -632,6 +785,96 @@ message CachedKillReported { } /** + * Logs the change in wifi health. + * + * Logged from: + * frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiDataStall.java + */ +message WifiHealthStatReported { + enum Band { + UNKNOWN = 0; + // All of 2.4GHz band + BAND_2G = 1; + // Frequencies in the range of [5150, 5250) GHz + BAND_5G_LOW = 2; + // Frequencies in the range of [5250, 5725) GHz + BAND_5G_MIDDLE = 3; + // Frequencies in the range of [5725, 5850) GHz + BAND_5G_HIGH = 4; + // Frequencies in the range of [5925, 6425) GHz + BAND_6G_LOW = 5; + // Frequencies in the range of [6425, 6875) GHz + BAND_6G_MIDDLE = 6; + // Frequencies in the range of [6875, 7125) GHz + BAND_6G_HIGH = 7; + } + // duration this stat is obtained over in milliseconds + optional int32 duration_millis = 1; + // whether wifi is classified as sufficient for the user's data traffic, determined + // by whether the calculated throughput exceeds the average demand within |duration_millis| + optional bool is_sufficient = 2; + // whether cellular data is available + optional bool is_cell_data_available = 3; + // the Band bucket the connected network is on + optional Band band = 4; +} + +/** + * Logged when wifi detects a significant change in connection failure rate. + * + * Logged from: frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiHealthMonitor.java + * + */ +message WifiFailureStatReported { + enum AbnormalityType { + UNKNOWN = 0; + SIGNIFICANT_INCREASE = 1; + SIGNIFICANT_DECREASE = 2; + SIMPLY_HIGH = 3; + } + enum FailureType { + FAILURE_UNKNOWN = 0; + FAILURE_CONNECTION = 1; + FAILURE_ASSOCIATION_REJECTION = 2; + FAILURE_ASSOCIATION_TIMEOUT = 3; + FAILURE_AUTHENTICATION = 4; + FAILURE_NON_LOCAL_DISCONNECTION = 5; + FAILURE_SHORT_CONNECTION_DUE_TO_NON_LOCAL_DISCONNECTION = 6; + } + // Reason for uploading this stat + optional AbnormalityType abnormality_type = 1; + // The particular type of failure + optional FailureType failure_type = 2; + // How many times we have encountered this combination of AbnormalityType and FailureType + optional int32 failure_count = 3; +} + +/** + * Logs whether a wifi connection is successful and reasons for failure if it isn't. + * + * Logged from: + * frameworks/opt/net/wifi/service/java/com/android/server/wifi/ClientModeImpl.java + */ +message WifiConnectionResultReported { + enum FailureCode { + FAILURE_UNKNOWN = 0; + FAILURE_ASSOCIATION_TIMEOUT = 1; + FAILURE_ASSOCIATION_REJECTION = 2; + FAILURE_AUTHENTICATION_GENERAL = 3; + FAILURE_AUTHENTICATION_EAP = 4; + FAILURE_DHCP = 5; + FAILURE_NETWORK_DISCONNECTION = 6; + FAILURE_ROAM_TIMEOUT = 7; + } + // true represents a successful connection + optional bool connection_result = 1; + // reason for the connection failure + optional FailureCode failure_code = 2; + // scan rssi before the connection attempt + optional int32 rssi = 3; +} + +/** * Logs when memory stats of a process is reported. * * Logged from: @@ -686,7 +929,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).primary_field_first_uid = true]; enum State { OFF = 0; @@ -694,14 +938,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).exclusive_state = true, + (state_field_option).default_state_value = 0 /* State.OFF */, + (state_field_option).trigger_state_reset_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).primary_field = true]; // 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).primary_field = true]; // 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).primary_field = true]; } /** @@ -833,11 +1082,23 @@ message ScheduledJobStateChanged { FREQUENT = 2; RARE = 3; NEVER = 4; + RESTRICTED = 5; } optional Bucket standby_bucket = 5 [default = UNKNOWN]; // The job id (as assigned by the app). optional int32 job_id = 6; + + // One flag for each of the API constraints defined by Jobscheduler. Does not include implcit + // constraints as they are always assumed to be set. + optional bool has_charging_constraint = 7; + optional bool has_battery_not_low_constraint = 8; + optional bool has_storage_not_low_constraint = 9; + optional bool has_timing_delay_constraint = 10; + optional bool has_deadline_constraint = 11; + optional bool has_idle_constraint = 12; + optional bool has_connectivity_constraint = 13; + optional bool has_content_trigger_constraint = 14; } /** @@ -919,14 +1180,15 @@ message CameraStateChanged { * TODO */ message WakelockStateChanged { - repeated AttributionNode attribution_node = 1; + repeated AttributionNode attribution_node = 1 + [(state_field_option).primary_field_first_uid = true]; // 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; + optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).primary_field = true]; // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). - optional string tag = 3; + optional string tag = 3 [(state_field_option).primary_field = true]; enum State { RELEASE = 0; @@ -934,7 +1196,11 @@ message WakelockStateChanged { CHANGE_RELEASE = 2; CHANGE_ACQUIRE = 3; } - optional State state = 4; + optional State state = 4 [ + (state_field_option).exclusive_state = true, + (state_field_option).default_state_value = 0, + (state_field_option).nested = true + ]; } /** @@ -984,7 +1250,8 @@ message BatterySaverModeStateChanged { OFF = 0; ON = 1; } - optional State state = 1; + optional State state = 1 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; } /** @@ -994,7 +1261,8 @@ message BatterySaverModeStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message DeviceIdleModeStateChanged { - optional android.server.DeviceIdleModeEnum state = 1; + optional android.server.DeviceIdleModeEnum state = 1 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; } @@ -1005,7 +1273,8 @@ message DeviceIdleModeStateChanged { * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ message DeviceIdlingModeStateChanged { - optional android.server.DeviceIdleModeEnum state = 1; + optional android.server.DeviceIdleModeEnum state = 1 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; } /** @@ -1038,7 +1307,8 @@ message BatteryLevelChanged { */ message ChargingStateChanged { // State of the battery, from frameworks/base/core/proto/android/os/enums.proto. - optional android.os.BatteryStatusEnum state = 1; + optional android.os.BatteryStatusEnum state = 1 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; } /** @@ -1049,7 +1319,8 @@ message ChargingStateChanged { */ message PluggedStateChanged { // Whether the device is plugged in, from frameworks/base/core/proto/android/os/enums.proto. - optional android.os.BatteryPluggedStateEnum state = 1; + optional android.os.BatteryPluggedStateEnum state = 1 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; } /** @@ -1067,18 +1338,8 @@ message WakeupAlarmOccurred { // Name of source package (for historical reasons, since BatteryStats tracked it). optional string package_name = 3; - // These enum values match the STANDBY_BUCKET_XXX constants defined in UsageStatsManager.java. - enum Bucket { - UNKNOWN = 0; - EXEMPTED = 5; - ACTIVE = 10; - WORKING_SET = 20; - FREQUENT = 30; - RARE = 40; - NEVER = 50; - } // The App Standby bucket of the app that scheduled the alarm at the time the alarm fired. - optional Bucket app_standby_bucket = 4; + optional AppStandbyBucketChanged.Bucket app_standby_bucket = 4; } /** @@ -1139,6 +1400,8 @@ message WifiEnabledStateChanged { } /** + * This atom is deprecated starting in R. + * * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode. * TODO: Include support for Hotspot, perhaps by using an extra field to denote 'mode'. * Note that Wifi Scanning is monitored separately in WifiScanStateChanged. @@ -1789,12 +2052,15 @@ message WatchdogRollbackOccurred { REASON_EXPLICIT_HEALTH_CHECK = 2; REASON_APP_CRASH = 3; REASON_APP_NOT_RESPONDING = 4; + REASON_NATIVE_CRASH_DURING_BOOT = 5; } optional RollbackReasonType rollback_reason = 4; // Set by RollbackPackageHealthObserver to be the package that is failing when a rollback // is initiated. Empty if the package is unknown. optional string failing_package_name = 5; + + optional TrainExperimentIds experiment_ids = 6 [(log_mode) = MODE_BYTES]; } /** @@ -2518,8 +2784,9 @@ message UsbConnectorStateChanged { STATE_DISCONNECTED = 0; STATE_CONNECTED = 1; } - optional State state = 1; - optional string id = 2; + optional State state = 1 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; + optional string id = 2 [(state_field_option).primary_field = true]; // Last active session in ms. // 0 when the port is in connected state. optional int64 last_connect_duration_millis = 3; @@ -2750,21 +3017,32 @@ message PhoneStateChanged { message BackGesture { enum BackType { - DEFAULT_BACK_TYPE = 0; - COMPLETED = 1; - COMPLETED_REJECTED = 2; // successful because coming from rejected area - INCOMPLETE_EXCLUDED = 3; // would have been successful but in the exclusion area - INCOMPLETE = 4; + DEFAULT_BACK_TYPE = 0; + COMPLETED = 1; + COMPLETED_REJECTED = 2; // successful because coming from rejected area + INCOMPLETE_EXCLUDED = 3; // would have been successful but in the exclusion area + INCOMPLETE = 4; // Unsuccessful, for reasons other than below. + INCOMPLETE_FAR_FROM_EDGE = 5; // Unsuccessful, far from the edge. + INCOMPLETE_MULTI_TOUCH = 6; // Unsuccessful, multi touch. + INCOMPLETE_LONG_PRESS = 7; // Unsuccessful, long press. + INCOMPLETE_VERTICAL_MOVE = 8; // Unsuccessful, move vertically. } optional BackType type = 1; - optional int32 y_coordinate = 2; // y coordinate for ACTION_DOWN event + optional int32 y_coordinate = 2 [deprecated = true]; // y coordinate for ACTION_DOWN event + optional int32 start_x = 4; // X coordinate for ACTION_DOWN event. + optional int32 start_y = 5; // Y coordinate for ACTION_DOWN event. + optional int32 end_x = 6; // X coordinate for ACTION_MOVE event. + optional int32 end_y = 7; // Y coordinate for ACTION_MOVE event. + optional int32 left_boundary = 8; // left edge width + left inset + optional int32 right_boundary = 9; // screen width - (right edge width + right inset) + enum WindowHorizontalLocation { DEFAULT_LOCATION = 0; LEFT = 1; RIGHT = 2; } - optional WindowHorizontalLocation x_location = 3; + optional WindowHorizontalLocation x_location = 3 [deprecated = true]; } message ExclusionRectStateChanged { @@ -2783,14 +3061,125 @@ message ExclusionRectStateChanged { optional int32 duration_millis = 7; } +/** + * Logs when IME is on. + * + * Logged from: /packages/SystemUI/src/com/android/systemui/ + statusbar/phone/NavigationBarView.java + * + */ +message ImeTouchReported { + optional int32 x_coordinate = 1; // X coordinate for ACTION_DOWN event. + optional int32 y_coordinate = 2; // Y coordinate for ACTION_DOWN event. +} + +/** + * Logs when Launcher (HomeScreen) UI has changed or was interacted. + * + * Logged from: + * packages/apps/Launcher3 + */ message LauncherUIChanged { - optional android.stats.launcher.LauncherAction action = 1; + optional android.stats.launcher.LauncherAction action = 1 [deprecated = true]; optional android.stats.launcher.LauncherState src_state = 2; optional android.stats.launcher.LauncherState dst_state = 3; - optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES]; - optional bool is_swipe_up_enabled = 5; + optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES, deprecated = true]; + optional bool is_swipe_up_enabled = 5 [deprecated = true]; + + // The event id (e.g., app launch, drag and drop, long press) + optional int32 event_id = 6; + // The event's source or target id (e.g., icon, task, button) + optional int32 target_id = 7; + // If the target needs to be tracked, use this id field + optional int32 instance_id = 8; + optional int32 uid = 9 [(is_uid) = true]; + optional string package_name = 10; + optional string component_name = 11; + + // (x, y) coordinate and the index information of the target on the container + optional int32 grid_x = 12 [default = -1]; + optional int32 grid_y = 13 [default = -1]; + optional int32 page_id = 14 [default = -2]; + + // e.g., folder icon's (x, y) location and index information on the workspace + optional int32 grid_x_parent = 15 [default = -1]; + optional int32 grid_y_parent = 16 [default = -1]; + optional int32 page_id_parent = 17 [default = -2]; + + // e.g., SEARCHBOX_ALLAPPS, FOLDER_WORKSPACE + optional int32 hierarchy = 18; + + optional bool is_work_profile = 19; + + // Used to store the predicted rank of the target + optional int32 rank = 20 [default = -1]; + + // e.g., folderLabelState can be captured in the following two fields + optional int32 from_state = 21; + optional int32 to_state = 22; + + // e.g., autofilled or suggested texts that are not user entered + optional string edittext = 23; + + // e.g., number of contents inside a container (e.g., icons inside a folder) + optional int32 cardinality = 24; +} + +/** + * Used for snapshot of the HomeScreen UI elements + * + * Logged from: + * packages/apps/Launcher3 + */ +message LauncherStaticLayout { + // The event id (e.g., snapshot, drag and drop) + optional int32 event_id = 1; + // The event's source or target id (e.g., icon, shortcut, widget) + optional int32 target_id = 2; + // If the target needs to be tracked, use this id field + optional int32 instance_id = 3; + optional int32 uid = 4 [(is_uid) = true]; + optional string package_name = 5; + optional string component_name = 6; + + // (x, y) coordinate and the index information of the target on the container + optional int32 grid_x = 7 [default = -1]; + optional int32 grid_y = 8 [default = -1]; + optional int32 page_id = 9 [default = -2]; + + // e.g., folder icon's (x, y) location and index information on the workspace + // e.g., when used with widgets target, use these values for (span_x, span_y) + optional int32 grid_x_parent = 10 [default = -1]; + optional int32 grid_y_parent = 11 [default = -1]; + optional int32 page_id_parent = 12 [default = -2]; + + // UNKNOWN = 0 + // HOTSEAT = 1 + // WORKSPACE = 2 + // FOLDER_HOTSEAT = 3 + // FOLDER_WORKSPACE = 4 + optional int32 hierarchy = 13; + + optional bool is_work_profile = 14; + + // e.g., PIN, WIDGET TRAY, APPS TRAY, PREDICTION + optional int32 origin = 15; + + // e.g., number of icons inside a folder + optional int32 cardinality = 16; + + // e.g., (x, y) span of the widget inside homescreen grid system + optional int32 span_x = 17 [default = 1]; + optional int32 span_y = 18 [default = 1]; } +/** + * Logs when Wallpaper or ThemePicker UI has changed. + * + * Logged from: + * packages/apps/ThemePicker + * packages/apps/WallpaperPicker2 + */ message StyleUIChanged { optional android.stats.style.Action action = 1; optional int32 color_package_hash = 2; @@ -2848,12 +3237,14 @@ message SettingsUIChanged { message TouchEventReported { /** * The fields latency_{min|max|mean|stdev} represent minimum, maximum, mean, - * and the standard deviation of latency between the kernel and framework - * for touchscreen events. The units are microseconds. + * and the standard deviation of the time spent processing touchscreen events + * in the kernel and inputflinger. The units are microseconds. * - * The number is measured as the difference between the time at which - * the input event was received in the evdev driver, - * and the time at which the input event was received in EventHub. + * On supported devices, the starting point is taken during the hard interrupt inside the + * kernel touch driver. On all other devices, the starting point is taken inside + * the kernel's input event subsystem upon receipt of the input event. + * The ending point is taken inside InputDispatcher, just after the input event + * is sent to the app. */ // Minimum value optional float latency_min_micros = 1; @@ -3023,7 +3414,7 @@ message AppCrashOccurred { optional string process_name = 3; // The pid if available. -1 means not available. - optional sint32 pid = 4; + optional int32 pid = 4; optional string package_name = 5; @@ -3059,7 +3450,7 @@ message WTFOccurred { optional string process_name = 3; // The pid if available. -1 means not available. - optional sint32 pid = 4; + optional int32 pid = 4; optional android.server.ErrorSource error_source = 5; } @@ -3295,9 +3686,9 @@ message PictureInPictureStateChanged { * services/core/java/com/android/server/wm/Session.java */ message OverlayStateChanged { - optional int32 uid = 1 [(is_uid) = true]; + optional int32 uid = 1 [(state_field_option).primary_field = true, (is_uid) = true]; - optional string package_name = 2; + optional string package_name = 2 [(state_field_option).primary_field = true]; optional bool using_alert_window = 3; @@ -3305,15 +3696,16 @@ message OverlayStateChanged { ENTERED = 1; EXITED = 2; } - optional State state = 4; + optional State state = 4 + [(state_field_option).exclusive_state = true, (state_field_option).nested = false]; } -/* +/** * Logs foreground service starts and stops. * Note that this is not when a service starts or stops, but when it is * considered foreground. * Logged from - * //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java + * frameworks/base/services/core/java/com/android/server/am/ActiveServices.java */ message ForegroundServiceStateChanged { optional int32 uid = 1 [(is_uid) = true]; @@ -3325,6 +3717,45 @@ message ForegroundServiceStateChanged { EXIT = 2; } optional State state = 3; + + // Whether the fgs is allowed while-in-use permissions, i.e. is considered 'in-use' to the user. + // (If the fgs was started while the app wasn't TOP it usually will be denied these permissions) + optional bool allow_while_in_use_permission = 4; +} + +/** + * Logs the number of times a uid accesses a sensitive AppOp during a foreground service session. + * A foreground service session is any continuous period during which the uid holds at least one + * foreground service; the atom will be pushed when the uid no longer holds any foreground services. + * Accesses initiated while the uid is in the TOP state are ignored. + * Sessions with no attempted accesses are not logged. + * Logged from + * frameworks/base/services/core/java/com/android/server/am/ActiveServices.java + */ +message ForegroundServiceAppOpSessionEnded { + optional int32 uid = 1 [(is_uid) = true]; + + // The operation's name. + // Only following four ops are logged + // COARSE_LOCATION = 0 + // FINE_LOCATION = 1 + // CAMERA = 26 + // RECORD_AUDIO = 27 + optional android.app.AppOpEnum app_op_name = 2 [default = APP_OP_NONE]; + + // The uid's permission mode for accessing the AppOp during this fgs session. + enum Mode { + MODE_UNKNOWN = 0; + MODE_ALLOWED = 1; // Always allowed + MODE_IGNORED = 2; // Denied + MODE_FOREGROUND = 3; // Allow-while-in-use (or allowed-one-time) + } + optional Mode app_op_mode = 3; + + // Number of times this AppOp was requested and allowed. + optional int32 count_ops_accepted = 4; + // Number of times this AppOp was requested but denied. + optional int32 count_ops_rejected = 5; } /** @@ -3471,7 +3902,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).exclusive_state = true]; } /** @@ -3486,6 +3917,144 @@ message GenericAtom { } /** + * Atom for simple logging of user interaction and impression events, such as "the user touched + * this button" or "this dialog was displayed". + * Keep the UI event stream clean: don't use for system or background events. + * Log using the UiEventLogger wrapper - don't write with the StatsLog API directly. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/ + * frameworks/base/packages/SystemUI/src/com/android/systemui/ + */ +message UiEventReported { + // The event_id. + optional int32 event_id = 1; + // The event's source or target uid and package, if applicable. + // For example, the package posting a notification, or the destination package of a share. + optional int32 uid = 2 [(is_uid) = true]; + optional string package_name = 3; + // An identifier used to disambiguate which logs refer to a particular instance of some + // UI element. Useful when there might be multiple instances simultaneously active. + optional int32 instance_id = 4; +} + +/** + * Reports a notification was created or updated. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/notification/ + */ +message NotificationReported { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + // The notifying app's uid and package. + optional int32 uid = 2 [(is_uid) = true]; + optional string package_name = 3; + // A small system-assigned identifier for the notification. + // Locally probably-unique, but expect collisions across users and/or days. + optional int32 instance_id = 4; + optional int32 notification_id_hash = 5; // Small hash of the app-assigned notif ID + tag + optional int32 channel_id_hash = 6; // Small hash of app-assigned channel ID + + // Grouping information + optional int32 group_id_hash = 7; // Small hash of the group ID of the notification + optional int32 group_instance_id = 8; // Instance_id of the group-summary notification + optional bool is_group_summary = 9; // Tags the group-summary notification + + // Attributes + optional string category = 10; // App-assigned notification category (API-defined strings) + optional int32 style = 11; // App-assigned notification style + optional int32 num_people = 12; // Number of Person records attached to the notification + + // Ordering, importance and interruptiveness + + optional int32 position = 13; // Position in NotificationManager's list + + optional android.stats.sysui.NotificationImportance importance = 14; + optional int32 alerting = 15; // Bitfield, 1=buzz 2=beep 4=blink + + enum NotificationImportanceExplanation { + IMPORTANCE_EXPLANATION_UNKNOWN = 0; + IMPORTANCE_EXPLANATION_APP = 1; // App-specified channel importance. + IMPORTANCE_EXPLANATION_USER = 2; // User-specified channel importance. + IMPORTANCE_EXPLANATION_ASST = 3; // Notification Assistant override. + IMPORTANCE_EXPLANATION_SYSTEM = 4; // System override. + // Like _APP, but based on pre-channels priority signal. + IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS = 5; + } + + optional NotificationImportanceExplanation importance_source = 16; + optional android.stats.sysui.NotificationImportance importance_initial = 17; + optional NotificationImportanceExplanation importance_initial_source = 18; + optional android.stats.sysui.NotificationImportance importance_asst = 19; + optional int32 assistant_hash = 20; + optional float assistant_ranking_score = 21; +} + +message Notification { + // The notifying app's uid and package. + optional int32 uid = 1 [(is_uid) = true]; + optional string package_name = 2; + // A small system-assigned identifier for the notification. + optional int32 instance_id = 3; + + // Grouping information. + optional int32 group_instance_id = 4; + optional bool is_group_summary = 5; + + // The section of the shade that the notification is in. + // See NotificationSectionsManager.PriorityBucket. + enum NotificationSection { + SECTION_UNKNOWN = 0; + SECTION_HEADS_UP = 1; + SECTION_MEDIA_CONTROLS = 2; + SECTION_PEOPLE = 3; + SECTION_ALERTING = 4; + SECTION_SILENT = 5; + } + optional NotificationSection section = 6; +} + +message NotificationList { + repeated Notification notifications = 1; // An ordered sequence of notifications. +} + +/** + * Reports a notification panel was displayed, e.g. from the lockscreen or status bar. + * + * Logged from: + * frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/ + */ +message NotificationPanelReported { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + optional int32 num_notifications = 2; + // The notifications in the panel, in the order that they appear there. + optional NotificationList notifications = 3 [(log_mode) = MODE_BYTES]; +} + +/** + * Reports a notification channel, or channel group, was created, updated, or deleted. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/notification/ + */ +message NotificationChannelModified { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + // The notifying app's uid and package. + optional int32 uid = 2 [(is_uid) = true]; + optional string package_name = 3; + // Hash of app-assigned notification channel ID or channel-group ID + optional int32 channel_id_hash = 4; + // Previous importance setting, if applicable + optional android.stats.sysui.NotificationImportance old_importance = 5; + // New importance setting + optional android.stats.sysui.NotificationImportance importance = 6; +} + + +/** * Logs when a biometric acquire event occurs. * * Logged from: @@ -3622,6 +4191,7 @@ message FlagFlipUpdateOccurred { /** * Potential experiment ids that goes with a train install. + * Should be kept in sync with experiment_ids.proto. */ message TrainExperimentIds { repeated int64 experiment_id = 1; @@ -3673,12 +4243,16 @@ message BinaryPushStateChanged { INSTALL_FAILURE_DOWNLOAD = 23; INSTALL_FAILURE_STATE_MISMATCH = 24; INSTALL_FAILURE_COMMIT = 25; + REBOOT_TRIGGERED = 26; } optional State state = 6; // Possible experiment ids for monitoring this push. optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES]; // user id optional int32 user_id = 8; + optional int32 reason = 9; + // Whether or not this is a rollback event + optional bool is_rollback = 10; } /* Test atom, is not logged anywhere */ @@ -3851,7 +4425,7 @@ message PrivacyIndicatorsInteracted { DIALOG_LINE_ITEM = 5; } - optional Type type = 1 [(state_field_option).option = EXCLUSIVE]; + optional Type type = 1 [(state_field_option).exclusive_state = true]; // Used if the type is LINE_ITEM optional string package_name = 2; @@ -3981,6 +4555,145 @@ message VmsClientConnectionStateChanged { optional State state = 2; } +message MimeTypes { + repeated string mime_types = 1; +} + +/** + * Logs statistics regarding accesses to external storage. + * All stats are normalized for one day period. + * + * Logged from: + * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java + */ +message GeneralExternalStorageAccessStats { + optional int32 uid = 1 [(is_uid) = true]; + // Total number of accesses like creation, open, delete and rename/update. + // Includes file path and ContentResolver accesses + optional uint32 total_accesses = 2; + // Number of file path accesses, as opposed to file path and ContentResolver. + optional uint32 file_path_accesses = 3; + // Number of accesses on secondary volumes like SD cards. + // Includes file path and ContentResolver accesses + optional uint32 secondary_storage_accesses = 4; + // Comma-separated list of mime types that were accessed. + optional MimeTypes mime_types_accessed = 5; +} + +/** + * Logs when MediaProvider has successfully finished scanning a storage volume. + * + * Logged from: + * packages/providers/MediaProvider/src/com/android/providers/media/scan/ModernMediaScanner.java + */ +message MediaProviderScanOccurred { + enum Reason { + // Scan triggered due to unknown reason + UNKNOWN = 0; + // Scan triggered due to storage volume being mounted + MOUNTED = 1; + // Scan triggered due to explicit user action or app request + DEMAND = 2; + // Scan triggered due to idle maintenance + IDLE = 3; + } + + // Volume type that this event pertains to + optional android.stats.mediaprovider.VolumeType volume_type = 1; + // Reason why this scan was triggered + optional Reason reason = 2; + // Total number of files scanned + optional int64 item_count = 3; + // Duration of scan, normalized per file + optional float normalized_duration_millis = 4; + // Number of database inserts, normalized per file + optional float normalized_insert_count = 5; + // Number of database updates, normalized per file + optional float normalized_update_count = 6; + // Number of database deletes, normalized per file + optional float normalized_delete_count = 7; +} + +/** + * Logs when an app has asked MediaProvider to delete media belonging to the user. + * + * Logged from: + * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java + */ +message MediaContentDeleted { + // Volume type that this event pertains to + optional android.stats.mediaprovider.VolumeType volume_type = 1; + // UID of app that requested deletion + optional int32 uid = 2 [(is_uid) = true]; + // Number of items that were deleted + optional int32 item_count = 3; +} + +/** + * Logs when an app has asked MediaProvider to grant them access to media belonging to the user. + * + * Logged from: + * packages/providers/MediaProvider/src/com/android/providers/media/PermissionActivity.java + */ +message MediaProviderPermissionRequested { + enum Result { + UNKNOWN = 0; + USER_GRANTED = 1; + AUTO_GRANTED = 2; + USER_DENIED = 3; + USER_DENIED_WITH_PREJUDICE = 4; + AUTO_DENIED = 5; + } + + // Volume type that this event pertains to + optional android.stats.mediaprovider.VolumeType volume_type = 1; + // UID of app that requested permission + optional int32 uid = 2 [(is_uid) = true]; + // Number of items that were requested + optional int32 item_count = 3; + // Result of this request + optional Result result = 4; +} + +/** + * Logs when MediaProvider has finished upgrading or downgrading its database schema. + * + * Logged from: + * packages/providers/MediaProvider/src/com/android/providers/media/DatabaseHelper.java + */ +message MediaProviderSchemaChanged { + // Volume type that this event pertains to + optional android.stats.mediaprovider.VolumeType volume_type = 1; + // Old database version code + optional int32 version_from = 2; + // New database version code + optional int32 version_to = 3; + // Total number of files in database + optional int64 item_count = 4; + // Duration of schema change, normalized per file + optional float normalized_duration_millis = 5; +} + +/** + * Logs when MediaProvider has finished an idle maintenance job. + * + * Logged from: + * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java + */ +message MediaProviderIdleMaintenanceFinished { + // Volume type that this event pertains to + optional android.stats.mediaprovider.VolumeType volume_type = 1; + + // Total number of files in database + optional int64 item_count = 2; + // Duration of idle maintenance, normalized per file + optional float normalized_duration_millis = 3; + // Number of thumbnails found to be stale, normalized per file + optional float normalized_stale_thumbnails = 4; + // Number of items found to be expired, normalized per file + optional float normalized_expired_media = 5; +} + /** * Represents boot time event with duration in ms. * @@ -4190,8 +4903,7 @@ message BootTimeEventErrorCode { * after an OTA. * * Logged from: - * - system/core/fs_mgr/libsnapshot/snapshot.cpp - * - system/core/fs_mgr/libsnapshot/snapshotctl.cpp + * - system/update_engine/cleanup_previous_update_action.cc */ message SnapshotMergeReported { // Keep in sync with @@ -4229,6 +4941,106 @@ message SnapshotMergeReported { // Number of reboots that occurred after issuing and before completing the // merge of all the snapshot devices. optional int32 intermediate_reboots = 3; + + // The device has been upgraded to Virtual A/B. + optional bool is_vab_retrofit = 4; + + // Space that has been temporarily allocated in the /data partition + // containing the dm-snapshot's copy-on-write data generated during a + // Virtual A/B update. + optional int64 cow_file_size_bytes = 5; +} + +/** + * Event representing when BlobStoreManager.Session#commit() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobCommitted { + // Uid of the Blob committer + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob committed + optional int64 blob_id = 2; + + // Size of the Blob + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Commit Succeeded + SUCCESS = 1; + // Commit Failed: Error occurred during commit + ERROR_DURING_COMMIT = 2; + // Commit Failed: Digest of the data did not match Blob digest + DIGEST_MISMATCH = 3; + // Commit Failed: Allowed count limit exceeded + COUNT_LIMIT_EXCEEDED = 4; + } + optional Result result = 4; +} + +/** + * Event representing when BlobStoreManager#acquireLease() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobLeased{ + // Uid of the Blob leasee + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob leased or 0 if the Blob does not exist + optional int64 blob_id = 2; + + // Size of the Blob or 0 if the Blob does not exist + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Lease Succeeded + SUCCESS = 1; + // Lease Failed: Blob does not exist + BLOB_DNE = 2; + // Lease Failed: Leasee does not have access to the Blob + ACCESS_NOT_ALLOWED = 3; + // Lease Failed: Leasee requested an invalid expiry duration + LEASE_EXPIRY_INVALID = 4; + // Lease Failed: Leasee has exceeded the total data lease limit + DATA_SIZE_LIMIT_EXCEEDED = 5; + // Leasee Failed: Allowed count limit exceeded + COUNT_LIMIT_EXCEEDED = 6; + } + optional Result result = 4; +} + +/** + * Event representing when BlobStoreManager#openBlob() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobOpened{ + // Uid of the Blob opener + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob opened or 0 if the Blob does not exist + optional int64 blob_id = 2; + + // Size of the Blob or 0 if the Blob does not exist + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Open Succeeded + SUCCESS = 1; + // Open Failed: Blob does not exist + BLOB_DNE = 2; + // Open Failed: Opener does not have access to the Blob + ACCESS_NOT_ALLOWED = 3; + } + optional Result result = 4; } ////////////////////////////////////////////////////////////////////// @@ -4315,6 +5127,66 @@ message MobileBytesTransferByFgBg { } /** + * Used for pull network statistics via mobile|wifi networks, and sliced by interesting dimensions. + * Note that the data is expected to be sliced into more dimensions in future. In other words, + * the caller must not assume any row of data is one full report when filtering with a set of + * matching conditions, because future data may represent with multiple rows what is currently + * represented by one. + * To avoid being broken by future slicing, callers must take care to aggregate rows even if they + * query all the existing columns. + * + * Pulled from: + * StatsPullAtomService (using NetworkStatsService to get NetworkStats) + */ +message DataUsageBytesTransfer { + // State of this record. Should be NetworkStats#SET_DEFAULT or NetworkStats#SET_FOREGROUND to + // indicate the foreground state, or NetworkStats#SET_ALL to indicate the record is for all + // states combined, not including debug states. See NetworkStats#SET_*. + optional int32 state = 1; + + optional int64 rx_bytes = 2; + + optional int64 rx_packets = 3; + + optional int64 tx_bytes = 4; + + optional int64 tx_packets = 5; + + // Radio Access Technology (RAT) type of this record, should be one of + // TelephonyManager#NETWORK_TYPE_* constants, or NetworkTemplate#NETWORK_TYPE_ALL to indicate + // the record is for all rat types combined. + optional int32 rat_type = 6; + + // Mcc/Mnc read from sim if the record is for a specific subscription, null indicates the + // record is combined across subscriptions. + optional string sim_mcc = 7; + optional string sim_mnc = 8; + + // Allows mobile virtual network operators (MVNOs) to be identified with individual IDs. + // See TelephonyManager#getSimCarrierId. + optional int32 carrier_id = 9; + + // Enumeration of opportunistic states with an additional ALL state indicates the record is + // combined regardless of the boolean value in its field. + enum DataSubscriptionState { + UNKNOWN = 0; // For server side backward compatibility. + ALL = 1; + OPPORTUNISTIC = 2; + NOT_OPPORTUNISTIC = 3; + } + // Mark whether the subscription is an opportunistic data subscription, and ALL indicates the + // record is combined across opportunistic data subscriptions. + // See {@link SubscriptionManager#setOpportunistic}. + optional DataSubscriptionState opportunistic_data_sub = 10; + + // Indicate whether NR is connected, server side could use this with RAT type to determine if + // the record is for 5G NSA (Non Stand Alone) mode, where the primary cell is still LTE and + // network allocates a secondary 5G cell so telephony reports RAT = LTE along with NR state as + // connected. + optional bool is_nr_connected = 11; +} + +/** * Pulls bytes transferred via bluetooth. It is pulled from Bluetooth controller. * * Pulled from: @@ -4521,8 +5393,8 @@ message ProcessMemoryState { optional int64 page_major_fault = 5; // RSS - // Value is read from /proc/PID/status. Or from memory.stat, field - // total_rss if per-app memory cgroups are enabled. + // Value is read from memory.stat, field total_rss if per-app memory + // cgroups are enabled. Otherwise, value from /proc/pid/stat. optional int64 rss_in_bytes = 6; // CACHE @@ -4532,67 +5404,52 @@ message ProcessMemoryState { // SWAP // Value is read from memory.stat, field total_swap if per-app memory - // cgroups are enabled. Otherwise, VmSwap from /proc/PID/status. + // cgroups are enabled. Otherwise, 0. optional int64 swap_in_bytes = 8; - // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always 0. + // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always -1. optional int64 rss_high_watermark_in_bytes = 9 [deprecated = true]; - // Elapsed real time when the process started. - // Value is read from /proc/PID/stat, field 22. 0 if read from per-app memory cgroups. - optional int64 start_time_nanos = 10; + // Deprecated: use ProcessMemorySnapshot atom instead. Always -1. + optional int64 start_time_nanos = 10 [deprecated = true]; - // Anonymous page size plus swap size. Values are read from /proc/PID/status. - optional int32 anon_rss_and_swap_in_kilobytes = 11; + // Deprecated: use ProcessMemorySnapshot atom instead. Always -1. + optional int32 anon_rss_and_swap_in_kilobytes = 11 [deprecated = true]; } /* - * Logs the memory stats for a native process (from procfs). + * Logs the memory high-water mark for a process. * - * Pulled from StatsCompanionService for selected native processes. + * Pulled from StatsCompanionService for all managed processes (from ActivityManagerServie) + * and for selected native processes. + * + * Pulling this atom resets high-water mark counters for all processes. */ -message NativeProcessMemoryState { +message ProcessMemoryHighWaterMark { // The uid if available. -1 means not available. optional int32 uid = 1 [(is_uid) = true]; // The process name. - // Value read from /proc/PID/cmdline. + // Usually package name or process cmdline. + // Provided by ActivityManagerService or read from /proc/PID/cmdline. optional string process_name = 2; - // # of page-faults - optional int64 page_fault = 3; - - // # of major page-faults - optional int64 page_major_fault = 4; - - // RSS - // Value read from /proc/PID/status. - optional int64 rss_in_bytes = 5; - - // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always 0. - optional int64 rss_high_watermark_in_bytes = 6 [deprecated = true]; - - // Elapsed real time when the process started. - // Value is read from /proc/PID/stat, field 22. - optional int64 start_time_nanos = 7; - - // SWAP - // Value read from /proc/PID/status, field VmSwap. - optional int64 swap_in_bytes = 8; + // Deprecated: use rss_high_water_mark_in_kilobytes instead. This field is + // computed by converting kilobytes to bytes. + optional int64 rss_high_water_mark_in_bytes = 3 [deprecated = true]; - // Anonymous page size plus swap size. Values are read from /proc/PID/status. - optional int32 anon_rss_and_swap_in_kilobytes = 9; + // RSS high-water mark. Peak RSS usage of the process. Read from the VmHWM field in + // /proc/PID/status. + optional int32 rss_high_water_mark_in_kilobytes = 4; } /* - * Logs the memory high-water mark for a process. + * Logs the memory stats for a process. * - * Pulled from StatsCompanionService for all managed processes (from ActivityManagerServie) + * Pulled from StatsCompanionService for all managed processes (from ActivityManagerService) * and for selected native processes. - * - * Pulling this atom resets high-water mark counters for all processes. */ -message ProcessMemoryHighWaterMark { +message ProcessMemorySnapshot { // The uid if available. -1 means not available. optional int32 uid = 1 [(is_uid) = true]; @@ -4601,9 +5458,29 @@ message ProcessMemoryHighWaterMark { // Provided by ActivityManagerService or read from /proc/PID/cmdline. optional string process_name = 2; - // RSS high-water mark. Peak RSS usage of the process. Read from the VmHWM field in - // /proc/PID/status. - optional int64 rss_high_water_mark_in_bytes = 3; + // The pid of the process. + // Allows to disambiguate instances of the process. + optional int32 pid = 3; + + // The current OOM score adjustment value. + // Read from ProcessRecord for managed processes. + // Placeholder -1001 (OOM_SCORE_ADJ_MIN - 1, outside of allowed range) for native ones. + optional int32 oom_score_adj = 4; + + // The current RSS of the process. + // VmRSS from /proc/pid/status. + optional int32 rss_in_kilobytes = 5; + + // The current anon RSS of the process. + // RssAnon from /proc/pid/status. + optional int32 anon_rss_in_kilobytes = 6; + + // The current swap size of the process. + // VmSwap from /proc/pid/status. + optional int32 swap_in_kilobytes = 7; + + // The sum of rss_in_kilobytes and swap_in_kilobytes. + optional int32 anon_rss_and_swap_in_kilobytes = 8; } /* @@ -5026,48 +5903,81 @@ message RoleHolder { } message AggStats { - optional int64 min = 1; - - optional int64 average = 2; - - optional int64 max = 3; -} - + // These are all in byte resolution. + optional int64 min = 1 [deprecated = true]; + optional int64 average = 2 [deprecated = true]; + optional int64 max = 3 [deprecated = true]; + + // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above + // int64 fields. + optional int32 mean_kb = 4; + optional int32 max_kb = 5; +} + +// A reduced subset of process states; reducing the number of possible states allows more +// aggressive device-side aggregation of statistics and hence reduces metric upload size. +enum ProcessStateAggregated { + PROCESS_STATE_UNKNOWN = 0; + // Persistent system process. + PROCESS_STATE_PERSISTENT = 1; + // Top activity; actually any visible activity. + PROCESS_STATE_TOP = 2; + // Process binding to top or a foreground service. + PROCESS_STATE_BOUND_TOP_OR_FGS = 3; + // Processing running a foreground service. + PROCESS_STATE_FGS = 4; + // Important foreground process (ime, wallpaper, etc). + PROCESS_STATE_IMPORTANT_FOREGROUND = 5; + // Important background process. + PROCESS_STATE_BACKGROUND = 6; + // Process running a receiver. + PROCESS_STATE_RECEIVER = 7; + // All kinds of cached processes. + PROCESS_STATE_CACHED = 8; +} + +// Next tag: 13 message ProcessStatsStateProto { optional android.service.procstats.ScreenState screen_state = 1; - optional android.service.procstats.MemoryState memory_state = 2; + optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true]; // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java // and not frameworks/base/core/java/android/app/ActivityManager.java - optional android.service.procstats.ProcessState process_state = 3; + optional android.service.procstats.ProcessState process_state = 3 [deprecated = true]; + + optional ProcessStateAggregated process_state_aggregated = 10; // Millisecond uptime duration spent in this state - optional int64 duration_millis = 4; + optional int64 duration_millis = 4 [deprecated = true]; + // Same as above, but with minute resolution so it fits into an int32. + optional int32 duration_minutes = 11; // Millisecond elapsed realtime duration spent in this state - optional int64 realtime_duration_millis = 9; + optional int64 realtime_duration_millis = 9 [deprecated = true]; + // Same as above, but with minute resolution so it fits into an int32. + optional int32 realtime_duration_minutes = 12; // # of samples taken optional int32 sample_size = 5; // PSS is memory reserved for this process - optional AggStats pss = 6; + optional AggStats pss = 6 [deprecated = true]; // USS is memory shared between processes, divided evenly for accounting - optional AggStats uss = 7; + optional AggStats uss = 7 [deprecated = true]; // RSS is memory resident for this process optional AggStats rss = 8; } -// Next Tag: 7 +// Next Tag: 8 message ProcessStatsProto { // Name of process. optional string process = 1; // Uid of the process. - optional int32 uid = 2; + optional int32 uid = 2 [(is_uid) = true]; // Information about how often kills occurred message Kill { @@ -5080,7 +5990,7 @@ message ProcessStatsProto { // PSS stats during cached kill optional AggStats cached_pss = 3; } - optional Kill kill = 3; + optional Kill kill = 3 [deprecated = true]; // Time and memory spent in various states. repeated ProcessStatsStateProto states = 5; @@ -5088,6 +5998,28 @@ message ProcessStatsProto { // Total time process has been running... screen_state, memory_state, and process_state // will not be set. optional ProcessStatsStateProto total_running_state = 6; + + // Association data for this process in this state; + // each entry here is one association. + repeated ProcessStatsAssociationProto assocs = 7; +} + +// Next Tag: 6 +message ProcessStatsAssociationProto { + // Procss Name of the associated process (client process of service binding) + optional string assoc_process_name = 1; + + // Package Name of the associated package (client package of service binding) + optional string assoc_package_name = 2 [deprecated = true]; + + // UID of the associated process/package (client package of service binding) + optional int32 assoc_uid = 5 [(is_uid) = true]; + + // Total count of the times this association (service binding) appeared. + optional int32 total_count = 3; + + // Uptime total duration in seconds this association (service binding) was around. + optional int32 total_duration_secs = 4; } message PackageServiceOperationStatsProto { @@ -5236,6 +6168,10 @@ message ProcessStatsAvailablePagesProto { */ message ProcStats { optional ProcessStatsSectionProto proc_stats_section = 1; + // Data pulled from device into this is sometimes sharded across multiple atoms to work around + // a size limit. When this happens, this shard ID will contain an increasing 1-indexed integer + // with the number of this shard. + optional int32 shard_id = 2; } /** @@ -5263,6 +6199,141 @@ message NotificationRemoteViews { optional NotificationRemoteViewsProto notification_remote_views = 1; } +/** + * Atom that contains a list of a package's preferences, pulled from NotificationManagerService.java + */ +message PackageNotificationPreferences { + // Uid under which the package is installed. + optional int32 uid = 1 [(is_uid) = true]; + // Notification importance, which specifies when and how a notification is displayed. + // Specified under core/java/android/app/NotificationManager.java. + optional int32 importance = 2; + // Lockscreen visibility as set by the user. + optional int32 visibility = 3; + // Bitfield mask indicating what fields were locked by the user (see LockableAppfields in + // PreferencesHelper.java) + optional int32 user_locked_fields = 4; +} + +/** + * Atom that contains a list of a package's channel preferences, pulled from + * NotificationManagerService.java. + */ +message PackageNotificationChannelPreferences { + // Uid under which the package is installed. + optional int32 uid = 1 [(is_uid) = true]; + // Channel's ID. Should always be available. + optional string channel_id = 2; + // Channel's name. Should always be available. + optional string channel_name = 3; + // Channel's description. Optionally set by the channel creator. + optional string description = 4; + // Notification importance, which specifies when and how a notification is displayed. Specified + // under core/java/android/app/NotificationManager.java. + optional int32 importance = 5; + // Bitmask representing which fields have been set by the user. See field bitmask descriptions + // at core/java/android/app/NotificationChannel.java + optional int32 user_locked_fields = 6; + // Indicates if the channel was deleted by the app. + optional bool is_deleted = 7; + // Indicates if the channel was marked as a conversation by the app. + optional bool is_conversation = 8; + // Indicates if the channel is a conversation that was demoted by the user. + optional bool is_demoted_conversation = 9; + // Indicates if the channel is a conversation that was marked as important by the user. + optional bool is_important_conversation = 10; +} + +/** + * Atom that represents an item in the list of Do Not Disturb rules, pulled from + * NotificationManagerService.java. + */ +message DNDModeProto { + enum Mode { + ROOT_CONFIG = -1; // Used to distinguish the config (one per user) from the rules. + ZEN_MODE_OFF = 0; + ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + ZEN_MODE_NO_INTERRUPTIONS = 2; + ZEN_MODE_ALARMS = 3; + } + optional int32 user = 1; // Android user ID (0, 1, 10, ...) + optional bool enabled = 2; // true for ROOT_CONFIG if a manualRule is enabled + optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG + optional Mode zen_mode = 4; + // id is one of the system default rule IDs, or empty + // May also be "MANUAL_RULE" to indicate app-activation of the manual rule. + optional string id = 5; + optional int32 uid = 6 [(is_uid) = true]; // currently only SYSTEM_UID or 0 for other + optional DNDPolicyProto policy = 7; +} + +/** + * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto. + */ +message DNDPolicyProto { + enum State { + STATE_UNSET = 0; + STATE_ALLOW = 1; + STATE_DISALLOW = 2; + } + optional State calls = 1; + optional State repeat_callers = 2; + optional State messages = 3; + optional State conversations = 4; + optional State reminders = 5; + optional State events = 6; + optional State alarms = 7; + optional State media = 8; + optional State system = 9; + optional State fullscreen = 10; + optional State lights = 11; + optional State peek = 12; + optional State status_bar = 13; + optional State badge = 14; + optional State ambient = 15; + optional State notification_list = 16; + + enum PeopleType { + PEOPLE_UNSET = 0; + PEOPLE_ANYONE = 1; + PEOPLE_CONTACTS = 2; + PEOPLE_STARRED = 3; + PEOPLE_NONE = 4; + } + + optional PeopleType allow_calls_from = 17; + optional PeopleType allow_messages_from = 18; + + enum ConversationType { + CONV_UNSET = 0; + CONV_ANYONE = 1; + CONV_IMPORTANT = 2; + CONV_NONE = 3; + } + + optional ConversationType allow_conversations_from = 19; +} + +/** + * Atom that contains a list of a package's channel group preferences, pulled from + * NotificationManagerService.java. + */ +message PackageNotificationChannelGroupPreferences { + // Uid under which the package is installed. + optional int32 uid = 1 [(is_uid) = true]; + // Channel Group's ID. Should always be available. + optional string group_id = 2; + // Channel Group's name. Should always be available. + optional string group_name = 3; + // Channel Group's description. Optionally set by group creator. + optional string description = 4; + // Indicates if notifications from this channel group are blocked. + optional bool is_blocked = 5; + // Bitmask representing which fields have been set by the user. See field bitmask descriptions + // at core/java/android/app/NotificationChannelGroup.java + optional int32 user_locked_fields = 6; +} + message PowerProfileProto { optional double cpu_suspend = 1; @@ -5494,6 +6565,16 @@ message ContentCaptureServiceEvents { SET_WHITELIST = 3; SET_DISABLED = 4; ON_USER_DATA_REMOVED = 5; + ON_DATA_SHARE_REQUEST = 6; + ACCEPT_DATA_SHARE_REQUEST = 7; + REJECT_DATA_SHARE_REQUEST = 8; + DATA_SHARE_WRITE_FINISHED = 9; + DATA_SHARE_ERROR_IOEXCEPTION = 10; + DATA_SHARE_ERROR_EMPTY_DATA = 11; + DATA_SHARE_ERROR_CLIENT_PIPE_FAIL = 12; + DATA_SHARE_ERROR_SERVICE_PIPE_FAIL = 13; + DATA_SHARE_ERROR_CONCURRENT_REQUEST = 14; + DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 15; } optional Event event = 1; // component/package of content capture service. @@ -5785,6 +6866,15 @@ message DocsUIPickResultReported { optional int32 repeatedly_pick_times = 7; } +/** Logs the drag and drop of files. + + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUIDragAndDropReported { + optional bool drag_initiated_from_docsui = 1; +} + /** * Logs when an app's memory is compacted. * @@ -6431,10 +7521,10 @@ message PermissionGrantRequestResultReported { optional int64 request_id = 1; // UID of package requesting the permission grant - optional int32 requesting_uid = 2 [(is_uid) = true]; + optional int32 uid = 2 [(is_uid) = true]; // Name of package requesting the permission grant - optional string requesting_package_name = 3; + optional string package_name = 3; // The permission to be granted optional string permission_name = 4; @@ -6462,6 +7552,20 @@ message PermissionGrantRequestResultReported { AUTO_DENIED = 8; // permission request was ignored because permission is restricted IGNORED_RESTRICTED_PERMISSION = 9; + // one time permission was granted by user action + USER_GRANTED_ONE_TIME = 10; + // user ignored request by leaving the request screen without choosing any option + USER_IGNORED = 11; + // user granted the permission after being linked to settings + USER_GRANTED_IN_SETTINGS = 12; + // user denied the permission after being linked to settings + USER_DENIED_IN_SETTINGS = 13; + // user denied the permission with prejudice after being linked to settings + USER_DENIED_WITH_PREJUDICE_IN_SETTINGS = 14; + // permission was automatically revoked after one-time permission expired + AUTO_ONE_TIME_PERMISSION_REVOKED = 15; + // permission was automatically revoked for unused app + AUTO_UNUSED_APP_PERMISSION_REVOKED = 16; } // The result of the permission grant optional Result result = 6; @@ -6922,8 +8026,58 @@ message MediametricsNuPlayerReported { } /** - * State of a dangerous permission requested by a package + * Track Legacy DRM usage + * Logged from + * frameworks/av/drm/drmserver/DrmManager.cpp */ +message MediametricsDrmManagerReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + optional int64 media_apex_version = 4; + + enum Method { + METHOD_NOT_FOUND = -1; + GET_CONSTRAINTS = 0; + GET_METADATA = 1; + CAN_HANDLE = 2; + PROCESS_DRM_INFO = 3; + ACQUIRE_DRM_INFO = 4; + SAVE_RIGHTS = 5; + GET_ORIGINAL_MIME_TYPE = 6; + GET_DRM_OBJECT_TYPE = 7; + CHECK_RIGHTS_STATUS = 8; + REMOVE_RIGHTS = 9; + REMOVE_ALL_RIGHTS = 10; + OPEN_CONVERT_SESSION = 11; + OPEN_DECRYPT_SESSION = 12; + } + + // plugin_id+description inform which Legacy DRM plugins are still in use on device + optional string plugin_id = 5; + optional string description = 6; + optional Method method = 7; + optional string mime_types = 8; + + optional int64 get_constraints_count = 9; + optional int64 get_metadata_count = 10; + optional int64 can_handle_count = 11; + optional int64 process_drm_info_count = 12; + optional int64 acquire_drm_info_count = 13; + optional int64 save_rights_count = 14; + optional int64 get_original_mime_type_count = 15; + optional int64 get_drm_object_type_count = 16; + optional int64 check_rights_status_count = 17; + optional int64 remove_rights_count = 18; + optional int64 remove_all_rights_count = 19; + optional int64 open_convert_session_count = 20; + optional int64 open_decrypt_session_count = 21; +} + +/** + * State of a dangerous permission requested by a package + * Pulled from: StatsCompanionService +*/ message DangerousPermissionState { // Name of the permission optional string permission_name = 1; @@ -6955,7 +8109,8 @@ message DeviceIdentifierAccessDenied { optional string method_name = 2; // True if the package is preinstalled. - optional bool is_preinstalled = 3; + // Starting from Android 11, this boolean is not set and will always be false. + optional bool is_preinstalled = 3 [deprecated = true]; // True if the package is privileged. // Starting from Android 11, this boolean is not set and will always be false. @@ -7003,6 +8158,7 @@ message TrainInfo { INSTALL_FAILURE_DOWNLOAD = 23; INSTALL_FAILURE_STATE_MISMATCH = 24; INSTALL_FAILURE_COMMIT = 25; + REBOOT_TRIGGERED = 26; } optional Status status = 4; } @@ -7131,6 +8287,12 @@ message GpuStatsAppInfo { // CPU Vulkan implementation is in use. optional bool cpu_vulkan_in_use = 6; + + // App is not doing pre-rotation correctly. + optional bool false_prerotation = 7; + + // App creates GLESv1 context. + optional bool gles_1_in_use = 8; } /* @@ -7139,11 +8301,27 @@ message GpuStatsAppInfo { * Pulled from StatsCompanionService. */ message SystemIonHeapSize { + // Deprecated due to limited support of ion stats in debugfs. + // Use `IonHeapSize` instead. + option deprecated = true; + // Size of the system ion heap in bytes. + // Read from debugfs. optional int64 size_in_bytes = 1; } /* + * Logs the total size of the ion heap. + * + * Pulled from StatsCompanionService. + */ +message IonHeapSize { + // Total size of all ion heaps in kilobytes. + // Read from: /sys/kernel/ion/total_heaps_kb. + optional int32 total_size_kb = 1; +} + +/* * Logs the per-process size of the system ion heap. * * Pulled from StatsCompanionService. @@ -7234,8 +8412,14 @@ message CoolingDevice { * Logged from the Intelligence mainline module. */ message IntelligenceEventReported { + // The event type. optional android.stats.intelligence.EventType event_id = 1; + // Success, failure. optional android.stats.intelligence.Status status = 2; + // How many times the event occured (to report a batch of high frequency events). + optional int32 count = 3; + // How long the event took (sum of durations if count > 1) + optional int64 duration_millis = 4; } /** @@ -7258,6 +8442,245 @@ message CarPowerStateChanged { } /** + * Logs when Car User Hal is requested to switch/create/remove user. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalModifyUserRequestReported { + // Request id for the request. + optional int32 request_id = 1; + // Request type. + enum RequestType { + UNKNOWN = 0; + // Car user manager requested user switch. + SWITCH_REQUEST_ANDROID = 1; + // OEM requested User switch. + SWITCH_REQUEST_OEM = 2; + // Hal switch requested after android switch using activity manager. + SWITCH_REQUEST_LEGACY = 3; + // Create User + CREATE_REQUEST = 4; + // Remove User + REMOVE_REQUEST = 5; + } + optional RequestType request_type = 2; + // Android User id of the current user which can only be 0, 10, 11 and so on. + // -1 if not available. + optional int32 user_id = 3; + // VHAL flags of the current user. (-1 if not available) + optional int32 user_flags = 4; + // Android User id of the target user for switch/create/remove. It can only + // be 0, 10, 11 and so on. -1 if not available. + optional int32 target_user_id = 5; + // VHAL flags of the target user for switch/create/remove. (-1 if not available) + optional int32 target_user_flags = 6; + // Request timeout Milliseconds (-1 if not available) + optional int32 timeout_millis = 7; +} + +/** + * Logs when Car User Hal responds to switch/create user request. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalModifyUserResponseReported { + // Request id of the request associated with the response. + optional int32 request_id = 1; + // Car user hal callback status. + enum CallbackStatus { + UNKNOWN = 0; + // Hal response was invalid. + INVALID = 1; + // Hal response was ok. + OK = 2; + // Hal timeout during set call. + HAL_SET_TIMEOUT = 3; + // Hal response timeout. + HAL_RESPONSE_TIMEOUT = 4; + // Hal responded with wrong info. + WRONG_HAL_RESPONSE = 5; + // Hal is processing multiple requests simultaneously. + CONCURRENT_OPERATION = 6; + } + optional CallbackStatus callback_status = 2; + + // Hal request status for user switch/create/remove. + enum HalRequestStatus { + UNSPECIFIED = 0; + // Hal request for user switch/create is successful. + SUCCESS = 1; + // Hal request for user switch/create failed. + FAILURE = 2; + } + optional HalRequestStatus request_status = 3; +} + +/** + * Logs when post switch response is posted to Car User Hal. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalPostSwitchResponseReported { + // Request id. + optional int32 request_id = 1; + + // Android user switch status. + enum UserSwitchStatus { + UNKNOWN = 0; + // Android user switch is successful. + SUCCESS = 1; + // Android user switch failed. + FAILURE = 2; + } + optional UserSwitchStatus switch_status = 2; +} + +/** + * Logs when initial user information is requested from Car User Hal. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalInitialUserInfoRequestReported { + // Request id for the request. + optional int32 request_id = 1; + + // Request type for initial user information. + enum InitialUserInfoRequestType { + UNKNOWN = 0; + // At the first time Android was booted (or after a factory reset). + FIRST_BOOT = 1; + // At the first time Android was booted after the system was updated. + FIRST_BOOT_AFTER_OTA = 2; + // When Android was booted "from scratch". + COLD_BOOT = 3; + // When Android was resumed after the system was suspended to memory. + RESUME = 4; + } + optional InitialUserInfoRequestType request_type = 2; + // Request timeout Milliseconds (-1 if not available) + optional int32 timeout_millis = 3; +} + +/** + * Logs when Car User Hal responds to initial user information requests. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalInitialUserInfoResponseReported { + // Request id of the request associated with the response. + optional int32 request_id = 1; + // Car user hal callback status. + enum CallbackStatus { + UNKNOWN = 0; + // Hal response was invalid. + INVALID = 1; + // Hal response was ok. + OK = 2; + // Hal timeout during set call. + HAL_SET_TIMEOUT = 3; + // Hal response timeout. + HAL_RESPONSE_TIMEOUT = 4; + // Hal responded with wrong info. + WRONG_HAL_RESPONSE = 5; + // Hal is processing multiple requests simultaneously. + CONCURRENT_OPERATION = 6; + } + optional CallbackStatus callback_status = 2; + // Response for initial user information request. + enum InitialUserInfoResponseAction { + UNSPECIFIED = 0; + // Let the Android System decide what to do. + DEFAULT = 1; + // Switch to an existing Android user. + SWITCH = 2; + // Create a new Android user (and switch to it). + CREATE = 3; + } + optional InitialUserInfoResponseAction response_action = 3; + // Android User id of the target user which can only be 0, 10, 11 and so on. + // -1 if not available. + optional int32 target_user = 4; + // VHAL flags of the current user. (-1 if not available) + optional int32 target_user_flags = 5; + // User locales + optional string user_locales = 6; +} + +/** + * Logs when set user association is requested from Car User Hal. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalUserAssociationRequestReported { + // Request id for the request. + optional int32 request_id = 1; + // Request type. + enum RequestType { + UNKNOWN = 0; + // For setting user association information. + SET = 1; + // For getting user association information. + GET = 2; + } + optional RequestType request_type = 2; + // Android User id of the current user which can only be 0, 10, 11 and so on. + // -1 if not available. + optional int32 current_user_id = 3; + // VHAL flags of the current user. (-1 if not available) + optional int32 current_user_flags = 4; + // Number of the set associations requested. + optional int32 number_associations = 5; + // Concatenated string for the types from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_types = 6; + // Concatenated string for the values from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_values = 7; +} + +/** + * Logs when Car User Hal responds to set user association requests. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalSetUserAssociationResponseReported { + // Request id of the request associated with the response. + optional int32 request_id = 1; + // Car user hal callback status. + enum CallbackStatus { + UNKNOWN = 0; + // Hal response was invalid. + INVALID = 1; + // Hal response was ok. + OK = 2; + // Hal timeout during set call. + HAL_SET_TIMEOUT = 3; + // Hal response timeout. + HAL_RESPONSE_TIMEOUT = 4; + // Hal responded with wrong info. + WRONG_HAL_RESPONSE = 5; + // Hal is processing multiple requests simultaneously. + CONCURRENT_OPERATION = 6; + } + optional CallbackStatus callback_status = 2; + // Number of the set associations in the response. + optional int32 number_associations = 3; + // Concatenated string for the types from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_types = 4; + // Concatenated string for the values from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_values = 5; +} + +/** * Logs whether GarageMode is entered. * * Logged from: @@ -7275,11 +8698,11 @@ message AppOps { // Uid of the package requesting the op optional int32 uid = 1 [(is_uid) = true]; - // Nmae of the package performing the op + // Name of the package performing the op optional string package_name = 2; - // operation id; maps to the OP_* constants in AppOpsManager.java - optional int32 op_id = 3; + // operation id + optional android.app.AppOpEnum op_id = 3 [default = APP_OP_NONE]; // The number of times the op was granted while the app was in the // foreground (only for trusted requests) @@ -7304,6 +8727,58 @@ message AppOps { // For long-running operations, total duration of the operation // while the app was in the background (only for trusted requests) optional int64 trusted_background_duration_millis = 9; + + // Whether AppOps is guarded by Runtime permission + optional bool is_runtime_permission = 10; +} + +/** + * Historical app ops data per package and attribution tag. + */ +message AttributedAppOps { + // Uid of the package requesting the op + optional int32 uid = 1 [(is_uid) = true]; + + // Name of the package performing the op + optional string package_name = 2; + + // tag; provided by developer when accessing related API, limited at 50 chars by API. + // Attributions must be provided through manifest using <attribution> tag available in R and + // above. + optional string tag = 3; + + // operation id + optional android.app.AppOpEnum op = 4 [default = APP_OP_NONE]; + + // The number of times the op was granted while the app was in the + // foreground (only for trusted requests) + optional int64 trusted_foreground_granted_count = 5; + + // The number of times the op was granted while the app was in the + // background (only for trusted requests) + optional int64 trusted_background_granted_count = 6; + + // The number of times the op was rejected while the app was in the + // foreground (only for trusted requests) + optional int64 trusted_foreground_rejected_count = 7; + + // The number of times the op was rejected while the app was in the + // background (only for trusted requests) + optional int64 trusted_background_rejected_count = 8; + + // For long-running operations, total duration of the operation + // while the app was in the foreground (only for trusted requests) + optional int64 trusted_foreground_duration_millis = 9; + + // For long-running operations, total duration of the operation + // while the app was in the background (only for trusted requests) + optional int64 trusted_background_duration_millis = 10; + + // Whether AppOps is guarded by Runtime permission + optional bool is_runtime_permission = 11; + + // Sampling rate used on device, from 0 to 100 + optional int32 sampling_rate = 12; } /** @@ -7418,6 +8893,9 @@ message GrantPermissionsActivityButtonActions { // Button clicked by user - same as bit flags in buttons_presented with only single bit set optional int32 button_clicked = 5; + + // id which identifies single session of user interacting with permission controller + optional int64 session_id = 6; } /** @@ -7470,6 +8948,28 @@ message AppPermissionFragmentActionReported { // The result of the permission grant optional bool permission_granted = 6; + + // State of Permission Flags after grant as per android.content.pm.PermissionFlags + optional int32 permission_flags = 7; + + enum Button { + UNDEFINED = 0; + // Allow button + ALLOW = 1; + // Deny button + DENY = 2; + // Ask every time button + ASK_EVERY_TIME = 3; + // Allow all the time button + ALLOW_ALWAYS = 4; + // Allow only while using the app button + ALLOW_FOREGROUND = 5; + // Same is Deny button but shown in while in use dialog + DENY_FOREGROUND = 6; + } + + // Button pressed in the dialog + optional Button button_pressed = 8; } /** @@ -7490,7 +8990,8 @@ message AppPermissionFragmentViewed { } /** -* Information about a AppPermissionsFragment viewed by user +* Information about a AppPermissionGroupsFragment viewed by user. Fragment has been renamed, but +* the log retains the old fragment name. */ message AppPermissionsFragmentViewed { // id which identifies single session of user interacting with permission controller @@ -7518,7 +9019,6 @@ message AppPermissionsFragmentViewed { } optional Category category = 6; } - /** * Information about a PermissionAppsFragment viewed by user. * Logged from ui/handheld/PermissionAppsFragment.java @@ -7551,6 +9051,478 @@ message PermissionAppsFragmentViewed { } /** +* Log that the Auto Revoke notification has been clicked +* Logged from ui/ManagePermissionsActivity +*/ +message AutoRevokeNotificationClicked { + // id which identifies single session of user interacting with permission controller + optional int64 session_id = 1; +} + +/** +* Log that an app has been displayed on the auto revoke page, and lists one permission that was +* auto revoked for it. +* Logged from ui/handheld/AutoRevokeFragment +*/ +message AutoRevokeFragmentAppViewed { + // id which identifies single session of user interacting with permission controller + optional int64 session_id = 1; + + // UID of package for which permissions are viewed + optional int32 uid = 2 [(is_uid) = true]; + + // Name of package for which permissions are viewed + optional string package_name = 3; + + // The name of a permission group that has been revoked + optional string permission_group_name = 4; + + // The age of the app- more than three months old, or more than six months + enum Age { + UNDEFINED = 0; + NEWER_BUCKET = 1; + OLDER_BUCKET = 2; + } + + // How long the app has been unused. Currently, newer bucket is 3 months, older is 6 months + optional Age age = 5; +} + +/** +* Log that the user has interacted with an app on the auto revoke fragment +* Logged from ui/handheld/AutoRevokeFragment +*/ +message AutoRevokedAppInteraction { + // id which identifies single session of user interacting with permission controller + optional int64 session_id = 1; + + // UID of package for which permissions are viewed + optional int32 uid = 2 [(is_uid) = true]; + + // Name of package for which permissions are viewed + optional string package_name = 3; + + enum Action { + UNDEFINED = 0; + REMOVE = 1; + OPEN = 2; + APP_INFO = 3; + PERMISSIONS = 4; + REMOVE_IN_SETTINGS = 5; + OPEN_IN_SETTINGS = 6; + } + + // The action the user took to interact with the app + optional Action action = 4; +} + +/** +* Log that the AppPermissionGroupsFragment has been interacted with for the possible purposes of +* auto revoke, or that the auto revoke switch has been changed +* Logged from ui/handheld/AppPermissionGroupsFragment + */ +message AppPermissionGroupsFragmentAutoRevokeAction { + // id which identifies single session of user interacting with permission controller + optional int64 session_id = 1; + + // UID of package for which permissions are viewed + optional int32 uid = 2 [(is_uid) = true]; + + // Name of package for which permissions are viewed + optional string package_name = 3; + + enum Action { + UNDEFINED = 0; + OPENED_FOR_AUTO_REVOKE = 1; + OPENED_FROM_INTENT = 2; + SWITCH_ENABLED = 3; + SWITCH_DISABLED = 4; + } + + // The action the user took to interact with the fragment + optional Action action = 4; +} + +/** + * Logs when there is a smart selection related event. + * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java + * Logged from: TextClassifierEventLogger.java + */ +message TextSelectionEvent { + // A session ID. + optional string session_id = 1; + + // Event type of this event. + optional android.stats.textclassifier.EventType event_type = 2; + + // Name of the annotator model that is involved in this event. + optional string model_name = 3; + + // Type of widget that was involved in triggering this event. + optional android.stats.textclassifier.WidgetType widget_type = 4; + + // Index of this event in a session. + optional int32 event_index = 5; + + // Entity type that is involved. + optional string entity_type = 6; + + // Relative word index of the start of the selection. + optional int32 relative_word_start_index = 7; + + // Relative word (exclusive) index of the end of the selection. + optional int32 relative_word_end_index = 8; + + // Relative word index of the start of the smart selection. + optional int32 relative_suggested_word_start_index = 9; + + // Relative word (exclusive) index of the end of the smart selection. + optional int32 relative_suggested_word_end_index = 10; + + // Name of source package. + optional string package_name = 11; + + // Name of the LangID model that is involved in this event. + optional string langid_model_name = 12; +} + +/** + * Logs when there is a smart linkify related event. + * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java + * Logged from: TextClassifierEventLogger.java + */ +message TextLinkifyEvent { + // A session ID. + optional string session_id = 1; + + // Event type of this event. + optional android.stats.textclassifier.EventType event_type = 2; + + // Name of the annotator model that is involved in this event. + optional string model_name = 3; + + // Type of widget that was involved in triggering this event. + optional android.stats.textclassifier.WidgetType widget_type = 4; + + // Index of this event in a session. + optional int32 event_index = 5; + + // Entity type that is involved. + optional string entity_type = 6; + + // Number of links detected. + optional int32 num_links = 7; + + // The total length of all links. + optional int32 linked_text_length = 8; + + // Length of input text. + optional int32 text_length = 9; + + // Time spent on generating links in ms. + optional int64 latency_millis = 10; + + // Name of source package. + optional string package_name = 11; + + // Name of the LangID model that is involved in this event. + optional string langid_model_name = 12; +} + +/** + * Logs when there is a conversation actions related event. + * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java + * Logged from: TextClassifierEventLogger.java + */ +message ConversationActionsEvent { + // A session ID. + optional string session_id = 1; + + // Event type of this event. + optional android.stats.textclassifier.EventType event_type = 2; + + // Name of the actions model that is involved in this event. + optional string model_name = 3; + + // Type of widget that was involved in triggering this event. + optional android.stats.textclassifier.WidgetType widget_type = 4; + + // The first entity type that is involved. + optional string first_entity_type = 5; + + // The second entity type that is involved. + optional string second_entity_type = 6; + + // The third entity type that is involved. + optional string third_entity_type = 7; + + // The score of the first entity type. + optional float score = 8; + + // Name of source package. + optional string package_name = 9; + + // Name of the annotator model that is involved in this event. + optional string annotator_model_name = 10; + + // Name of the LangID model that is involved in this event. + optional string langid_model_name = 11; +} + +/** + * Logs when there is a language detection related event. + * See frameworks/base/core/java/android/view/textclassifier/TextClassifierEvent.java + * Logged from: TextClassifierEventLogger.java + */ +message LanguageDetectionEvent { + // A session ID. + optional string session_id = 1; + + // Event type of this event. + optional android.stats.textclassifier.EventType event_type = 2; + + // Name of the language detection model that is involved in this event. + optional string model_name = 3; + + // Type of widget that was involved in triggering this event. + optional android.stats.textclassifier.WidgetType widget_type = 4; + + // Detected language. + optional string language_tag = 5; + + // Score of the detected language. + optional float score = 6; + + // Position of this action. + optional int32 action_index = 7; + + // Name of source package. + optional string package_name = 8; +} + +/** + * Information about an OTA update attempt by update_engine. + * Logged from platform/system/update_engine/metrics_reporter_android.cc + */ +message UpdateEngineUpdateAttemptReported { + // The number of attempts for the update engine to apply a given payload. + optional int32 attempt_number = 1; + + optional android.stats.otaupdate.PayloadType payload_type = 2; + + // The total time in minutes for the update engine to apply a given payload. + // The time is calculated by calling clock_gettime() / CLOCK_BOOTTIME; and + // it's increased when the system is sleeping. + optional int32 duration_boottime_in_minutes = 3; + + // The total time in minutes for the update engine to apply a given payload. + // The time is calculated by calling clock_gettime() / CLOCK_MONOTONIC_RAW; + // and it's not increased when the system is sleeping. + optional int32 duration_monotonic_in_minutes = 4; + + // The size of the payload in MiBs. + optional int32 payload_size_mib = 5; + + // The attempt result reported by the update engine for an OTA update. + optional android.stats.otaupdate.AttemptResult attempt_result = 6; + + // The error code reported by the update engine after an OTA update attempt + // on A/B devices. + optional android.stats.otaupdate.ErrorCode error_code = 7; + + // The build fingerprint of the source system. The value is read from a + // system property when the device takes the update. e.g. + // Android/aosp_sailfish/sailfish:10/QP1A.190425.004/5507117:userdebug/test-keys + optional string source_fingerprint = 8; + + // Size of super partition. + optional int64 super_partition_size_bytes = 9; + + // Size of current slot within the super partition. + optional int64 slot_size_bytes = 10; + + // Free space available in the super partition. + optional int64 super_free_space_bytes = 11; +} + +/** + * Information about all the attempts the device make before finishing the + * successful update. + * Logged from platform/system/update_engine/metrics_reporter_android.cc + */ +message UpdateEngineSuccessfulUpdateReported { + // The number of attempts for the update engine to apply the payload for a + // successful update. + optional int32 attempt_count = 1; + + optional android.stats.otaupdate.PayloadType payload_type = 2; + + optional int32 payload_size_mib = 3; + + // The total number of bytes downloaded by update_engine since the last + // successful update. + optional int32 total_bytes_downloaded_mib = 4; + + // The ratio in percentage of the over-downloaded bytes compared to the + // total bytes needed to successfully install the update. e.g. 200 if we + // download 200MiB in total for a 100MiB package. + optional int32 download_overhead_percentage = 5; + + // The total time in minutes for the update engine to apply the payload for a + // successful update. + optional int32 total_duration_minutes = 6; + + // The number of reboot of the device during a successful update. + optional int32 reboot_count = 7; +} + +/** + * Reported when the RebootEscrow HAL has attempted to recover the escrowed + * key to indicate whether it was successful or not. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java + */ +message RebootEscrowRecoveryReported { + optional bool successful = 1; +} + +/** + * Global display pipeline metrics reported by SurfaceFlinger. + * Pulled from: + * frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp + */ +message SurfaceflingerStatsGlobalInfo { + // Total number of frames presented during the tracing period + optional int64 total_frames = 1; + // Total number of frames missed + optional int64 missed_frames = 2; + // Total number of frames that fell back to client composition + optional int64 client_composition_frames = 3; + // Total time the display was turned on + optional int64 display_on_millis = 4; + // Total time that was spent performing animations. + // This is derived from the present-to-present layer histogram + optional int64 animation_millis = 5; + // Total number of event connections tracked by SurfaceFlinger at the time + // of this pull. If this number grows prohibitively large, then this can + // cause jank due to resource contention. + optional int32 event_connection_count = 6; + // Set of timings measured from when SurfaceFlinger began compositing a + // frame, until the frame was requested to be presented to the display. This + // measures SurfaceFlinger's total CPU walltime on the critical path per + // frame. + optional FrameTimingHistogram frame_duration = 7 + [(android.os.statsd.log_mode) = MODE_BYTES]; + // Set of timings measured from when SurfaceFlinger first began using the + // GPU to composite a frame, until the GPU has finished compositing that + // frame. This measures the total additional time SurfaceFlinger needed to + // perform due to falling back into GPU composition. + optional FrameTimingHistogram render_engine_timing = 8 + [(android.os.statsd.log_mode) = MODE_BYTES]; +} + +/** + * Per-layer display pipeline metrics reported by SurfaceFlinger. + * The number of layers uploaded will be restricted due to size limitations. + * Pulled from: + * frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp + */ +message SurfaceflingerStatsLayerInfo { + // The layer for this set of metrics + // For now we can infer that the package name is included in the layer + // name. + optional string layer_name = 1; + // Total number of frames presented + optional int64 total_frames = 2; + // Total number of dropped frames while latching a buffer for this layer. + optional int64 dropped_frames = 3; + // Set of timings measured between successive presentation timestamps. + optional FrameTimingHistogram present_to_present = 4 + [(android.os.statsd.log_mode) = MODE_BYTES]; + // Set of timings measured from when an app queued a buffer for + // presentation, until the buffer was actually presented to the + // display. + optional FrameTimingHistogram post_to_present = 5 + [(android.os.statsd.log_mode) = MODE_BYTES]; + // Set of timings measured from when a buffer is ready to be presented, + // until the buffer was actually presented to the display. + optional FrameTimingHistogram acquire_to_present = 6 + [(android.os.statsd.log_mode) = MODE_BYTES]; + // Set of timings measured from when a buffer was latched by + // SurfaceFlinger, until the buffer was presented to the display + optional FrameTimingHistogram latch_to_present = 7 + [(android.os.statsd.log_mode) = MODE_BYTES]; + // Set of timings measured from the desired presentation to the actual + // presentation time + optional FrameTimingHistogram desired_to_present = 8 + [(android.os.statsd.log_mode) = MODE_BYTES]; + // Set of timings measured from when an app queued a buffer for + // presentation, until the buffer was ready to be presented. + optional FrameTimingHistogram post_to_acquire = 9 + [(android.os.statsd.log_mode) = MODE_BYTES]; + // Frames missed latch because the acquire fence didn't fire + optional int64 late_acquire_frames = 10; + // Frames latched early because the desired present time was bad + optional int64 bad_desired_present_frames = 11; +} + +/** + * Histogram of frame counts bucketed by time in milliseconds. + * Because of size limitations, we hard-cap the number of buckets, with + * buckets for corresponding to larger milliseconds being less precise. + */ +message FrameTimingHistogram { + // Timings in milliseconds that describes a set of histogram buckets + repeated int32 time_millis_buckets = 1; + // Number of frames that match to each time_millis, i.e. the bucket + // contents + // It's required that len(time_millis) == len(frame_count) + repeated int64 frame_counts = 2; +} + +/** + * Janky event as reported by SurfaceFlinger. + * This event is intended to be consumed by a Perfetto subscriber for + * automated trace collection. + * + * Logged from: + * frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp + */ +message DisplayJankReported { + // Informational field for how long the janky event lasted in milliseconds + optional int64 event_duration_millis = 1; + // Number of frame deadlines missed, where SurfaceFlinger failed to update + // the display on time. + optional int32 present_deadlines_missed = 2; +} + +/** + * Information about camera facing and API level usage. + * Logged from: + * frameworks/base/services/core/java/com/android/server/camera/CameraServiceProxy.java + */ +message CameraActionEvent { + // Camera session duration + optional int64 duration_millis = 1; + + // Camera API level used + optional int32 api_level = 2; + + // Name of client package + optional string package_name = 3; + + // Camera facing + enum Facing { + UNKNOWN = 0; + BACK = 1; + FRONT = 2; + EXTERNAL = 3; + } + optional Facing facing = 4; +} + +/** * Logs when a compatibility change is affecting an app. * * Logged from: @@ -7583,6 +9555,7 @@ message AppCompatibilityChangeReported { // Where it was logged from. optional Source source = 4; + } /** @@ -7649,70 +9622,123 @@ message VmsClientStats { } /** - * Information about an OTA update attempt by update_engine. - * Logged from platform/system/update_engine/metrics_reporter_android.cc + * State of a dangerous permission requested by a package - sampled + * Pulled from: StatsCompanionService.java with data obtained from PackageManager API +*/ +message DangerousPermissionStateSampled { + // Name of the permission + optional string permission_name = 1; + + // Uid of the package + optional int32 uid = 2 [(is_uid) = true]; + + // If the permission is granted to the uid + optional bool is_granted = 3; + + // Permission flags as per android.content.pm.PermissionFlags + optional int32 permission_flags = 4; +} + +/** + * HWUI stats for a given app. */ -message UpdateEngineUpdateAttemptReported { - // The number of attempts for the update engine to apply a given payload. - optional int32 attempt_number = 1; +message GraphicsStats { + // The package name of the app + optional string package_name = 1; - optional android.stats.otaupdate.PayloadType payload_type = 2; + // The version code of the app + optional int64 version_code = 2; - // The total time in minutes for the update engine to apply a given payload. - // The time is calculated by calling clock_gettime() / CLOCK_BOOTTIME; and - // it's increased when the system is sleeping. - optional int32 duration_boottime_in_minutes = 3; + // The start & end timestamps in UTC as + // milliseconds since January 1, 1970 + // Compatible with java.util.Date#setTime() + optional int64 start_millis = 3; - // The total time in minutes for the update engine to apply a given payload. - // The time is calculated by calling clock_gettime() / CLOCK_MONOTONIC_RAW; - // and it's not increased when the system is sleeping. - optional int32 duration_monotonic_in_minutes = 4; + optional int64 end_millis = 4; - // The size of the payload in MiBs. - optional int32 payload_size_mib = 5; + // HWUI renders pipeline type: GL (1) or Vulkan (2). + enum PipelineType { + UNKNOWN = 0; + GL = 1; + VULKAN = 2; + } - // The attempt result reported by the update engine for an OTA update. - optional android.stats.otaupdate.AttemptResult attempt_result = 6; + // HWUI renders pipeline type: GL or Vulkan. + optional PipelineType pipeline = 5; - // The error code reported by the update engine after an OTA update attempt - // on A/B devices. - optional android.stats.otaupdate.ErrorCode error_code = 7; + // Distinct frame count. + optional int32 total_frames = 6; - // The build fingerprint of the source system. The value is read from a - // system property when the device takes the update. e.g. - // Android/aosp_sailfish/sailfish:10/QP1A.190425.004/5507117:userdebug/test-keys - optional string source_fingerprint = 8; + // Number of "missed vsync" events. + optional int32 missed_vsync_count = 7; + + // Number of frames in triple-buffering scenario (high input latency) + optional int32 high_input_latency_count = 8; + + // Number of "slow UI thread" events. + optional int32 slow_ui_thread_count = 9; + + // Number of "slow bitmap upload" events. + optional int32 slow_bitmap_upload_count = 10; + + // Number of "slow draw" events. + optional int32 slow_draw_count = 11; + + // Number of frames that missed their deadline (aka, visibly janked) + optional int32 missed_deadline_count = 12; + + // The frame time histogram for the package + optional FrameTimingHistogram cpu_histogram = 13 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // The gpu frame time histogram for the package + optional FrameTimingHistogram gpu_histogram = 14 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // UI mainline module version. + optional int64 version_ui_module = 15; + + // If true, these are HWUI stats for up to a 24h period for a given app from today. + // If false, these are HWUI stats for a 24h period for a given app from the last complete + // day (yesterday). Stats from yesterday stay constant, while stats from today may change as + // more apps are running / rendering. + optional bool is_today = 16; } /** - * Information about all the attempts the device make before finishing the - * successful update. - * Logged from platform/system/update_engine/metrics_reporter_android.cc + * Message related to dangerous (runtime) app ops access */ -message UpdateEngineSuccessfulUpdateReported { - // The number of attempts for the update engine to apply the payload for a - // successful update. - optional int32 attempt_count = 1; +message RuntimeAppOpAccess { + // Uid of the package accessing app op + optional int32 uid = 1 [(is_uid) = true]; - optional android.stats.otaupdate.PayloadType payload_type = 2; + // Name of the package accessing app op + optional string package_name = 2; - optional int32 payload_size_mib = 3; + // deprecated - set to empty string + optional string op_deprecated = 3 [deprecated = true]; - // The total number of bytes downloaded by update_engine since the last - // successful update. - optional int32 total_bytes_downloaded_mib = 4; + // attribution_tag; provided by developer when accessing related API, limited at 50 chars by + // API. Attributions must be provided through manifest using <attribution> tag available in R + // and above. + optional string attribution_tag = 4; - // The ratio in percentage of the over-downloaded bytes compared to the - // total bytes needed to successfully install the update. e.g. 200 if we - // download 200MiB in total for a 100MiB package. - optional int32 download_overhead_percentage = 5; + // message related to app op access, limited to 600 chars by API + optional string message = 5; - // The total time in minutes for the update engine to apply the payload for a - // successful update. - optional int32 total_duration_minutes = 6; + enum SamplingStrategy { + DEFAULT = 0; + UNIFORM = 1; + RARELY_USED = 2; + BOOT_TIME_SAMPLING = 3; + UNIFORM_OPS = 4; + } - // The number of reboot of the device during a successful update. - optional int32 reboot_count = 7; + // sampling strategy used to collect this message + optional SamplingStrategy sampling_strategy = 6; + + // operation id + optional android.app.AppOpEnum op = 7 [default = APP_OP_NONE]; } /* @@ -7755,6 +9781,274 @@ message UserspaceRebootReported { optional UserEncryptionState user_encryption_state = 3; } +/* + * Logs integrity check information during each install. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java + */ +message IntegrityCheckResultReported { + optional string package_name = 1; + optional string app_certificate_hash = 2; + optional int64 version_code = 3; + optional string installer_package_name = 4; + enum Response { + UNKNOWN = 0; + ALLOWED = 1; + REJECTED = 2; + FORCE_ALLOWED = 3; + } + optional Response response = 5; + // An estimate on the cause of the response. This will only be populated for + // REJECTED and FORCE_ALLOWED + optional bool caused_by_app_cert_rule = 6; + optional bool caused_by_installer_rule = 7; +} + +/** + * Logs the information about the rules and the provider whenever rules are + * pushed into AppIntegrityManager. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java + */ +message IntegrityRulesPushed { + optional bool success = 1; + // Package name of the app that pushed the rules. + optional string rule_provider = 2; + // Version string of arbitrary format provided by the rule provider to + // identify the rules. + optional string rule_version = 3; +} + +/** + * Logs when a cell broadcast message is received on the device. + * + * Logged from Cell Broadcast module and platform: + * packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/ + * packages/apps/CellBroadcastReceiver/ + * frameworks/opt/telephony/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java + */ +message CellBroadcastMessageReported { + // The type of Cell Broadcast message + enum CbType { + UNKNOWN_TYPE = 0; + GSM = 1; + CDMA = 2; + CDMA_SPC = 3; + } + + // The parts of the cell broadcast message pipeline + enum ReportSource { + UNKNOWN_SOURCE = 0; + FRAMEWORK = 1; + CB_SERVICE = 2; + CB_RECEIVER_APP = 3; + } + + // GSM, CDMA, CDMA-SCP + optional CbType type = 1; + + // The source of the report + optional ReportSource source = 2; +} + +/** + * Logs when a cell broadcast message is filtered out, or otherwise intentionally not sent to CBR. + * + * Logged from CellBroadcastService module: + * packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/ + */ +message CellBroadcastMessageFiltered { + enum FilterReason { + NOT_FILTERED = 0; + DUPLICATE_MESSAGE = 1; + GEOFENCED_MESSAGE = 2; + AREA_INFO_MESSAGE = 3; + } + + // GSM, CDMA, CDMA-SCP + optional CellBroadcastMessageReported.CbType type = 1; + + // The source of the report + optional FilterReason filter = 2; +} + +/** + * Logs when an error occurs while handling a cell broadcast message; + * + * Logged from CellBroadcastService module: + * packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/ + */ +message CellBroadcastMessageError { + // The type of error raised when trying to handle a cell broadcast message + enum ErrorType { + UNKNOWN_TYPE = 0; + CDMA_DECODING_ERROR = 1; + CDMA_SCP_EMPTY = 2; + CDMA_SCP_HANDLING_ERROR = 3; + GSM_INVALID_HEADER_LENGTH = 4; + GSM_UNSUPPORTED_HEADER_MESSAGE_TYPE = 5; + GSM_UNSUPPORTED_HEADER_DATA_CODING_SCHEME = 6; + GSM_INVALID_PDU = 7; + GSM_INVALID_GEO_FENCING_DATA = 8; + GSM_UMTS_INVALID_WAC = 9; + FAILED_TO_INSERT_TO_DB = 10; + UNEXPECTED_GEOMETRY_FROM_FWK = 11; + UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK = 12; + UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK = 13; + UNEXPECTED_CDMA_SCP_MESSAGE_TYPE_FROM_FWK = 14; + NO_CONNECTION_TO_CB_SERVICE = 15; + } + + // What kind of error occurred + optional ErrorType type = 1; + + // Exception message (or log message) associated with the error (max 1000 chars) + optional string exception_message = 2; +} + +/** + * Logs when a tune occurs through device's Frontend. + * This is atom ID 276. + * + * Logged from: + * frameworks/base/media/java/android/media/tv/tuner/Tuner.java + */ +message TvTunerStateChanged { + enum State { + UNKNOWN = 0; + TUNING = 1; // Signal is tuned + LOCKED = 2; // the signal is locked + NOT_LOCKED = 3; // the signal isn’t locked. + SIGNAL_LOST = 4; // the signal was locked, but is lost now. + SCANNING = 5; // the signal is scanned + SCAN_STOPPED = 6; // the scan is stopped. + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // new state + optional State state = 2; +} + +/** + * Logs the status of a dvr playback or record. + * This is atom ID 279. + * + * Logged from: + * frameworks/base/media/java/android/media/tv/tuner/dvr + */ +message TvTunerDvrStatus { + enum Type { + UNKNOWN_TYPE = 0; + PLAYBACK = 1; // is a playback + RECORD = 2; // is a record + } + enum State { + UNKNOWN_STATE = 0; + STARTED = 1; // DVR is started + STOPPED = 2; // DVR is stopped + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // DVR type + optional Type type = 2; + // DVR state + optional State state = 3; + // Identify the segment of a record or playback + optional int32 segment_id = 4; + // indicate how many overflow or underflow happened between started to stopped + optional int32 overflow_underflow_count = 5; +} + +/** + * Logs when a cas session opened through MediaCas. + * This is atom ID 280. + * + * Logged from: + * frameworks/base/media/java/android/media/MediaCas.java + */ +message TvCasSessionOpenStatus { + enum State { + UNKNOWN = 0; + SUCCEEDED = 1; // indicate that the session is opened successfully. + FAILED = 2; // indicate that the session isn’t opened successfully. + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // Cas system Id + optional int32 cas_system_id = 2; + // State of the session + optional State state = 3; +} + +/** + * Logs for ContactsProvider general usage. + * This is atom ID 301. + * + * Logged from: + * packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java + */ +message ContactsProviderStatusReported { + enum ApiType { + UNKNOWN_API = 0; + QUERY = 1; + // INSERT includes insert and bulkInsert, and inserts triggered by applyBatch. + INSERT = 2; + // UPDATE and DELETE includes update/delete and the ones triggered by applyBatch. + UPDATE = 3; + DELETE = 4; + } + + enum ResultType { + UNKNOWN_RESULT = 0; + SUCCESS = 1; + FAIL = 2; + ILLEGAL_ARGUMENT = 3; + UNSUPPORTED_OPERATION = 4; + } + + enum CallerType { + UNSPECIFIED_CALLER_TYPE = 0; + CALLER_IS_SYNC_ADAPTER = 1; + CALLER_IS_NOT_SYNC_ADAPTER = 2; + } + + optional ApiType api_type = 1; + // Defined in + // packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java + optional int32 uri_type = 2; + optional CallerType caller_type = 3; + optional ResultType result_type = 4; + optional int32 result_count = 5; + optional int64 latency_micros = 6; +} + +/** + * Logs when an app is frozen or unfrozen. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java + */ +message AppFreezeChanged { + // The type of event. + enum Action { + UNKNOWN = 0; + FREEZE_APP = 1; + UNFREEZE_APP = 2; + } + optional Action action = 1; + + // Pid of the process being frozen. + optional int32 pid = 2; + + // Name of the process being frozen. + optional string process_name = 3; + + // Time since last unfrozen. + optional int64 time_unfrozen_millis = 4; +} + /** * Pulls information for a single voice call. * @@ -7906,6 +10200,808 @@ message SupportedRadioAccessFamily { } /** + * Logs gnss stats from location service provider + * + * Pulled from: + * frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java + */ + +message GnssStats { + // Number of location reports since boot + optional int64 location_reports = 1; + + // Total pulled reports of Location failures since boot + optional int64 location_failure_reports = 2; + + // Number of time to first fix reports since boot + optional int64 time_to_first_fix_reports = 3; + + // Total pulled reported time to first fix (in milli-seconds) since boot + optional int64 time_to_first_fix_millis = 4; + + // Number of position accuracy reports since boot + optional int64 position_accuracy_reports = 5; + + // Total pulled reported position accuracy (in meters) since boot + optional int64 position_accuracy_meters = 6; + + // Number of top 4 average CN0 reports since boot + optional int64 top_four_average_cn0_reports = 7; + + // Total pulled reported of top 4 average CN0 (dB-mHz) since boot + optional int64 top_four_average_cn0_db_mhz = 8; + + // Number of l5 top 4 average CN0 reports since boot + optional int64 l5_top_four_average_cn0_reports = 9; + + // Total pulled reported of l5 top 4 average CN0 (dB-mHz) since boot + optional int64 l5_top_four_average_cn0_db_mhz = 10; + + // Total number of sv status messages reports since boot + optional int64 sv_status_reports = 11; + + // Total number of sv status messages reports, where sv is used in fix since boot + optional int64 sv_status_reports_used_in_fix = 12; + + // Total number of L5 sv status messages reports since boot + optional int64 l5_sv_status_reports = 13; + + // Total number of L5 sv status messages reports, where sv is used in fix since boot + optional int64 l5_sv_status_reports_used_in_fix = 14; +} + +/** + * Logs when an app is moved to a different standby bucket. + * + * Logged from: + * frameworks/base/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java + */ +message AppStandbyBucketChanged { + optional string package_name = 1; + + // Should be 0, 10, 11, 12, etc. where 0 is the owner. See UserHandle for more documentation. + optional int32 user_id = 2; + + // These enum values match the constants defined in UsageStatsManager.java. + enum Bucket { + BUCKET_UNKNOWN = 0; + BUCKET_EXEMPTED = 5; + BUCKET_ACTIVE = 10; + BUCKET_WORKING_SET = 20; + BUCKET_FREQUENT = 30; + BUCKET_RARE = 40; + BUCKET_RESTRICTED = 45; + BUCKET_NEVER = 50; + } + optional Bucket bucket = 3; + + enum MainReason { + MAIN_UNKNOWN = 0; + MAIN_DEFAULT = 0x0100; + MAIN_TIMEOUT = 0x0200; + MAIN_USAGE = 0x0300; + MAIN_FORCED_BY_USER = 0x0400; + MAIN_PREDICTED = 0x0500; + MAIN_FORCED_BY_SYSTEM = 0x0600; + } + optional MainReason main_reason = 4; + + // A more detailed reason for the standby bucket change. The sub reason name is dependent on + // the main reason. Values are one of the REASON_SUB_XXX constants defined in + // UsageStatsManager.java. + optional int32 sub_reason = 5; +} + +/** +* Reports a started sharesheet transaction. +* +* Logged from: +* frameworks/base/core/java/com/android/internal/app/ChooserActivity.java +*/ +message SharesheetStarted { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + // The calling app's package name. + optional string package_name = 2; + // An identifier to tie together multiple logs relating to the same share event + optional int32 instance_id = 3; + // The mime type of the share + optional string mime_type = 4; + // The number of direct targets the calling app is providing that will be shown. + optional int32 num_app_provided_direct_targets = 5; + // The number of app targets the calling app is providing that will be shown. + optional int32 num_app_provided_app_targets = 6; + // True if the share originates from the workprofile + optional bool is_workprofile = 7; + + enum SharesheetPreviewType { // Constants from ChooserActivity.java + CONTENT_PREVIEW_TYPE_UNKNOWN = 0; // Default for proto 2 / 3 compatibility. + CONTENT_PREVIEW_IMAGE = 1; // The preview shown in the sharesheet is an image. + CONTENT_PREVIEW_FILE = 2; // The preview shown in the sharesheet is a file. + CONTENT_PREVIEW_TEXT = 3; // The preview shown in the sharesheet is text. + } + // How the sharesheet preview is presented. + optional SharesheetPreviewType preview_type = 8; + + enum ResolverActivityIntent { // Intents handled by ResolverActivity.java + INTENT_DEFAULT = 0; + INTENT_ACTION_VIEW = 1; + INTENT_ACTION_EDIT = 2; + INTENT_ACTION_SEND = 3; + INTENT_ACTION_SENDTO = 4; + INTENT_ACTION_SEND_MULTIPLE = 5; + INTENT_ACTION_IMAGE_CAPTURE = 6; + INTENT_ACTION_MAIN = 7; + } + // The intent being processed (only SEND and SEND_MULTIPLE are system sharesheet) + optional ResolverActivityIntent intent_type = 9; +} + +/** + * Reports a ranking selection event. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/app/ChooserActivity.java (sharesheet) + */ +message RankingSelected { + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + // The relevant app's package name (can be source or picked package). + optional string package_name = 2; + // An identifier to tie together multiple logs relating to the same share event. + optional int32 instance_id = 3; + // Which of the ranked targets got picked, default starting position 0. + optional int32 position_picked = 4; +} + +/** + * Logs when TvSettings UI is interacted at. + * + * Logged from: packages/apps/TvSettings + */ +message TvSettingsUIInteracted { + + /** The UI action category */ + optional android.app.tvsettings.Action action = 1; + + /** The ID of the entry that the users actioned on */ + optional android.app.tvsettings.ItemId item_id = 2; +} + +/** + * Logs information about a package installation using package installer V2 APIs. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java + */ +message PackageInstallerV2Reported { + // Whether this installation uses Incremental File System + optional bool is_incremental = 1; + // Name of the package that is intended to be installed + optional string package_name = 2; + // The duration between when the install was requested to when the install has completed + optional int64 duration_millis = 3; + // Installation result in final integer, which are SystemApi's. + // Return_code 1 indicates success. + // For full list, see frameworks/base/core/java/android/content/pm/PackageManager.java + optional int32 return_code = 4; + // Total size of the APKs installed for this package + optional int64 apks_size_bytes = 5; +} + +/** + * Logs settings provider values. + * + * Use DeviceConfig.getProperties to get a list Setting key, query the data from content provider, + * then write the value to proto. + * + */ +message SettingSnapshot { + + // Setting key + optional string name = 1; + + enum SettingsValueType { + NOTASSIGNED = 0; + ASSIGNED_BOOL_TYPE = 1; + ASSIGNED_INT_TYPE = 2; + ASSIGNED_FLOAT_TYPE = 3; + ASSIGNED_STRING_TYPE = 4; + }; + // Setting value type + optional SettingsValueType type = 2; + + optional bool bool_value = 3; + + optional int32 int_value = 4; + + optional float float_value = 5; + + optional string str_value = 6; + + // Android user index. 0 for primary user, 10, 11 for secondary or profile user + optional int32 user_id = 7; +} + +/** + * An event logged to indicate that a user journey is about to be performed. This atom includes + * relevant information about the users involved in the journey. A UserLifecycleEventOccurred event + * will immediately follow this atom which will describe the event(s) and its state. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/UserController.java + * frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java + */ +message UserLifecycleJourneyReported { + // An identifier to track a chain of user lifecycle events occurring (referenced in the + // UserLifecycleEventOccurred atom) + optional int64 session_id = 1; + + // Indicates what type of user journey this session is related to + enum Journey { + UNKNOWN = 0; // Undefined user lifecycle journey + USER_SWITCH_UI = 1; // A user switch journey where a UI is shown + USER_SWITCH_FG = 2; // A user switch journey without a UI shown + USER_START = 3; // A user start journey + USER_CREATE = 4; // A user creation journey + } + optional Journey journey = 2; + // Which user the journey is originating from - could be -1 for certain phases (eg USER_CREATE) + // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest) + optional int32 origin_user = 3; + // Which user the journey is targeting + // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest) + optional int32 target_user = 4; + + // What is the user type of the target user + // These should be in sync with USER_TYPE_* flags defined in UserManager.java + enum UserType { + TYPE_UNKNOWN = 0; + FULL_SYSTEM = 1; + FULL_SECONDARY = 2; + FULL_GUEST = 3; + FULL_DEMO = 4; + FULL_RESTRICTED = 5; + PROFILE_MANAGED = 6; + SYSTEM_HEADLESS = 7; + } + optional UserType user_type = 5; + // What are the flags attached to the target user + optional int32 user_flags = 6; +} + +/** + * An event logged when a specific user lifecycle event is performed. These events should be + * correlated with a UserLifecycleJourneyReported atom via the session_id. + * Note: journeys can span over multiple events, hence some events may share a single session id. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/UserController.java + * frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java + */ +message UserLifecycleEventOccurred { + // An id which links back to user details (reported in the UserLifecycleJourneyReported atom) + optional int64 session_id = 1; + // The target user for this event (same as target_user in the UserLifecycleJourneyReported atom) + // This integer is a UserIdInt (eg 0 for the system user, 10 for secondary/guest) + optional int32 user_id = 2; + + enum Event { + UNKNOWN = 0; // Indicates that the associated user journey timed-out or resulted in an error + SWITCH_USER = 1; // Indicates that this is a user switch event + START_USER = 2; // Indicates that this is a user start event + CREATE_USER = 3; // Indicates that this is a user create event + USER_RUNNING_LOCKED = 4; // Indicates that user is running in locked state + UNLOCKING_USER = 5; // Indicates that this is a user unlocking event + UNLOCKED_USER = 6; // Indicates that this is a user unlocked event + } + optional Event event = 3; + + enum State { + NONE = 0; // Indicates the associated event has no start/end defined + BEGIN = 1; + FINISH = 2; + } + optional State state = 4; // Represents the state of an event (beginning/ending) +} + +/** + * Logs when accessibility shortcut clicked. + * + * Logged from: + * frameworks/base/services/accessibility/java/com/android/server/accessibility + */ +message AccessibilityShortcutReported { + // The accessibility feature(including installed a11y service, framework a11y feature, + // and installed a11y activity) package name that is assigned to the accessibility shortcut. + optional string package_name = 1; + + // The definition of the accessibility shortcut. + // From frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto. + optional android.stats.accessibility.ShortcutType shortcut_type = 2; + + // The definition of the service status. + // From frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto. + optional android.stats.accessibility.ServiceStatus service_status = 3; +} + +/** + * Logs when accessibility service status changed. + * + * Logged from: + * packages/apps/Settings/src/com/android/settings/accessibility + */ +message AccessibilityServiceReported { + // The accessibility service package name. + optional string package_name = 1; + + // The definition of the service status. + // From frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto. + optional android.stats.accessibility.ServiceStatus service_status = 2; +} + +/** + * Logs when display wake up. + * + * Logged from: + * services/core/java/com/android/server/power/Notifier.java + */ + +message DisplayWakeReported { + // Wake_up_reason code + // If LOWORD(wake_up_reason) = 0 + // reference to HIWORD(wake_up_reason) PowerManager.WAKE_REASON_XXX + // else reference wake_up_reason to + // services/core/java/com/android/server/power/Notifier.java#onWakeUp + optional int32 wake_up_reason = 1; +} + +/** + * Logs app usage events. + */ +message AppUsageEventOccurred { + optional int32 uid = 1 [(is_uid) = true]; + optional string package_name = 2; + optional string class_name = 3; + + enum EventType { + NONE = 0; + MOVE_TO_FOREGROUND = 1; + MOVE_TO_BACKGROUND = 2; + } + optional EventType event_type = 4; +} + +/* + * Quality metrics logged when EVS cameras are active. + * + * Logged from: + * packages/services/Car/evs/manager/1.1/Enumerator.cpp + */ +message EvsUsageStatsReported { + + // Camera identifier to distinguish the source camera device. This is not + // globally unique and therefore cannot be used to identify the user and/or + // the device. + optional int32 device_id = 1; + + // Peak number of clients during the service + optional int32 peak_num_clients = 2; + + // Number of erroneous events during the service + optional int32 num_errors = 3; + + // Round trip latency of the very first frame + optional int64 first_latency_millis = 4; + + // Average frame round trip latency + optional float avg_latency_millis = 5; + + // Peak frame round trip latency + optional int64 peak_latency_millis = 6; + + // Total number of frames received + optional int64 total_frames = 7; + + // Number of frames ignored + optional int64 ignored_frames = 8; + + // Number of dropped frames to synchronize camera devices + optional int64 dropped_frames_to_sync = 9; + + // The duration of the service + optional int64 duration_millis = 10; +} + +/** + * Logs audio power usage stats. + * + * Pushed from: + * frameworks/av/services/mediametrics/AudioPowerUsage.cpp + */ +message AudioPowerUsageDataReported { + /** + * Device used for input/output + * + * All audio devices please refer to below file: + * system/media/audio/include/system/audio-base.h + * + * Define our own enum values because we don't report all audio devices. + * Currently, we only report built-in audio devices such as handset, speaker, + * built-in mics, common audio devices such as wired headset, usb headset + * and bluetooth devices. + */ + enum AudioDevice { + OUTPUT_EARPIECE = 0x1; // handset + OUTPUT_SPEAKER = 0x2; // dual speaker + OUTPUT_WIRED_HEADSET = 0x4; // 3.5mm headset + OUTPUT_USB_HEADSET = 0x8; // usb headset + OUTPUT_BLUETOOTH_SCO = 0x10; // bluetooth sco + OUTPUT_BLUETOOTH_A2DP = 0x20; // a2dp + OUTPUT_SPEAKER_SAFE = 0x40; // bottom speaker + + INPUT_DEVICE_BIT = 0x40000000; // non-negative positive int32. + INPUT_BUILTIN_MIC = 0x40000001; // buildin mic + INPUT_BUILTIN_BACK_MIC = 0x40000002; // buildin back mic + INPUT_WIRED_HEADSET_MIC = 0x40000004; // 3.5mm headset mic + INPUT_USB_HEADSET_MIC = 0x40000008; // usb headset mic + INPUT_BLUETOOTH_SCO = 0x40000010; // bluetooth sco mic + } + optional AudioDevice audio_device = 1; + + // Duration of the audio in seconds + optional int32 duration_secs = 2; + + // Average volume (0 ... 1.0) + optional float average_volume = 3; + + enum AudioType { + UNKNOWN_TYPE = 0; + VOICE_CALL_TYPE = 1; // voice call + VOIP_CALL_TYPE = 2; // voip call, including uplink and downlink + MEDIA_TYPE = 3; // music and system sound + RINGTONE_NOTIFICATION_TYPE = 4; // ringtone and notification + ALARM_TYPE = 5; // alarm type + // record type + CAMCORDER_TYPE = 6; // camcorder + RECORD_TYPE = 7; // other recording + } + optional AudioType type = 4; +} + +/** + * Pulls bytes transferred over WiFi and mobile networks sliced by uid, is_metered, and tag. + * + * Pulled from: + * StatsPullAtomService, which uses NetworkStatsService to query NetworkStats. + */ +message BytesTransferByTagAndMetered { + optional int32 uid = 1 [(is_uid) = true]; + + optional bool is_metered = 2; + + optional int32 tag = 3; + + optional int64 rx_bytes = 4; + + optional int64 rx_packets = 5; + + optional int64 tx_bytes = 6; + + optional int64 tx_packets = 7; +} + +/* + * Logs when the Media Output Switcher finishes a media switch operation. + * + * Logged from: + * packages/apps/Settings/src/com/android/settings/media/MediaOutputSliceWorker.java + */ +message MediaOutputOpSwitchReported { + // Source medium type before switching. + optional android.app.settings.mediaoutput.MediumType source = 1; + + // Target medium type after switching. + optional android.app.settings.mediaoutput.MediumType target = 2; + + // The result of switching. + optional android.app.settings.mediaoutput.SwitchResult result = 3; + + // The detail code of a switching result. + optional android.app.settings.mediaoutput.SubResult subresult = 4; + + /* + * The package name of a pre-installed app, whose media session is being switched. + */ + optional string media_session_package_name = 5; + + // The amount of available wired devices when a switching is being performed. + optional int32 available_wired_device_count = 6; + + // The amount of available Bluetooth devices a switching is being performed. + optional int32 available_bt_device_count = 7; + + // The amount of available remote devices when a switching is being performed. + optional int32 available_remote_device_count = 8; + + // The amount of applied devices within a remote dynamic group after a switching is done. + optional int32 applied_device_count_within_remote_group = 9; +} + +/** + * Logs when the Assistant is invoked. + * + * Logged from: + * frameworks/base/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java + */ +message AssistantInvocationReported { + + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + + // The registered Assistant's uid and package (as for UiEventReported). + optional int32 uid = 2 [(is_uid) = true]; + optional string package_name = 3; + + // An identifier used to disambiguate which logs refer to a particular invocation of the + // Assistant (as for UiEventReported). + optional int32 instance_id = 4; + + // The state of the device at the time of invocation. + enum DeviceState { + UNKNOWN_DEVICE_STATE = 0; + AOD1 = 1; + AOD2 = 2; + BOUNCER = 3; + UNLOCKED_LOCKSCREEN = 4; + LAUNCHER_HOME = 5; + LAUNCHER_OVERVIEW = 6; + LAUNCHER_ALL_APPS = 7; + APP_DEFAULT = 8; + APP_IMMERSIVE = 9; + APP_FULLSCREEN = 10; + } + optional DeviceState device_state = 5; + + // Whether the Assistant handles were showing at the time of invocation. + optional bool assistant_handles_showing = 6; +} + +/** + * Logs when an AudioRecord finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioRecordDeviceUsageReported { + // The devices connected to this AudioRecord. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // See lookup<INPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The client-server buffer framecount. + // The framecount is generally between 960 - 48000 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the AudioRecord. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio input flags used to construct the AudioRecord. + // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t + optional string flags = 8; + + // The santized package name of the audio client associated with the AudioRecord. + // See getSanitizedPackageNameAndVersionCode() in + // frameworks/av/services/mediametrics/MediaMetricsService.cpp + optional string package_name = 9; + + // The selected device id (nonzero if a non-default device is selected) + optional int32 selected_device_id = 10; + + // The caller of the AudioRecord. + // See lookup<CALLER_NAME>() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string caller = 11; + + // The audio source for AudioRecord. + // An enumeration from system/media/audio/include/system/audio-base.h audio_source_t + optional string source = 12; +} + +/** + * Logs when an AudioThread finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioThreadDeviceUsageReported { + // The devices connected to this audio thread. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // (for record threads): + // See lookup<INPUT_DEVICE> in frameworks/av/services/mediametrics/AudioTypes.cpp + // (for playback threads): + // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The framecount of the buffer delivered to (or from) the HAL. + // The framecount is generally ~960 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the audio thread. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio flags used to construct the thread + // (for record threads): + // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t + // (for playback threads): + // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t + optional string flags = 8; + + // The number of underruns encountered for a playback thread or the + // number of overruns encountered for a capture thread. + optional int32 xruns = 9; + + // The type of thread + // A thread type enumeration from + // frameworks/av/mediametrics/services/Translate.h + optional string type = 10; +} + +/** + * Logs when an AudioTrack finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioTrackDeviceUsageReported { + // The output devices connected to this AudioTrack. + // A string OR of various output device categories, e.g. "DEVICE1|DEVICE2". + // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The client-server buffer framecount. + // The framecount is generally between 960 - 48000 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + // A static track (see traits) may have a very large framecount. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the AudioTrack. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio flags used to construct the AudioTrack. + // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t + optional string flags = 8; + + // The number of underruns encountered. + optional int32 xruns = 9; + + // The santized package name of the audio client associated with the AudioTrack. + // See getSanitizedPackageNameAndVersionCode() in + // frameworks/av/services/mediametrics/MediaMetricsService.cpp + optional string package_name = 10; + + // The latency of the last sample in the buffer in milliseconds. + optional float device_latency_millis = 11; + + // The startup time in milliseconds from start() to sample played. + optional float device_startup_millis = 12; + + // The average volume of the track on the device [ 0.f - 1.f ] + optional float device_volume = 13; + + // The selected device id (nonzero if a non-default device is selected) + optional int32 selected_device_id = 14; + + // The stream_type category for the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_stream_type_t + optional string stream_type = 15; + + // The usage for the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_usage_t + optional string usage = 16; + + // The content type of the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_content_type_t + optional string content_type = 17; + + // The caller of the AudioTrack. + // See lookup<CALLER_NAME>() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string caller = 18; + + // The traits of the AudioTrack. + // A string OR of different traits, may be empty string. + // Only "static" is supported for R. + // See lookup<TRACK_TRAITS>() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string traits = 19; +} + +/** + * Logs the status of an audio device connection attempt. + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioDeviceConnectionReported { + // The input devices represented by this report. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // See lookup<INPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string input_devices = 1; + + // The output devices represented by this report. + // A string OR of various output device categories. + // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string output_devices = 2; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 3; + + // The result of the audio device connection. + // 0 indicates success: connection verified. + // 1 indicates unknown: connection not verified or not known if diverted properly. + // Other values indicate specific status. + // See DeviceConnectionResult in frameworks/av/services/mediametrics/AudioTypes.h + optional int32 result = 4; + + // Average milliseconds of time to connect + optional float time_to_connect_millis = 5; + + // Number of connections if aggregated statistics, otherwise 1. + optional int32 connection_count = 6; +} + +/** * Logs: i) creation of different types of cryptographic keys in the keystore, * ii) operations performed using the keys, * iii) attestation of the keys @@ -7999,9 +11095,9 @@ message KeystoreKeyEventReported { optional KeyBlobUsageRequirements key_blob_usage_reqs = 11; enum Type { - KEY_OPERATION = 0; - KEY_CREATION = 1; - KEY_ATTESTATION = 2; + key_operation = 0; + key_creation = 1; + key_attestation = 2; } /** Key creation event, operation event or attestation event? */ optional Type type = 12; @@ -8012,3 +11108,72 @@ message KeystoreKeyEventReported { /** Response code or error code */ optional int32 error_code = 14; } + +// Blob Committer stats +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobCommitterProto { + // Committer app's uid + optional int32 uid = 1 [(is_uid) = true]; + + // Unix epoch timestamp of the commit in milliseconds + optional int64 commit_timestamp_millis = 2; + + // Flags of what access types the committer has set for the Blob + optional int32 access_mode = 3; + + // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST + optional int32 num_whitelisted_package = 4; +} + +// Blob Leasee stats +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobLeaseeProto { + // Leasee app's uid + optional int32 uid = 1 [(is_uid) = true]; + + // Unix epoch timestamp for lease expiration in milliseconds + optional int64 lease_expiry_timestamp_millis = 2; +} + +// List of Blob Committers +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobCommitterListProto { + repeated BlobCommitterProto committer = 1; +} + +// List of Blob Leasees +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobLeaseeListProto { + repeated BlobLeaseeProto leasee = 1; +} + +/** + * Logs the current state of a Blob committed with BlobStoreManager + * + * Pulled from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobInfo { + // Id of the Blob + optional int64 blob_id = 1; + + // Size of the Blob data + optional int64 size = 2; + + // Unix epoch timestamp of the Blob's expiration in milliseconds + optional int64 expiry_timestamp_millis = 3; + + // List of committers of this Blob + optional BlobCommitterListProto committers = 4; + + // List of leasees of this Blob + optional BlobLeaseeListProto leasees = 5; +} diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index cf0cc3d59d86..e9875baf58c7 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -37,7 +37,8 @@ CombinationConditionTracker::~CombinationConditionTracker() { bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConfig, const vector<sp<ConditionTracker>>& allConditionTrackers, const unordered_map<int64_t, int>& conditionIdIndexMap, - vector<bool>& stack) { + vector<bool>& stack, + vector<ConditionState>& initialConditionCache) { VLOG("Combination predicate init() %lld", (long long)mConditionId); if (mInitialized) { return true; @@ -73,15 +74,15 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf return false; } - - bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers, - conditionIdIndexMap, stack); + bool initChildSucceeded = + childTracker->init(allConditionConfig, allConditionTrackers, conditionIdIndexMap, + stack, initialConditionCache); if (!initChildSucceeded) { ALOGW("Child initialization failed %lld ", (long long)child); return false; } else { - ALOGW("Child initialization success %lld ", (long long)child); + VLOG("Child initialization success %lld ", (long long)child); } if (allConditionTrackers[childIndex]->isSliced()) { @@ -95,6 +96,11 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf childTracker->getLogTrackerIndex().end()); } + mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation, + initialConditionCache); + initialConditionCache[mIndex] = + evaluateCombinationCondition(mChildren, mLogicalOperation, initialConditionCache); + // unmark this node in the recursion stack. stack[mIndex] = false; @@ -105,20 +111,14 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf void CombinationConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const std::vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { + vector<ConditionState>& conditionCache) const { // So far, this is fine as there is at most one child having sliced output. for (const int childIndex : mChildren) { if (conditionCache[childIndex] == ConditionState::kNotEvaluated) { allConditions[childIndex]->isConditionMet(conditionParameters, allConditions, - dimensionFields, - isSubOutputDimensionFields, isPartialLink, - conditionCache, - dimensionsKeySet); + conditionCache); } } conditionCache[mIndex] = @@ -147,17 +147,14 @@ void CombinationConditionTracker::evaluateCondition( ConditionState newCondition = evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache); if (!mSliced) { + bool nonSlicedChanged = (mUnSlicedPartCondition != newCondition); + mUnSlicedPartCondition = newCondition; - bool nonSlicedChanged = (mNonSlicedConditionState != newCondition); - mNonSlicedConditionState = newCondition; - - nonSlicedConditionCache[mIndex] = mNonSlicedConditionState; - + nonSlicedConditionCache[mIndex] = mUnSlicedPartCondition; conditionChangedCache[mIndex] = nonSlicedChanged; - mUnSlicedPart = newCondition; } else { - mUnSlicedPart = evaluateCombinationCondition( - mUnSlicedChildren, mLogicalOperation, nonSlicedConditionCache); + mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation, + nonSlicedConditionCache); for (const int childIndex : mChildren) { // If any of the sliced condition in children condition changes, the combination @@ -173,25 +170,6 @@ void CombinationConditionTracker::evaluateCondition( } } -ConditionState CombinationConditionTracker::getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const std::vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { - vector<ConditionState> conditionCache(allConditions.size(), ConditionState::kNotEvaluated); - // So far, this is fine as there is at most one child having sliced output. - for (const int childIndex : mChildren) { - conditionCache[childIndex] = conditionCache[childIndex] | - allConditions[childIndex]->getMetConditionDimension( - allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet); - } - evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache); - if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) { - dimensionsKeySet.insert(DEFAULT_DIMENSION_KEY); - } - return conditionCache[mIndex]; -} - bool CombinationConditionTracker::equalOutputDimensions( const std::vector<sp<ConditionTracker>>& allConditions, const vector<Matcher>& dimensions) const { diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h index 481cb200d8e6..39ff0ab03266 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -32,8 +32,8 @@ public: bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<int64_t, int>& conditionIdIndexMap, - std::vector<bool>& stack) override; + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack, + std::vector<ConditionState>& initialConditionCache) override; void evaluateCondition(const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues, @@ -43,17 +43,8 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; - - ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + std::vector<ConditionState>& conditionCache) const override; // Only one child predicate can have dimension. const std::set<HashableDimensionKey>* getChangedToTrueDimensions( diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 5ff0e1d5a885..62736c8160bb 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -36,7 +36,7 @@ public: mIndex(index), mInitialized(false), mTrackerIndex(), - mNonSlicedConditionState(ConditionState::kUnknown), + mUnSlicedPartCondition(ConditionState::kUnknown), mSliced(false){}; virtual ~ConditionTracker(){}; @@ -51,10 +51,12 @@ public: // need to call init() on children conditions) // conditionIdIndexMap: the mapping from condition id to its index. // stack: a bit map to keep track which nodes have been visited on the stack in the recursion. + // initialConditionCache: tracks initial conditions of all ConditionTrackers. virtual bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, const std::unordered_map<int64_t, int>& conditionIdIndexMap, - std::vector<bool>& stack) = 0; + std::vector<bool>& stack, + std::vector<ConditionState>& initialConditionCache) = 0; // evaluate current condition given the new event. // event: the new log event @@ -72,39 +74,19 @@ public: std::vector<ConditionState>& conditionCache, std::vector<bool>& conditionChanged) = 0; - // Return the current condition state. - virtual ConditionState isConditionMet() const { - return mNonSlicedConditionState; - }; - // Query the condition with parameters. // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the // condition. // [allConditions]: all condition trackers. This is needed because the condition evaluation is // done recursively - // [dimensionFields]: the needed dimension fields which should be all or subset of the condition - // tracker output dimension. - // [isSubOutputDimensionFields]: true if the needed dimension fields which is strictly subset of - // the condition tracker output dimension. // [isPartialLink]: true if the link specified by 'conditionParameters' contains all the fields // in the condition tracker output dimension. // [conditionCache]: the cache holding the condition evaluation values. - // [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination - // condition, it assumes that only one child predicate is sliced. virtual void isConditionMet( const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0; - - virtual ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0; + std::vector<ConditionState>& conditionCache) const = 0; // return the list of LogMatchingTracker index that this ConditionTracker uses. virtual const std::set<int>& getLogTrackerIndex() const { @@ -140,8 +122,9 @@ public: const std::vector<sp<ConditionTracker>>& allConditions, const vector<Matcher>& dimensions) const = 0; + // Return the current condition state of the unsliced part of the condition. inline ConditionState getUnSlicedPartConditionState() const { - return mUnSlicedPart; + return mUnSlicedPartCondition; } protected: @@ -156,10 +139,16 @@ protected: // the list of LogMatchingTracker index that this ConditionTracker uses. std::set<int> mTrackerIndex; - ConditionState mNonSlicedConditionState; + // This variable is only used for CombinationConditionTrackers. + // SimpleConditionTrackers technically don't have an unsliced part because + // they are either sliced or unsliced. + // + // CombinationConditionTrackers have multiple children ConditionTrackers + // that can be a mixture of sliced or unsliced. This tracks the + // condition of the unsliced part of the combination condition. + ConditionState mUnSlicedPartCondition; bool mSliced; - ConditionState mUnSlicedPart; }; } // namespace statsd diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp index f371316a1420..c542032b48ea 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -22,27 +22,15 @@ namespace statsd { using std::vector; ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet) { + const bool isPartialLink) { vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated); mAllConditions[index]->isConditionMet( - parameters, mAllConditions, dimensionFields, isSubOutputDimensionFields, isPartialLink, - cache, *dimensionKeySet); + parameters, mAllConditions, isPartialLink, + cache); return cache[index]; } -ConditionState ConditionWizard::getMetConditionDimension( - const int index, const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const { - return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields, - isSubOutputDimensionFields, - *dimensionsKeySet); -} - const set<HashableDimensionKey>* ConditionWizard::getChangedToTrueDimensions( const int index) const { return mAllConditions[index]->getChangedToTrueDimensions(mAllConditions); @@ -79,4 +67,4 @@ bool ConditionWizard::equalOutputDimensions(const int index, const vector<Matche } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h index 2c8814772839..892647910d9f 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -40,15 +40,7 @@ public: // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case, // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet); - - virtual ConditionState getMetConditionDimension( - const int index, const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const; + const bool isPartialLink); virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(const int index) const; virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 12ff184a2608..efb4d4989425 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -85,12 +85,6 @@ SimpleConditionTracker::SimpleConditionTracker( mInitialValue = ConditionState::kUnknown; } - mNonSlicedConditionState = mInitialValue; - - if (!mSliced) { - mUnSlicedPart = mInitialValue; - } - mInitialized = true; } @@ -101,9 +95,11 @@ SimpleConditionTracker::~SimpleConditionTracker() { bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig, const vector<sp<ConditionTracker>>& allConditionTrackers, const unordered_map<int64_t, int>& conditionIdIndexMap, - vector<bool>& stack) { + vector<bool>& stack, + vector<ConditionState>& initialConditionCache) { // SimpleConditionTracker does not have dependency on other conditions, thus we just return // if the initialization was successful. + initialConditionCache[mIndex] = mInitialValue; return mInitialized; } @@ -141,9 +137,6 @@ void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditio mInitialValue = ConditionState::kFalse; mSlicedConditionState.clear(); conditionCache[mIndex] = ConditionState::kFalse; - if (!mSliced) { - mUnSlicedPart = ConditionState::kFalse; - } } bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -305,9 +298,7 @@ void SimpleConditionTracker::evaluateCondition( conditionCache[mIndex] = itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; } - mUnSlicedPart = conditionCache[mIndex]; } - return; } @@ -333,18 +324,12 @@ void SimpleConditionTracker::evaluateCondition( } conditionCache[mIndex] = overallState; conditionChangedCache[mIndex] = overallChanged; - if (!mSliced) { - mUnSlicedPart = overallState; - } } void SimpleConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { + vector<ConditionState>& conditionCache) const { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. @@ -356,18 +341,13 @@ void SimpleConditionTracker::isConditionMet( if (pair == conditionParameters.end()) { ConditionState conditionState = ConditionState::kNotEvaluated; - if (dimensionFields.size() > 0 && dimensionFields[0].mMatcher.getTag() == mDimensionTag) { - conditionState = conditionState | getMetConditionDimension( - allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet); - } else { - conditionState = conditionState | mInitialValue; - if (!mSliced) { - const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); - if (itr != mSlicedConditionState.end()) { - ConditionState sliceState = - itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionState = conditionState | sliceState; - } + conditionState = conditionState | mInitialValue; + if (!mSliced) { + const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); + if (itr != mSlicedConditionState.end()) { + ConditionState sliceState = + itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; + conditionState = conditionState | sliceState; } } conditionCache[mIndex] = conditionState; @@ -385,15 +365,6 @@ void SimpleConditionTracker::isConditionMet( slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; if (slice.first.contains(key)) { conditionState = conditionState | sliceState; - if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) { - if (isSubOutputDimensionFields) { - HashableDimensionKey dimensionKey; - filterValues(dimensionFields, slice.first.getValues(), &dimensionKey); - dimensionsKeySet.insert(dimensionKey); - } else { - dimensionsKeySet.insert(slice.first); - } - } } } } else { @@ -403,15 +374,6 @@ void SimpleConditionTracker::isConditionMet( ConditionState sliceState = startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; conditionState = conditionState | sliceState; - if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) { - if (isSubOutputDimensionFields) { - HashableDimensionKey dimensionKey; - filterValues(dimensionFields, startedCountIt->first.getValues(), &dimensionKey); - dimensionsKeySet.insert(dimensionKey); - } else { - dimensionsKeySet.insert(startedCountIt->first); - } - } } } @@ -419,41 +381,6 @@ void SimpleConditionTracker::isConditionMet( VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]); } -ConditionState SimpleConditionTracker::getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { - ConditionState conditionState = mInitialValue; - if (dimensionFields.size() == 0 || mOutputDimensions.size() == 0 || - dimensionFields[0].mMatcher.getTag() != mOutputDimensions[0].mMatcher.getTag()) { - const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY); - if (itr != mSlicedConditionState.end()) { - ConditionState sliceState = - itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionState = conditionState | sliceState; - } - return conditionState; - } - - for (const auto& slice : mSlicedConditionState) { - ConditionState sliceState = - slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionState = conditionState | sliceState; - - if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) { - if (isSubOutputDimensionFields) { - HashableDimensionKey dimensionKey; - filterValues(dimensionFields, slice.first.getValues(), &dimensionKey); - dimensionsKeySet.insert(dimensionKey); - } else { - dimensionsKeySet.insert(slice.first); - } - } - } - return conditionState; -} - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h index 47d1eceb9022..ea7f87bde2b8 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -37,8 +37,8 @@ public: bool init(const std::vector<Predicate>& allConditionConfig, const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<int64_t, int>& conditionIdIndexMap, - std::vector<bool>& stack) override; + const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack, + std::vector<ConditionState>& initialConditionCache) override; void evaluateCondition(const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues, @@ -48,17 +48,8 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; - - ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; + std::vector<ConditionState>& conditionCache) const override; virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( const std::vector<sp<ConditionTracker>>& allConditions) const { diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp deleted file mode 100644 index 1965ce6015ff..000000000000 --- a/cmds/statsd/src/condition/StateTracker.cpp +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StateTracker.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -using std::string; -using std::unordered_set; -using std::vector; - -StateTracker::StateTracker(const ConfigKey& key, const int64_t& id, const int index, - const SimplePredicate& simplePredicate, - const unordered_map<int64_t, int>& trackerNameIndexMap, - const vector<Matcher> primaryKeys) - : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) { - if (simplePredicate.has_start()) { - auto pair = trackerNameIndexMap.find(simplePredicate.start()); - if (pair == trackerNameIndexMap.end()) { - ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); - return; - } - mStartLogMatcherIndex = pair->second; - mTrackerIndex.insert(mStartLogMatcherIndex); - } else { - ALOGW("Condition %lld must have a start matcher", (long long)id); - return; - } - - if (simplePredicate.has_dimensions()) { - translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); - if (mOutputDimensions.size() > 0) { - mSliced = true; - mDimensionTag = mOutputDimensions[0].mMatcher.getTag(); - } else { - ALOGW("Condition %lld has invalid dimensions", (long long)id); - return; - } - } else { - ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id); - return; - } - - if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { - mInitialValue = ConditionState::kFalse; - } else { - mInitialValue = ConditionState::kUnknown; - } - - mNonSlicedConditionState = mInitialValue; - mInitialized = true; -} - -StateTracker::~StateTracker() { - VLOG("~StateTracker()"); -} - -bool StateTracker::init(const vector<Predicate>& allConditionConfig, - const vector<sp<ConditionTracker>>& allConditionTrackers, - const unordered_map<int64_t, int>& conditionIdIndexMap, - vector<bool>& stack) { - return mInitialized; -} - -void StateTracker::dumpState() { - VLOG("StateTracker %lld DUMP:", (long long)mConditionId); - for (const auto& value : mSlicedState) { - VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str()); - } - VLOG("Last Changed to True: "); - for (const auto& value : mLastChangedToTrueDimensions) { - VLOG("%s", value.toString().c_str()); - } - VLOG("Last Changed to False: "); - for (const auto& value : mLastChangedToFalseDimensions) { - VLOG("%s", value.toString().c_str()); - } -} - -bool StateTracker::hitGuardRail(const HashableDimensionKey& newKey) { - if (mSlicedState.find(newKey) != mSlicedState.end()) { - // if the condition is not sliced or the key is not new, we are good! - return false; - } - // 1. Report the tuple count if the tuple count > soft limit - if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mSlicedState.size() + 1; - StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("Predicate %lld dropping data for dimension key %s", - (long long)mConditionId, newKey.toString().c_str()); - return true; - } - } - return false; -} - -void StateTracker::evaluateCondition(const LogEvent& event, - const vector<MatchingState>& eventMatcherValues, - const vector<sp<ConditionTracker>>& mAllConditions, - vector<ConditionState>& conditionCache, - vector<bool>& conditionChangedCache) { - mLastChangedToTrueDimensions.clear(); - mLastChangedToFalseDimensions.clear(); - if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { - // it has been evaluated. - VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); - return; - } - - if (mStartLogMatcherIndex >= 0 && - eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) { - conditionCache[mIndex] = - mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionChangedCache[mIndex] = false; - return; - } - - VLOG("StateTracker evaluate event %s", event.ToString().c_str()); - - // Primary key can exclusive fields must be simple fields. so there won't be more than - // one keys matched. - HashableDimensionKey primaryKey; - HashableDimensionKey state; - if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) || - !filterValues(mOutputDimensions, event.getValues(), &state)) { - ALOGE("Failed to filter fields in the event?? panic now!"); - conditionCache[mIndex] = - mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionChangedCache[mIndex] = false; - return; - } - hitGuardRail(primaryKey); - - VLOG("StateTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str()); - - auto it = mSlicedState.find(primaryKey); - if (it == mSlicedState.end()) { - mSlicedState[primaryKey] = state; - conditionCache[mIndex] = ConditionState::kTrue; - mLastChangedToTrueDimensions.insert(state); - conditionChangedCache[mIndex] = true; - } else if (!(it->second == state)) { - mLastChangedToFalseDimensions.insert(it->second); - mLastChangedToTrueDimensions.insert(state); - mSlicedState[primaryKey] = state; - conditionCache[mIndex] = ConditionState::kTrue; - conditionChangedCache[mIndex] = true; - } else { - conditionCache[mIndex] = ConditionState::kTrue; - conditionChangedCache[mIndex] = false; - } - - if (DEBUG) { - dumpState(); - } - return; -} - -void StateTracker::isConditionMet( - const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - const bool isPartialLink, - vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { - if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { - // it has been evaluated. - VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); - return; - } - - const auto pair = conditionParameters.find(mConditionId); - if (pair == conditionParameters.end()) { - if (mSlicedState.size() > 0) { - conditionCache[mIndex] = ConditionState::kTrue; - - for (const auto& state : mSlicedState) { - dimensionsKeySet.insert(state.second); - } - } else { - conditionCache[mIndex] = ConditionState::kUnknown; - } - return; - } - - const auto& primaryKey = pair->second; - conditionCache[mIndex] = mInitialValue; - auto it = mSlicedState.find(primaryKey); - if (it != mSlicedState.end()) { - conditionCache[mIndex] = ConditionState::kTrue; - dimensionsKeySet.insert(it->second); - } -} - -ConditionState StateTracker::getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const { - if (mSlicedState.size() > 0) { - for (const auto& state : mSlicedState) { - dimensionsKeySet.insert(state.second); - } - return ConditionState::kTrue; - } - - return mInitialValue; -} - -} // namespace statsd -} // namespace os -} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateTracker.h deleted file mode 100644 index 2bdf98c34c32..000000000000 --- a/cmds/statsd/src/condition/StateTracker.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include <gtest/gtest_prod.h> -#include "ConditionTracker.h" -#include "config/ConfigKey.h" -#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -class StateTracker : public virtual ConditionTracker { -public: - StateTracker(const ConfigKey& key, const int64_t& id, const int index, - const SimplePredicate& simplePredicate, - const std::unordered_map<int64_t, int>& trackerNameIndexMap, - const vector<Matcher> primaryKeys); - - ~StateTracker(); - - bool init(const std::vector<Predicate>& allConditionConfig, - const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<int64_t, int>& conditionIdIndexMap, - std::vector<bool>& stack) override; - - void evaluateCondition(const LogEvent& event, - const std::vector<MatchingState>& eventMatcherValues, - const std::vector<sp<ConditionTracker>>& mAllConditions, - std::vector<ConditionState>& conditionCache, - std::vector<bool>& changedCache) override; - - /** - * Note: dimensionFields will be ignored in StateTracker, because we demand metrics - * must take the entire dimension fields from StateTracker. This is to make implementation - * simple and efficient. - * - * For example: wakelock duration by uid process states: - * dimension in condition must be {uid, process state}. - */ - void isConditionMet(const ConditionKey& conditionParameters, - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - const bool isPartialLink, - std::vector<ConditionState>& conditionCache, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; - - /** - * Note: dimensionFields will be ignored in StateTracker, because we demand metrics - * must take the entire dimension fields from StateTracker. This is to make implementation - * simple and efficient. - */ - ConditionState getMetConditionDimension( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensionFields, - const bool isSubOutputDimensionFields, - std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override; - - virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( - const std::vector<sp<ConditionTracker>>& allConditions) const { - return &mLastChangedToTrueDimensions; - } - - virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( - const std::vector<sp<ConditionTracker>>& allConditions) const { - return &mLastChangedToFalseDimensions; - } - - bool IsChangedDimensionTrackable() const override { return true; } - - bool IsSimpleCondition() const override { return true; } - - bool equalOutputDimensions( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensions) const override { - return equalDimensions(mOutputDimensions, dimensions); - } - - void getTrueSlicedDimensions( - const std::vector<sp<ConditionTracker>>& allConditions, - std::set<HashableDimensionKey>* dimensions) const override { - for (const auto& itr : mSlicedState) { - dimensions->insert(itr.second); - } - } - -private: - const ConfigKey mConfigKey; - - // The index of the LogEventMatcher which defines the start. - int mStartLogMatcherIndex; - - std::set<HashableDimensionKey> mLastChangedToTrueDimensions; - std::set<HashableDimensionKey> mLastChangedToFalseDimensions; - - std::vector<Matcher> mOutputDimensions; - std::vector<Matcher> mPrimaryKeys; - - ConditionState mInitialValue; - - int mDimensionTag; - - void dumpState(); - - bool hitGuardRail(const HashableDimensionKey& newKey); - - // maps from [primary_key] to [primary_key, exclusive_state]. - std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState; - - FRIEND_TEST(StateTrackerTest, TestStateChange); -}; - -} // namespace statsd -} // namespace os -} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 13d251ab7736..13020e06dc5d 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -42,7 +42,78 @@ using std::vector; using android::base::StringPrintf; using std::unique_ptr; -ConfigManager::ConfigManager() { +struct ConfigReceiverDeathCookie { + ConfigReceiverDeathCookie(const wp<ConfigManager>& configManager, const ConfigKey& configKey, + const shared_ptr<IPendingIntentRef>& pir) : + mConfigManager(configManager), mConfigKey(configKey), mPir(pir) { + } + + wp<ConfigManager> mConfigManager; + ConfigKey mConfigKey; + shared_ptr<IPendingIntentRef> mPir; +}; + +void ConfigManager::configReceiverDied(void* cookie) { + auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie); + sp<ConfigManager> thiz = cookie_->mConfigManager.promote(); + if (!thiz) { + return; + } + + ConfigKey& configKey = cookie_->mConfigKey; + shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; + + // Erase the mapping from the config key to the config receiver (pir) if the + // mapping still exists. + lock_guard<mutex> lock(thiz->mMutex); + auto it = thiz->mConfigReceivers.find(configKey); + if (it != thiz->mConfigReceivers.end() && it->second == pir) { + thiz->mConfigReceivers.erase(configKey); + } + + // The death recipient corresponding to this specific pir can never be + // triggered again, so free up resources. + delete cookie_; +} + +struct ActiveConfigChangedReceiverDeathCookie { + ActiveConfigChangedReceiverDeathCookie(const wp<ConfigManager>& configManager, const int uid, + const shared_ptr<IPendingIntentRef>& pir) : + mConfigManager(configManager), mUid(uid), mPir(pir) { + } + + wp<ConfigManager> mConfigManager; + int mUid; + shared_ptr<IPendingIntentRef> mPir; +}; + +void ConfigManager::activeConfigChangedReceiverDied(void* cookie) { + auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie); + sp<ConfigManager> thiz = cookie_->mConfigManager.promote(); + if (!thiz) { + return; + } + + int uid = cookie_->mUid; + shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; + + // Erase the mapping from the config key to the active config changed + // receiver (pir) if the mapping still exists. + lock_guard<mutex> lock(thiz->mMutex); + auto it = thiz->mActiveConfigsChangedReceivers.find(uid); + if (it != thiz->mActiveConfigsChangedReceivers.end() && it->second == pir) { + thiz->mActiveConfigsChangedReceivers.erase(uid); + } + + // The death recipient corresponding to this specific pir can never + // be triggered again, so free up resources. + delete cookie_; +} + +ConfigManager::ConfigManager() : + mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)), + mActiveConfigChangedReceiverDeathRecipient( + AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) { } ConfigManager::~ConfigManager() { @@ -114,9 +185,12 @@ void ConfigManager::UpdateConfig(const ConfigKey& key, const StatsdConfig& confi } } -void ConfigManager::SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender) { +void ConfigManager::SetConfigReceiver(const ConfigKey& key, + const shared_ptr<IPendingIntentRef>& pir) { lock_guard<mutex> lock(mMutex); - mConfigReceivers[key] = intentSender; + mConfigReceivers[key] = pir; + AIBinder_linkToDeath(pir->asBinder().get(), mConfigReceiverDeathRecipient.get(), + new ConfigReceiverDeathCookie(this, key, pir)); } void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) { @@ -125,9 +199,13 @@ void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) { } void ConfigManager::SetActiveConfigsChangedReceiver(const int uid, - const sp<IBinder>& intentSender) { - lock_guard<mutex> lock(mMutex); - mActiveConfigsChangedReceivers[uid] = intentSender; + const shared_ptr<IPendingIntentRef>& pir) { + { + lock_guard<mutex> lock(mMutex); + mActiveConfigsChangedReceivers[uid] = pir; + } + AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(), + new ActiveConfigChangedReceiverDeathCookie(this, uid, pir)); } void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) { @@ -146,25 +224,11 @@ void ConfigManager::RemoveConfig(const ConfigKey& key) { // Remove from map uidIt->second.erase(key); - // No more configs for this uid, lets remove the active configs callback. - if (uidIt->second.empty()) { - auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid); - if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) { - mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver); - } - } - for (const sp<ConfigListener>& listener : mListeners) { broadcastList.push_back(listener); } } - auto itReceiver = mConfigReceivers.find(key); - if (itReceiver != mConfigReceivers.end()) { - // Remove from map - mConfigReceivers.erase(itReceiver); - } - // Remove from disk. There can still be a lingering file on disk so we check // whether or not the config was on memory. remove_saved_configs(key); @@ -195,12 +259,6 @@ void ConfigManager::RemoveConfigs(int uid) { // Remove from map remove_saved_configs(*it); removed.push_back(*it); - mConfigReceivers.erase(*it); - } - - auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid); - if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) { - mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver); } mConfigs.erase(uidIt); @@ -234,8 +292,6 @@ void ConfigManager::RemoveAllConfigs() { uidIt = mConfigs.erase(uidIt); } - mConfigReceivers.clear(); - mActiveConfigsChangedReceivers.clear(); for (const sp<ConfigListener>& listener : mListeners) { broadcastList.push_back(listener); } @@ -262,7 +318,7 @@ vector<ConfigKey> ConfigManager::GetAllConfigKeys() const { return ret; } -const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key) const { +const shared_ptr<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const { lock_guard<mutex> lock(mMutex); auto it = mConfigReceivers.find(key); @@ -273,7 +329,8 @@ const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key } } -const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const { +const shared_ptr<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) + const { lock_guard<mutex> lock(mMutex); auto it = mActiveConfigsChangedReceivers.find(uid); diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h index 1fdec316e499..bef057f96409 100644 --- a/cmds/statsd/src/config/ConfigManager.h +++ b/cmds/statsd/src/config/ConfigManager.h @@ -16,16 +16,18 @@ #pragma once -#include "binder/IBinder.h" #include "config/ConfigKey.h" #include "config/ConfigListener.h" -#include <map> +#include <aidl/android/os/IPendingIntentRef.h> #include <mutex> #include <string> #include <stdio.h> +using aidl::android::os::IPendingIntentRef; +using std::shared_ptr; + namespace android { namespace os { namespace statsd { @@ -63,12 +65,12 @@ public: /** * Sets the broadcast receiver for a configuration key. */ - void SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender); + void SetConfigReceiver(const ConfigKey& key, const shared_ptr<IPendingIntentRef>& pir); /** * Returns the package name and class name representing the broadcast receiver for this config. */ - const sp<android::IBinder> GetConfigReceiver(const ConfigKey& key) const; + const shared_ptr<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const; /** * Returns all config keys registered. @@ -84,13 +86,13 @@ public: * Sets the broadcast receiver that is notified whenever the list of active configs * changes for this uid. */ - void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender); + void SetActiveConfigsChangedReceiver(const int uid, const shared_ptr<IPendingIntentRef>& pir); /** * Returns the broadcast receiver for active configs changed for this uid. */ - const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const; + const shared_ptr<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const; /** * Erase any active configs changed broadcast receiver associated with this uid. @@ -140,21 +142,38 @@ private: std::map<int, std::set<ConfigKey>> mConfigs; /** - * Each config key can be subscribed by up to one receiver, specified as IBinder from - * PendingIntent. + * Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef. */ - std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers; + std::map<ConfigKey, shared_ptr<IPendingIntentRef>> mConfigReceivers; /** * Each uid can be subscribed by up to one receiver to notify that the list of active configs - * for this uid has changed. The receiver is specified as IBinder from PendingIntent. + * for this uid has changed. The receiver is specified as IPendingIntentRef. */ - std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers; + std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers; /** * The ConfigListeners that will be told about changes. */ std::vector<sp<ConfigListener>> mListeners; + + // Death recipients that are triggered when the host process holding an + // IPendingIntentRef dies. + ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient; + ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient; + + /** + * Death recipient callback that is called when a config receiver dies. + * The cookie is a pointer to a ConfigReceiverDeathCookie. + */ + static void configReceiverDied(void* cookie); + + /** + * Death recipient callback that is called when an active config changed + * receiver dies. The cookie is a pointer to an + * ActiveConfigChangedReceiverDeathCookie. + */ + static void activeConfigChangedReceiverDied(void* cookie); }; } // namespace statsd diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java b/cmds/statsd/src/experiment_ids.proto index e000918fa0f7..c2036314cf58 100644 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfParser.java +++ b/cmds/statsd/src/experiment_ids.proto @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 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. @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.statsd.loadtest; -import android.annotation.Nullable; +syntax = "proto2"; -public interface PerfParser { +package android.os.statsd; - /** - * Parses one line of the dumpsys output, and returns a string to write to the data file, - * or null if no string should be written. - */ - @Nullable String parseLine(String line); +option java_package = "com.android.internal.os"; +option java_outer_classname = "ExperimentIdsProto"; + +// StatsLogProcessor uses the proto to parse experiment ids from +// BinaryPushStateChanged atoms. This needs to be in sync with +// TrainExperimentIds in atoms.proto. +message ExperimentIds { + repeated int64 experiment_id = 1; } diff --git a/cmds/statsd/src/external/CarStatsPuller.cpp b/cmds/statsd/src/external/CarStatsPuller.cpp deleted file mode 100644 index 70c0456b5eb4..000000000000 --- a/cmds/statsd/src/external/CarStatsPuller.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2019 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. - */ - -#define DEBUG false -#include "Log.h" - -#include <binder/IServiceManager.h> -#include <com/android/internal/car/ICarStatsService.h> - -#include "CarStatsPuller.h" -#include "logd/LogEvent.h" -#include "stats_log_util.h" - -using android::binder::Status; -using com::android::internal::car::ICarStatsService; - -namespace android { -namespace os { -namespace statsd { - -static std::mutex gCarStatsMutex; -static sp<ICarStatsService> gCarStats = nullptr; - -class CarStatsDeathRecipient : public android::IBinder::DeathRecipient { - public: - CarStatsDeathRecipient() = default; - ~CarStatsDeathRecipient() override = default; - - // android::IBinder::DeathRecipient override: - void binderDied(const android::wp<android::IBinder>& /* who */) override { - ALOGE("Car service has died"); - std::lock_guard<std::mutex> lock(gCarStatsMutex); - if (gCarStats) { - sp<IBinder> binder = IInterface::asBinder(gCarStats); - binder->unlinkToDeath(this); - gCarStats = nullptr; - } - } -}; - -static sp<CarStatsDeathRecipient> gDeathRecipient = new CarStatsDeathRecipient(); - -static sp<ICarStatsService> getCarService() { - std::lock_guard<std::mutex> lock(gCarStatsMutex); - if (!gCarStats) { - const sp<IBinder> binder = defaultServiceManager()->checkService(String16("car_stats")); - if (!binder) { - ALOGW("Car service is unavailable"); - return nullptr; - } - gCarStats = interface_cast<ICarStatsService>(binder); - binder->linkToDeath(gDeathRecipient); - } - return gCarStats; -} - -CarStatsPuller::CarStatsPuller(const int tagId) : StatsPuller(tagId) { -} - -bool CarStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) { - const sp<ICarStatsService> carService = getCarService(); - if (!carService) { - return false; - } - - vector<StatsLogEventWrapper> returned_value; - Status status = carService->pullData(mTagId, &returned_value); - if (!status.isOk()) { - ALOGW("CarStatsPuller::pull failed for %d", mTagId); - return false; - } - - data->clear(); - for (const StatsLogEventWrapper& it : returned_value) { - LogEvent::createLogEvents(it, *data); - } - VLOG("CarStatsPuller::pull succeeded for %d", mTagId); - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/GpuStatsPuller.cpp b/cmds/statsd/src/external/GpuStatsPuller.cpp deleted file mode 100644 index 0d3aca05e0e5..000000000000 --- a/cmds/statsd/src/external/GpuStatsPuller.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2019 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 "GpuStatsPuller.h" - -#include <binder/IServiceManager.h> -#include <graphicsenv/GpuStatsInfo.h> -#include <graphicsenv/IGpuService.h> - -#include "logd/LogEvent.h" - -#include "stats_log_util.h" -#include "statslog.h" - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoReader; - -GpuStatsPuller::GpuStatsPuller(const int tagId) : StatsPuller(tagId) { -} - -static sp<IGpuService> getGpuService() { - const sp<IBinder> binder = defaultServiceManager()->checkService(String16("gpu")); - if (!binder) { - ALOGE("Failed to get gpu service"); - return nullptr; - } - - return interface_cast<IGpuService>(binder); -} - -static bool pullGpuStatsGlobalInfo(const sp<IGpuService>& gpuService, - std::vector<std::shared_ptr<LogEvent>>* data) { - std::vector<GpuStatsGlobalInfo> stats; - status_t status = gpuService->getGpuStatsGlobalInfo(&stats); - if (status != OK) { - return false; - } - - data->clear(); - data->reserve(stats.size()); - for (const auto& info : stats) { - std::shared_ptr<LogEvent> event = make_shared<LogEvent>( - android::util::GPU_STATS_GLOBAL_INFO, getWallClockNs(), getElapsedRealtimeNs()); - if (!event->write(info.driverPackageName)) return false; - if (!event->write(info.driverVersionName)) return false; - if (!event->write((int64_t)info.driverVersionCode)) return false; - if (!event->write(info.driverBuildTime)) return false; - if (!event->write((int64_t)info.glLoadingCount)) return false; - if (!event->write((int64_t)info.glLoadingFailureCount)) return false; - if (!event->write((int64_t)info.vkLoadingCount)) return false; - if (!event->write((int64_t)info.vkLoadingFailureCount)) return false; - if (!event->write(info.vulkanVersion)) return false; - if (!event->write(info.cpuVulkanVersion)) return false; - if (!event->write(info.glesVersion)) return false; - if (!event->write((int64_t)info.angleLoadingCount)) return false; - if (!event->write((int64_t)info.angleLoadingFailureCount)) return false; - event->init(); - data->emplace_back(event); - } - - return true; -} - -static bool pullGpuStatsAppInfo(const sp<IGpuService>& gpuService, - std::vector<std::shared_ptr<LogEvent>>* data) { - std::vector<GpuStatsAppInfo> stats; - status_t status = gpuService->getGpuStatsAppInfo(&stats); - if (status != OK) { - return false; - } - - data->clear(); - data->reserve(stats.size()); - for (const auto& info : stats) { - std::shared_ptr<LogEvent> event = make_shared<LogEvent>( - android::util::GPU_STATS_APP_INFO, getWallClockNs(), getElapsedRealtimeNs()); - if (!event->write(info.appPackageName)) return false; - if (!event->write((int64_t)info.driverVersionCode)) return false; - if (!event->write(int64VectorToProtoByteString(info.glDriverLoadingTime))) return false; - if (!event->write(int64VectorToProtoByteString(info.vkDriverLoadingTime))) return false; - if (!event->write(int64VectorToProtoByteString(info.angleDriverLoadingTime))) return false; - if (!event->write(info.cpuVulkanInUse)) return false; - event->init(); - data->emplace_back(event); - } - - return true; -} - -bool GpuStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) { - const sp<IGpuService> gpuService = getGpuService(); - if (!gpuService) { - return false; - } - - switch (mTagId) { - case android::util::GPU_STATS_GLOBAL_INFO: - return pullGpuStatsGlobalInfo(gpuService, data); - case android::util::GPU_STATS_APP_INFO: - return pullGpuStatsAppInfo(gpuService, data); - default: - break; - } - - return false; -} - -static std::string protoOutputStreamToByteString(ProtoOutputStream& proto) { - if (!proto.size()) return ""; - - std::string byteString; - sp<ProtoReader> reader = proto.data(); - while (reader->readBuffer() != nullptr) { - const size_t toRead = reader->currentToRead(); - byteString.append((char*)reader->readBuffer(), toRead); - reader->move(toRead); - } - - if (byteString.size() != proto.size()) return ""; - - return byteString; -} - -std::string int64VectorToProtoByteString(const std::vector<int64_t>& value) { - if (value.empty()) return ""; - - ProtoOutputStream proto; - for (const auto& ele : value) { - proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | - 1 /* field id */, - (long long)ele); - } - - return protoOutputStreamToByteString(proto); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/GpuStatsPuller.h b/cmds/statsd/src/external/GpuStatsPuller.h deleted file mode 100644 index 2da199c51e0f..000000000000 --- a/cmds/statsd/src/external/GpuStatsPuller.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Pull GpuStats from GpuService. - */ -class GpuStatsPuller : public StatsPuller { -public: - explicit GpuStatsPuller(const int tagId); - bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override; -}; - -// convert a int64_t vector into a byte string for proto message like: -// message RepeatedInt64Wrapper { -// repeated int64 value = 1; -// } -std::string int64VectorToProtoByteString(const std::vector<int64_t>& value); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/Perfetto.h b/cmds/statsd/src/external/Perfetto.h index ab2c1954e2a0..095782a49f9b 100644 --- a/cmds/statsd/src/external/Perfetto.h +++ b/cmds/statsd/src/external/Perfetto.h @@ -16,10 +16,6 @@ #pragma once -#include <android/os/StatsLogEventWrapper.h> - -using android::os::StatsLogEventWrapper; - namespace android { namespace os { namespace statsd { diff --git a/cmds/statsd/src/external/PowerStatsPuller.cpp b/cmds/statsd/src/external/PowerStatsPuller.cpp deleted file mode 100644 index dc69b78f0329..000000000000 --- a/cmds/statsd/src/external/PowerStatsPuller.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include <android/hardware/power/stats/1.0/IPowerStats.h> - -#include <vector> - -#include "PowerStatsPuller.h" -#include "statslog.h" -#include "stats_log_util.h" - -using android::hardware::hidl_vec; -using android::hardware::power::stats::V1_0::IPowerStats; -using android::hardware::power::stats::V1_0::EnergyData; -using android::hardware::power::stats::V1_0::RailInfo; -using android::hardware::power::stats::V1_0::Status; -using android::hardware::Return; -using android::hardware::Void; - -using std::make_shared; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHal = nullptr; -static std::mutex gPowerStatsHalMutex; -static bool gPowerStatsExist = true; // Initialized to ensure making a first attempt. -static std::vector<RailInfo> gRailInfo; - -struct PowerStatsPullerDeathRecipient : virtual public hardware::hidl_death_recipient { - virtual void serviceDied(uint64_t cookie, - const wp<android::hidl::base::V1_0::IBase>& who) override { - // The HAL just died. Reset all handles to HAL services. - std::lock_guard<std::mutex> lock(gPowerStatsHalMutex); - gPowerStatsHal = nullptr; - } -}; - -static sp<PowerStatsPullerDeathRecipient> gDeathRecipient = new PowerStatsPullerDeathRecipient(); - -static bool getPowerStatsHalLocked() { - if (gPowerStatsHal == nullptr && gPowerStatsExist) { - gPowerStatsHal = android::hardware::power::stats::V1_0::IPowerStats::getService(); - if (gPowerStatsHal == nullptr) { - ALOGW("Couldn't load power.stats HAL service"); - gPowerStatsExist = false; - } else { - // Link death recipient to power.stats service handle - hardware::Return<bool> linked = gPowerStatsHal->linkToDeath(gDeathRecipient, 0); - if (!linked.isOk()) { - ALOGE("Transaction error in linking to power.stats HAL death: %s", - linked.description().c_str()); - gPowerStatsHal = nullptr; - return false; - } else if (!linked) { - ALOGW("Unable to link to power.stats HAL death notifications"); - // We should still continue even though linking failed - } - } - } - return gPowerStatsHal != nullptr; -} - -PowerStatsPuller::PowerStatsPuller() : StatsPuller(android::util::ON_DEVICE_POWER_MEASUREMENT) { -} - -bool PowerStatsPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - std::lock_guard<std::mutex> lock(gPowerStatsHalMutex); - - if (!getPowerStatsHalLocked()) { - return false; - } - - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - - data->clear(); - - // Pull getRailInfo if necessary - if (gRailInfo.empty()) { - bool resultSuccess = true; - Return<void> ret = gPowerStatsHal->getRailInfo( - [&resultSuccess](const hidl_vec<RailInfo> &list, Status status) { - resultSuccess = (status == Status::SUCCESS || status == Status::NOT_SUPPORTED); - if (status != Status::SUCCESS) return; - - gRailInfo.reserve(list.size()); - for (size_t i = 0; i < list.size(); ++i) { - gRailInfo.push_back(list[i]); - } - }); - if (!resultSuccess || !ret.isOk()) { - ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str()); - gPowerStatsHal = nullptr; - return false; - } - // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again. - if (gRailInfo.empty()) { - ALOGE("power.stats has no rail information"); - gPowerStatsExist = false; // No rail info, so never try again. - gPowerStatsHal = nullptr; - return false; - } - } - - // Pull getEnergyData and write the data out - const hidl_vec<uint32_t> desiredRailIndices; // Empty vector indicates we want all. - bool resultSuccess = true; - Return<void> ret = gPowerStatsHal->getEnergyData(desiredRailIndices, - [&data, wallClockTimestampNs, elapsedTimestampNs, &resultSuccess] - (hidl_vec<EnergyData> energyDataList, Status status) { - resultSuccess = (status == Status::SUCCESS); - if (!resultSuccess) return; - - for (size_t i = 0; i < energyDataList.size(); i++) { - const EnergyData& energyData = energyDataList[i]; - - if (energyData.index >= gRailInfo.size()) { - ALOGE("power.stats getEnergyData() returned an invalid rail index %u.", - energyData.index); - resultSuccess = false; - return; - } - const RailInfo& rail = gRailInfo[energyData.index]; - - auto ptr = make_shared<LogEvent>(android::util::ON_DEVICE_POWER_MEASUREMENT, - wallClockTimestampNs, elapsedTimestampNs); - ptr->write(rail.subsysName); - ptr->write(rail.railName); - ptr->write(energyData.timestamp); - ptr->write(energyData.energy); - ptr->init(); - data->push_back(ptr); - - VLOG("power.stat: %s.%s: %llu, %llu", - rail.subsysName.c_str(), - rail.railName.c_str(), - (unsigned long long)energyData.timestamp, - (unsigned long long)energyData.energy); - } - }); - if (!resultSuccess || !ret.isOk()) { - ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str()); - gPowerStatsHal = nullptr; - return false; - } - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.h b/cmds/statsd/src/external/PullResultReceiver.cpp index 2e133207f01d..8aa4792dc179 100644 --- a/cmds/statsd/src/external/StatsCompanionServicePuller.h +++ b/cmds/statsd/src/external/PullResultReceiver.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2019 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. @@ -14,26 +14,25 @@ * limitations under the License. */ -#pragma once - -#include <utils/String16.h> -#include "StatsPuller.h" +#include "PullResultReceiver.h" namespace android { namespace os { namespace statsd { -class StatsCompanionServicePuller : public StatsPuller { -public: - explicit StatsCompanionServicePuller(int tagId); +PullResultReceiver::PullResultReceiver( + std::function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCb) + : pullFinishCallback(std::move(pullFinishCb)) { +} - void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) override; +Status PullResultReceiver::pullFinished(int32_t atomTag, bool success, + const vector<StatsEventParcel>& output) { + pullFinishCallback(atomTag, success, output); + return Status::ok(); +} -private: - Mutex mStatsCompanionServiceLock; - sp<IStatsCompanionService> mStatsCompanionService = nullptr; - bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override; -}; +PullResultReceiver::~PullResultReceiver() { +} } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/external/PullResultReceiver.h b/cmds/statsd/src/external/PullResultReceiver.h new file mode 100644 index 000000000000..ceaae801b2d5 --- /dev/null +++ b/cmds/statsd/src/external/PullResultReceiver.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 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 <aidl/android/os/BnPullAtomResultReceiver.h> +#include <aidl/android/util/StatsEventParcel.h> + +using namespace std; + +using Status = ::ndk::ScopedAStatus; +using aidl::android::os::BnPullAtomResultReceiver; +using aidl::android::util::StatsEventParcel; + +namespace android { +namespace os { +namespace statsd { + +class PullResultReceiver : public BnPullAtomResultReceiver { +public: + PullResultReceiver(function<void(int32_t, bool, const vector<StatsEventParcel>&)> + pullFinishCallback); + ~PullResultReceiver(); + + /** + * Binder call for finishing a pull. + */ + Status pullFinished(int32_t atomTag, bool success, + const vector<StatsEventParcel>& output) override; + +private: + function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCallback; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.h b/cmds/statsd/src/external/PullUidProvider.h index 87f5f02614a9..2318c501ea4b 100644 --- a/cmds/statsd/src/external/SubsystemSleepStatePuller.h +++ b/cmds/statsd/src/external/PullUidProvider.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 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. @@ -13,25 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #pragma once -#include <utils/String16.h> +#include <utils/RefBase.h> + #include "StatsPuller.h" +#include "logd/LogEvent.h" namespace android { namespace os { namespace statsd { -/** - * Reads hal for sleep states - */ -class SubsystemSleepStatePuller : public StatsPuller { +class PullUidProvider : virtual public RefBase { public: - SubsystemSleepStatePuller(); + virtual ~PullUidProvider() {} -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; + /** + * @param atomId The atom for which to get the uids. + */ + virtual vector<int32_t> getPullAtomUids(int32_t atomId) = 0; }; } // namespace statsd diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp deleted file mode 100644 index 75b63f4b5f9e..000000000000 --- a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include <android/hardware/health/2.0/IHealth.h> -#include <healthhalutils/HealthHalUtils.h> -#include "external/ResourceHealthManagerPuller.h" -#include "external/StatsPuller.h" - -#include "ResourceHealthManagerPuller.h" -#include "logd/LogEvent.h" -#include "stats_log_util.h" -#include "statslog.h" - -using android::hardware::hidl_vec; -using android::hardware::Return; -using android::hardware::Void; -using android::hardware::health::V2_0::get_health_service; -using android::hardware::health::V2_0::HealthInfo; -using android::hardware::health::V2_0::IHealth; -using android::hardware::health::V2_0::Result; - -using std::make_shared; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -sp<android::hardware::health::V2_0::IHealth> gHealthHal = nullptr; - -bool getHealthHal() { - if (gHealthHal == nullptr) { - gHealthHal = get_health_service(); - } - return gHealthHal != nullptr; -} - -ResourceHealthManagerPuller::ResourceHealthManagerPuller(int tagId) : StatsPuller(tagId) { -} - -// TODO(b/110565992): add other health atoms (eg. Temperature). -bool ResourceHealthManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - if (!getHealthHal()) { - ALOGE("Health Hal not loaded"); - return false; - } - - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - - data->clear(); - bool result_success = true; - - // Get the data from the Health HAL (hardware/interfaces/health/1.0/types.hal). - Return<void> ret = gHealthHal->getHealthInfo([&](Result r, HealthInfo v) { - if (r != Result::SUCCESS) { - result_success = false; - return; - } - if (mTagId == android::util::REMAINING_BATTERY_CAPACITY) { - auto ptr = make_shared<LogEvent>(android::util::REMAINING_BATTERY_CAPACITY, - wallClockTimestampNs, elapsedTimestampNs); - ptr->write(v.legacy.batteryChargeCounter); - ptr->init(); - data->push_back(ptr); - } else if (mTagId == android::util::FULL_BATTERY_CAPACITY) { - auto ptr = make_shared<LogEvent>(android::util::FULL_BATTERY_CAPACITY, - wallClockTimestampNs, elapsedTimestampNs); - ptr->write(v.legacy.batteryFullCharge); - ptr->init(); - data->push_back(ptr); - } else if (mTagId == android::util::BATTERY_VOLTAGE) { - auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE, wallClockTimestampNs, - elapsedTimestampNs); - ptr->write(v.legacy.batteryVoltage); - ptr->init(); - data->push_back(ptr); - } else if (mTagId == android::util::BATTERY_LEVEL) { - auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL, wallClockTimestampNs, - elapsedTimestampNs); - ptr->write(v.legacy.batteryLevel); - ptr->init(); - data->push_back(ptr); - } else if (mTagId == android::util::BATTERY_CYCLE_COUNT) { - auto ptr = make_shared<LogEvent>(android::util::BATTERY_CYCLE_COUNT, - wallClockTimestampNs, elapsedTimestampNs); - ptr->write(v.legacy.batteryCycleCount); - ptr->init(); - data->push_back(ptr); - } else { - ALOGE("Unsupported tag in ResourceHealthManagerPuller: %d", mTagId); - } - }); - if (!result_success || !ret.isOk()) { - ALOGE("getHealthHal() failed: health HAL service not available. Description: %s", - ret.description().c_str()); - if (!ret.isOk() && ret.isDeadObject()) { - gHealthHal = nullptr; - } - return false; - } - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp index d718273e9b85..78e6f094db7e 100644 --- a/cmds/statsd/src/external/StatsCallbackPuller.cpp +++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp @@ -17,43 +17,98 @@ #define DEBUG false // STOPSHIP if true #include "Log.h" -#include <android/os/IStatsPullerCallback.h> - #include "StatsCallbackPuller.h" +#include "PullResultReceiver.h" +#include "StatsPullerManager.h" #include "logd/LogEvent.h" #include "stats_log_util.h" -using namespace android::binder; +#include <aidl/android/util/StatsEventParcel.h> + +using namespace std; + +using Status = ::ndk::ScopedAStatus; +using aidl::android::util::StatsEventParcel; +using ::ndk::SharedRefBase; namespace android { namespace os { namespace statsd { -StatsCallbackPuller::StatsCallbackPuller(int tagId, const sp<IStatsPullerCallback>& callback) : - StatsPuller(tagId), mCallback(callback) { - VLOG("StatsCallbackPuller created for tag %d", tagId); +StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback, + const int64_t coolDownNs, int64_t timeoutNs, + const vector<int> additiveFields) + : StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) { + VLOG("StatsCallbackPuller created for tag %d", tagId); } bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - VLOG("StatsCallbackPuller called for tag %d", mTagId) + VLOG("StatsCallbackPuller called for tag %d", mTagId); if(mCallback == nullptr) { ALOGW("No callback registered"); return false; } - int64_t wallClockTimeNs = getWallClockNs(); - int64_t elapsedTimeNs = getElapsedRealtimeNs(); - vector<StatsLogEventWrapper> returned_value; - Status status = mCallback->pullData(mTagId, elapsedTimeNs, wallClockTimeNs, &returned_value); + + // Shared variables needed in the result receiver. + shared_ptr<mutex> cv_mutex = make_shared<mutex>(); + shared_ptr<condition_variable> cv = make_shared<condition_variable>(); + shared_ptr<bool> pullFinish = make_shared<bool>(false); + shared_ptr<bool> pullSuccess = make_shared<bool>(false); + shared_ptr<vector<shared_ptr<LogEvent>>> sharedData = + make_shared<vector<shared_ptr<LogEvent>>>(); + + shared_ptr<PullResultReceiver> resultReceiver = SharedRefBase::make<PullResultReceiver>( + [cv_mutex, cv, pullFinish, pullSuccess, sharedData]( + int32_t atomTag, bool success, const vector<StatsEventParcel>& output) { + // This is the result of the pull, executing in a statsd binder thread. + // The pull could have taken a long time, and we should only modify + // data (the output param) if the pointer is in scope and the pull did not time out. + { + lock_guard<mutex> lk(*cv_mutex); + for (const StatsEventParcel& parcel: output) { + shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1); + bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(), + parcel.buffer.size()); + if (valid) { + sharedData->push_back(event); + } else { + StatsdStats::getInstance().noteAtomError(event->GetTagId(), + /*pull=*/true); + } + } + *pullSuccess = success; + *pullFinish = true; + } + cv->notify_one(); + }); + + // Initiate the pull. This is a oneway call to a different process, except + // in unit tests. In process calls are not oneway. + Status status = mCallback->onPullAtom(mTagId, resultReceiver); if (!status.isOk()) { - ALOGW("StatsCallbackPuller::pull failed for %d", mTagId); + StatsdStats::getInstance().notePullBinderCallFailed(mTagId); return false; } - data->clear(); - for (const StatsLogEventWrapper& it: returned_value) { - LogEvent::createLogEvents(it, *data); + + { + unique_lock<mutex> unique_lk(*cv_mutex); + // Wait until the pull finishes, or until the pull timeout. + cv->wait_for(unique_lk, chrono::nanoseconds(mPullTimeoutNs), + [pullFinish] { return *pullFinish; }); + if (!*pullFinish) { + // Note: The parent stats puller will also note that there was a timeout and that the + // cache should be cleared. Once we migrate all pullers to this callback, we could + // consolidate the logic. + return true; + } else { + // Only copy the data if we did not timeout and the pull was successful. + if (*pullSuccess) { + *data = std::move(*sharedData); + } + VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId); + return *pullSuccess; + } } - VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId); - return true; } } // namespace statsd diff --git a/cmds/statsd/src/external/StatsCallbackPuller.h b/cmds/statsd/src/external/StatsCallbackPuller.h index 03e78be4d474..e82e8bb532be 100644 --- a/cmds/statsd/src/external/StatsCallbackPuller.h +++ b/cmds/statsd/src/external/StatsCallbackPuller.h @@ -16,21 +16,29 @@ #pragma once -#include <android/os/IStatsPullerCallback.h> - +#include <aidl/android/os/IPullAtomCallback.h> #include "StatsPuller.h" +using aidl::android::os::IPullAtomCallback; +using std::shared_ptr; + namespace android { namespace os { namespace statsd { class StatsCallbackPuller : public StatsPuller { public: - explicit StatsCallbackPuller(int tagId, const sp<IStatsPullerCallback>& callback); + explicit StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback, + const int64_t coolDownNs, const int64_t timeoutNs, + const std::vector<int> additiveFields); private: - bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override; - const sp<IStatsPullerCallback> mCallback; + bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; + const shared_ptr<IPullAtomCallback> mCallback; + + FRIEND_TEST(StatsCallbackPullerTest, PullFail); + FRIEND_TEST(StatsCallbackPullerTest, PullSuccess); + FRIEND_TEST(StatsCallbackPullerTest, PullTimeout); }; } // namespace statsd diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp deleted file mode 100644 index f37d2bedf8c2..000000000000 --- a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp +++ /dev/null @@ -1,78 +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. - */ - -#define DEBUG false -#include "Log.h" - -#include <android/os/IStatsCompanionService.h> -#include <binder/IPCThreadState.h> -#include <private/android_filesystem_config.h> -#include "../stats_log_util.h" -#include "../statscompanion_util.h" -#include "StatsCompanionServicePuller.h" - -using namespace android; -using namespace android::base; -using namespace android::binder; -using namespace android::os; -using std::make_shared; -using std::shared_ptr; -using std::vector; - -namespace android { -namespace os { -namespace statsd { - -// The reading and parsing are implemented in Java. It is not difficult to port over. But for now -// let StatsCompanionService handle that and send the data back. -StatsCompanionServicePuller::StatsCompanionServicePuller(int tagId) : StatsPuller(tagId) { -} - -void StatsCompanionServicePuller::SetStatsCompanionService( - sp<IStatsCompanionService> statsCompanionService) { - AutoMutex _l(mStatsCompanionServiceLock); - sp<IStatsCompanionService> tmpForLock = mStatsCompanionService; - mStatsCompanionService = statsCompanionService; -} - -bool StatsCompanionServicePuller::PullInternal(vector<shared_ptr<LogEvent> >* data) { - sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService; - if (statsCompanionServiceCopy != nullptr) { - vector<StatsLogEventWrapper> returned_value; - Status status = statsCompanionServiceCopy->pullData(mTagId, &returned_value); - if (!status.isOk()) { - ALOGW("StatsCompanionServicePuller::pull failed for %d", mTagId); - StatsdStats::getInstance().noteStatsCompanionPullFailed(mTagId); - if (status.exceptionCode() == Status::Exception::EX_TRANSACTION_FAILED) { - StatsdStats::getInstance().noteStatsCompanionPullBinderTransactionFailed(mTagId); - } - return false; - } - data->clear(); - for (const StatsLogEventWrapper& it : returned_value) { - LogEvent::createLogEvents(it, *data); - } - VLOG("StatsCompanionServicePuller::pull succeeded for %d", mTagId); - return true; - } else { - ALOGW("statsCompanion not found!"); - return false; - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index 9552c0a5e35e..bb5d0a6bab58 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -32,50 +32,64 @@ using std::lock_guard; sp<UidMap> StatsPuller::mUidMap = nullptr; void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; } -StatsPuller::StatsPuller(const int tagId) - : mTagId(tagId), mLastPullTimeNs(0) { +StatsPuller::StatsPuller(const int tagId, const int64_t coolDownNs, const int64_t pullTimeoutNs, + const std::vector<int> additiveFields) + : mTagId(tagId), + mPullTimeoutNs(pullTimeoutNs), + mCoolDownNs(coolDownNs), + mAdditiveFields(additiveFields), + mLastPullTimeNs(0), + mLastEventTimeNs(0) { } -bool StatsPuller::Pull(std::vector<std::shared_ptr<LogEvent>>* data) { +bool StatsPuller::Pull(const int64_t eventTimeNs, std::vector<std::shared_ptr<LogEvent>>* data) { lock_guard<std::mutex> lock(mLock); - int64_t elapsedTimeNs = getElapsedRealtimeNs(); + const int64_t elapsedTimeNs = getElapsedRealtimeNs(); + const int64_t systemUptimeMillis = getSystemUptimeMillis(); StatsdStats::getInstance().notePull(mTagId); - const bool shouldUseCache = elapsedTimeNs - mLastPullTimeNs < - StatsPullerManager::kAllPullAtomInfo.at(mTagId).coolDownNs; + const bool shouldUseCache = + (mLastEventTimeNs == eventTimeNs) || (elapsedTimeNs - mLastPullTimeNs < mCoolDownNs); if (shouldUseCache) { if (mHasGoodData) { (*data) = mCachedData; StatsdStats::getInstance().notePullFromCache(mTagId); + } return mHasGoodData; } - if (mLastPullTimeNs > 0) { StatsdStats::getInstance().updateMinPullIntervalSec( mTagId, (elapsedTimeNs - mLastPullTimeNs) / NS_PER_SEC); } mCachedData.clear(); mLastPullTimeNs = elapsedTimeNs; + mLastEventTimeNs = eventTimeNs; mHasGoodData = PullInternal(&mCachedData); if (!mHasGoodData) { return mHasGoodData; } - const int64_t pullDurationNs = getElapsedRealtimeNs() - elapsedTimeNs; - StatsdStats::getInstance().notePullTime(mTagId, pullDurationNs); - const bool pullTimeOut = - pullDurationNs > StatsPullerManager::kAllPullAtomInfo.at(mTagId).pullTimeoutNs; + const int64_t pullElapsedDurationNs = getElapsedRealtimeNs() - elapsedTimeNs; + const int64_t pullSystemUptimeDurationMillis = getSystemUptimeMillis() - systemUptimeMillis; + StatsdStats::getInstance().notePullTime(mTagId, pullElapsedDurationNs); + const bool pullTimeOut = pullElapsedDurationNs > mPullTimeoutNs; if (pullTimeOut) { // Something went wrong. Discard the data. - clearCacheLocked(); + mCachedData.clear(); mHasGoodData = false; - StatsdStats::getInstance().notePullTimeout(mTagId); + StatsdStats::getInstance().notePullTimeout( + mTagId, pullSystemUptimeDurationMillis, NanoToMillis(pullElapsedDurationNs)); ALOGW("Pull for atom %d exceeds timeout %lld nano seconds.", mTagId, - (long long)pullDurationNs); + (long long)pullElapsedDurationNs); return mHasGoodData; } if (mCachedData.size() > 0) { - mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId); + mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId, mAdditiveFields); + } + + if (mCachedData.empty()) { + VLOG("Data pulled is empty"); + StatsdStats::getInstance().noteEmptyData(mTagId); } (*data) = mCachedData; @@ -95,12 +109,12 @@ int StatsPuller::clearCacheLocked() { int ret = mCachedData.size(); mCachedData.clear(); mLastPullTimeNs = 0; + mLastEventTimeNs = 0; return ret; } int StatsPuller::ClearCacheIfNecessary(int64_t timestampNs) { - if (timestampNs - mLastPullTimeNs > - StatsPullerManager::kAllPullAtomInfo.at(mTagId).coolDownNs) { + if (timestampNs - mLastPullTimeNs > mCoolDownNs) { return clearCache(); } else { return 0; diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h index c83c4f8536ae..470d15e6fbd1 100644 --- a/cmds/statsd/src/external/StatsPuller.h +++ b/cmds/statsd/src/external/StatsPuller.h @@ -16,7 +16,7 @@ #pragma once -#include <android/os/IStatsCompanionService.h> +#include <aidl/android/os/IStatsCompanionService.h> #include <utils/RefBase.h> #include <mutex> #include <vector> @@ -26,13 +26,19 @@ #include "logd/LogEvent.h" #include "puller_util.h" +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; + namespace android { namespace os { namespace statsd { class StatsPuller : public virtual RefBase { public: - explicit StatsPuller(const int tagId); + explicit StatsPuller(const int tagId, + const int64_t coolDownNs = NS_PER_SEC, + const int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs, + const std::vector<int> additiveFields = std::vector<int>()); virtual ~StatsPuller() {} @@ -45,7 +51,7 @@ public: // 2) pull takes longer than mPullTimeoutNs (intrinsic to puller) // If a metric wants to make any change to the data, like timestamps, it // should make a copy as this data may be shared with multiple metrics. - bool Pull(std::vector<std::shared_ptr<LogEvent>>* data); + bool Pull(const int64_t eventTimeNs, std::vector<std::shared_ptr<LogEvent>>* data); // Clear cache immediately int ForceClearCache(); @@ -55,11 +61,18 @@ public: static void SetUidMap(const sp<UidMap>& uidMap); - virtual void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService){}; + virtual void SetStatsCompanionService( + shared_ptr<IStatsCompanionService> statsCompanionService) {}; protected: const int mTagId; + // Max time allowed to pull this atom. + // We cannot reliably kill a pull thread. So we don't terminate the puller. + // The data is discarded if the pull takes longer than this and mHasGoodData + // marked as false. + const int64_t mPullTimeoutNs = StatsdStats::kPullMaxDelayNs; + private: mutable std::mutex mLock; @@ -68,8 +81,24 @@ private: bool mHasGoodData = false; + // Minimum time before this puller does actual pull again. + // Pullers can cause significant impact to system health and battery. + // So that we don't pull too frequently. + // If a pull request comes before cooldown, a cached version from previous pull + // will be returned. + const int64_t mCoolDownNs = 1 * NS_PER_SEC; + + // The field numbers of the fields that need to be summed when merging + // isolated uid with host uid. + const std::vector<int> mAdditiveFields; + int64_t mLastPullTimeNs; + // All pulls happen due to an event (app upgrade, bucket boundary, condition change, etc). + // If multiple pulls need to be done at the same event time, we will always use the cache after + // the first pull. + int64_t mLastEventTimeNs; + // Cache of data from last pull. If next request comes before cool down finishes, // cached data will be returned. // Cached data is cleared when diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 0b0e4f6ee230..8a9ec7456e55 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -17,26 +17,22 @@ #define DEBUG false #include "Log.h" -#include <android/os/IStatsCompanionService.h> -#include <android/os/IStatsPullerCallback.h> +#include "StatsPullerManager.h" + #include <cutils/log.h> #include <math.h> #include <stdint.h> + #include <algorithm> +#include <iostream> + #include "../StatsService.h" #include "../logd/LogEvent.h" #include "../stats_log_util.h" #include "../statscompanion_util.h" -#include "CarStatsPuller.h" -#include "GpuStatsPuller.h" -#include "PowerStatsPuller.h" -#include "ResourceHealthManagerPuller.h" #include "StatsCallbackPuller.h" -#include "StatsCompanionServicePuller.h" -#include "StatsPullerManager.h" -#include "SubsystemSleepStatePuller.h" #include "TrainInfoPuller.h" -#include "statslog.h" +#include "statslog_statsd.h" using std::shared_ptr; using std::vector; @@ -45,254 +41,132 @@ namespace android { namespace os { namespace statsd { +// Stores the puller as a wp to avoid holding a reference in case it is unregistered and +// pullAtomCallbackDied is never called. +struct PullAtomCallbackDeathCookie { + PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager, + const PullerKey& pullerKey, const wp<StatsPuller>& puller) : + mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) { + } + + wp<StatsPullerManager> mPullerManager; + PullerKey mPullerKey; + wp<StatsPuller> mPuller; +}; + +void StatsPullerManager::pullAtomCallbackDied(void* cookie) { + PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie); + sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote(); + if (!thiz) { + return; + } + + const PullerKey& pullerKey = cookie_->mPullerKey; + wp<StatsPuller> puller = cookie_->mPuller; + + // Erase the mapping from the puller key to the puller if the mapping still exists. + // Note that we are removing the StatsPuller object, which internally holds the binder + // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works. + lock_guard<mutex> lock(thiz->mLock); + const auto& it = thiz->kAllPullAtomInfo.find(pullerKey); + if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) { + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag, + /*registered=*/false); + thiz->kAllPullAtomInfo.erase(pullerKey); + } + // The death recipient corresponding to this specific IPullAtomCallback can never + // be triggered again, so free up resources. + delete cookie_; +} + // Values smaller than this may require to update the alarm. const int64_t NO_ALARM_UPDATE = INT64_MAX; -std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { - // wifi_bytes_transfer - {android::util::WIFI_BYTES_TRANSFER, - {.additiveFields = {2, 3, 4, 5}, - .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}}, - // wifi_bytes_transfer_by_fg_bg - {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG, - {.additiveFields = {3, 4, 5, 6}, - .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}}, - // mobile_bytes_transfer - {android::util::MOBILE_BYTES_TRANSFER, - {.additiveFields = {2, 3, 4, 5}, - .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}}, - // mobile_bytes_transfer_by_fg_bg - {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG, - {.additiveFields = {3, 4, 5, 6}, - .puller = - new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}}, - // bluetooth_bytes_transfer - {android::util::BLUETOOTH_BYTES_TRANSFER, - {.additiveFields = {2, 3}, - .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}}, - // kernel_wakelock - {android::util::KERNEL_WAKELOCK, - {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}}, - // subsystem_sleep_state - {android::util::SUBSYSTEM_SLEEP_STATE, {.puller = new SubsystemSleepStatePuller()}}, - // on_device_power_measurement - {android::util::ON_DEVICE_POWER_MEASUREMENT, {.puller = new PowerStatsPuller()}}, - // cpu_time_per_freq - {android::util::CPU_TIME_PER_FREQ, - {.additiveFields = {3}, - .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}}, - // cpu_time_per_uid - {android::util::CPU_TIME_PER_UID, - {.additiveFields = {2, 3}, - .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}}, - // cpu_time_per_uid_freq - // the throttling is 3sec, handled in - // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader - {android::util::CPU_TIME_PER_UID_FREQ, - {.additiveFields = {4}, - .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}}, - // cpu_active_time - // the throttling is 3sec, handled in - // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader - {android::util::CPU_ACTIVE_TIME, - {.additiveFields = {2}, - .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}}, - // cpu_cluster_time - // the throttling is 3sec, handled in - // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader - {android::util::CPU_CLUSTER_TIME, - {.additiveFields = {3}, - .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}}, - // wifi_activity_energy_info - {android::util::WIFI_ACTIVITY_INFO, - {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}}, - // modem_activity_info - {android::util::MODEM_ACTIVITY_INFO, - {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}}, - // bluetooth_activity_info - {android::util::BLUETOOTH_ACTIVITY_INFO, - {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}}, - // system_elapsed_realtime - {android::util::SYSTEM_ELAPSED_REALTIME, - {.coolDownNs = NS_PER_SEC, - .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME), - .pullTimeoutNs = NS_PER_SEC / 2, - }}, - // system_uptime - {android::util::SYSTEM_UPTIME, - {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}}, - // remaining_battery_capacity - {android::util::REMAINING_BATTERY_CAPACITY, - {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}}, - // full_battery_capacity - {android::util::FULL_BATTERY_CAPACITY, - {.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}}, - // battery_voltage - {android::util::BATTERY_VOLTAGE, - {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}}, - // battery_level - {android::util::BATTERY_LEVEL, - {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}}, - // battery_cycle_count - {android::util::BATTERY_CYCLE_COUNT, - {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}}, - // process_memory_state - {android::util::PROCESS_MEMORY_STATE, - {.additiveFields = {4, 5, 6, 7, 8, 9}, - .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}}, - // native_process_memory_state - {android::util::NATIVE_PROCESS_MEMORY_STATE, - {.additiveFields = {3, 4, 5, 6, 8}, - .puller = new StatsCompanionServicePuller(android::util::NATIVE_PROCESS_MEMORY_STATE)}}, - // process_memory_high_water_mark - {android::util::PROCESS_MEMORY_HIGH_WATER_MARK, - {.additiveFields = {3}, - .puller = - new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}}, - // system_ion_heap_size - {android::util::SYSTEM_ION_HEAP_SIZE, - {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}}, - // process_system_ion_heap_size - {android::util::PROCESS_SYSTEM_ION_HEAP_SIZE, - {.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}}, - // temperature - {android::util::TEMPERATURE, - {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}}, - // cooling_device - {android::util::COOLING_DEVICE, - {.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}}, - // binder_calls - {android::util::BINDER_CALLS, - {.additiveFields = {4, 5, 6, 8, 12}, - .puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}}, - // binder_calls_exceptions - {android::util::BINDER_CALLS_EXCEPTIONS, - {.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}}, - // looper_stats - {android::util::LOOPER_STATS, - {.additiveFields = {5, 6, 7, 8, 9}, - .puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}}, - // Disk Stats - {android::util::DISK_STATS, - {.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}}, - // Directory usage - {android::util::DIRECTORY_USAGE, - {.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}}, - // Size of app's code, data, and cache - {android::util::APP_SIZE, - {.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}}, - // Size of specific categories of files. Eg. Music. - {android::util::CATEGORY_SIZE, - {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}}, - // Number of fingerprints enrolled for each user. - {android::util::NUM_FINGERPRINTS_ENROLLED, - {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}}, - // Number of faces enrolled for each user. - {android::util::NUM_FACES_ENROLLED, - {.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}}, - // ProcStats. - {android::util::PROC_STATS, - {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}}, - // ProcStatsPkgProc. - {android::util::PROC_STATS_PKG_PROC, - {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}}, - // Disk I/O stats per uid. - {android::util::DISK_IO, - {.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, - .coolDownNs = 3 * NS_PER_SEC, - .puller = new StatsCompanionServicePuller(android::util::DISK_IO)}}, - // PowerProfile constants for power model calculations. - {android::util::POWER_PROFILE, - {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}}, - // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses. - {android::util::PROCESS_CPU_TIME, - {.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/, - .puller = new StatsCompanionServicePuller(android::util::PROCESS_CPU_TIME)}}, - {android::util::CPU_TIME_PER_THREAD_FREQ, - {.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21}, - .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}}, - // DeviceCalculatedPowerUse. - {android::util::DEVICE_CALCULATED_POWER_USE, - {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}}, - // DeviceCalculatedPowerBlameUid. - {android::util::DEVICE_CALCULATED_POWER_BLAME_UID, - {.puller = new StatsCompanionServicePuller( - android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}}, - // DeviceCalculatedPowerBlameOther. - {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER, - {.puller = new StatsCompanionServicePuller( - android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}}, - // DebugElapsedClock. - {android::util::DEBUG_ELAPSED_CLOCK, - {.additiveFields = {1, 2, 3, 4}, - .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}}, - // DebugFailingElapsedClock. - {android::util::DEBUG_FAILING_ELAPSED_CLOCK, - {.additiveFields = {1, 2, 3, 4}, - .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}}, - // BuildInformation. - {android::util::BUILD_INFORMATION, - {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}}, - // RoleHolder. - {android::util::ROLE_HOLDER, - {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}}, - // PermissionState. - {android::util::DANGEROUS_PERMISSION_STATE, - {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}}, - // TrainInfo. - {android::util::TRAIN_INFO, {.puller = new TrainInfoPuller()}}, - // TimeZoneDataInfo. - {android::util::TIME_ZONE_DATA_INFO, - {.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}}, - // ExternalStorageInfo - {android::util::EXTERNAL_STORAGE_INFO, - {.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}}, - // GpuStatsGlobalInfo - {android::util::GPU_STATS_GLOBAL_INFO, - {.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}}, - // GpuStatsAppInfo - {android::util::GPU_STATS_APP_INFO, - {.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}}, - // AppsOnExternalStorageInfo - {android::util::APPS_ON_EXTERNAL_STORAGE_INFO, - {.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}}, - // Face Settings - {android::util::FACE_SETTINGS, - {.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}}, - // App ops - {android::util::APP_OPS, - {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}}, - // VmsClientStats - {android::util::VMS_CLIENT_STATS, - {.additiveFields = {5, 6, 7, 8, 9, 10}, - .puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}}, - // NotiifcationRemoteViews. - {android::util::NOTIFICATION_REMOTE_VIEWS, - {.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}}, -}; +StatsPullerManager::StatsPullerManager() + : kAllPullAtomInfo({ + // TrainInfo. + {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()}, + }), + mNextPullTimeNs(NO_ALARM_UPDATE), + mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) { +} -StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) { +bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, + vector<shared_ptr<LogEvent>>* data, bool useUids) { + std::lock_guard<std::mutex> _l(mLock); + return PullLocked(tagId, configKey, eventTimeNs, data, useUids); } -bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) { - VLOG("Initiating pulling %d", tagId); +bool StatsPullerManager::Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool useUids) { + std::lock_guard<std::mutex> _l(mLock); + return PullLocked(tagId, uids, eventTimeNs, data, useUids); +} + +bool StatsPullerManager::PullLocked(int tagId, const ConfigKey& configKey, + const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data, + bool useUids) { + vector<int32_t> uids; + if (useUids) { + auto uidProviderIt = mPullUidProviders.find(configKey); + if (uidProviderIt == mPullUidProviders.end()) { + ALOGE("Error pulling tag %d. No pull uid provider for config key %s", tagId, + configKey.ToString().c_str()); + StatsdStats::getInstance().notePullUidProviderNotFound(tagId); + return false; + } + sp<PullUidProvider> pullUidProvider = uidProviderIt->second.promote(); + if (pullUidProvider == nullptr) { + ALOGE("Error pulling tag %d, pull uid provider for config %s is gone.", tagId, + configKey.ToString().c_str()); + StatsdStats::getInstance().notePullUidProviderNotFound(tagId); + return false; + } + uids = pullUidProvider->getPullAtomUids(tagId); + } + return PullLocked(tagId, uids, eventTimeNs, data, useUids); +} - if (kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end()) { - bool ret = kAllPullAtomInfo.find(tagId)->second.puller->Pull(data); - VLOG("pulled %d items", (int)data->size()); - if (!ret) { - StatsdStats::getInstance().notePullFailed(tagId); +bool StatsPullerManager::PullLocked(int tagId, const vector<int32_t>& uids, + const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data, + bool useUids) { + VLOG("Initiating pulling %d", tagId); + if (useUids) { + for (int32_t uid : uids) { + PullerKey key = {.atomTag = tagId, .uid = uid}; + auto pullerIt = kAllPullAtomInfo.find(key); + if (pullerIt != kAllPullAtomInfo.end()) { + bool ret = pullerIt->second->Pull(eventTimeNs, data); + VLOG("pulled %zu items", data->size()); + if (!ret) { + StatsdStats::getInstance().notePullFailed(tagId); + } + return ret; + } } - return ret; + StatsdStats::getInstance().notePullerNotFound(tagId); + ALOGW("StatsPullerManager: Unknown tagId %d", tagId); + return false; // Return early since we don't know what to pull. } else { - VLOG("Unknown tagId %d", tagId); + PullerKey key = {.atomTag = tagId, .uid = -1}; + auto pullerIt = kAllPullAtomInfo.find(key); + if (pullerIt != kAllPullAtomInfo.end()) { + bool ret = pullerIt->second->Pull(eventTimeNs, data); + VLOG("pulled %zu items", data->size()); + if (!ret) { + StatsdStats::getInstance().notePullFailed(tagId); + } + return ret; + } + ALOGW("StatsPullerManager: Unknown tagId %d", tagId); return false; // Return early since we don't know what to pull. } } bool StatsPullerManager::PullerForMatcherExists(int tagId) const { - // Vendor pulled atoms might be registered after we parse the config. - return isVendorPulledAtom(tagId) || kAllPullAtomInfo.find(tagId) != kAllPullAtomInfo.end(); + // Pulled atoms might be registered after we parse the config, so just make sure the id is in + // an appropriate range. + return isVendorPulledAtom(tagId) || isPulledAtom(tagId); } void StatsPullerManager::updateAlarmLocked() { @@ -301,9 +175,9 @@ void StatsPullerManager::updateAlarmLocked() { return; } - sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService; - if (statsCompanionServiceCopy != nullptr) { - statsCompanionServiceCopy->setPullingAlarm(mNextPullTimeNs / 1000000); + // TODO(b/151045771): do not hold a lock while making a binder call + if (mStatsCompanionService != nullptr) { + mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000); } else { VLOG("StatsCompanionService not available. Alarm not set."); } @@ -311,22 +185,23 @@ void StatsPullerManager::updateAlarmLocked() { } void StatsPullerManager::SetStatsCompanionService( - sp<IStatsCompanionService> statsCompanionService) { - AutoMutex _l(mLock); - sp<IStatsCompanionService> tmpForLock = mStatsCompanionService; + shared_ptr<IStatsCompanionService> statsCompanionService) { + std::lock_guard<std::mutex> _l(mLock); + shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService; mStatsCompanionService = statsCompanionService; for (const auto& pulledAtom : kAllPullAtomInfo) { - pulledAtom.second.puller->SetStatsCompanionService(statsCompanionService); + pulledAtom.second->SetStatsCompanionService(statsCompanionService); } if (mStatsCompanionService != nullptr) { updateAlarmLocked(); } } -void StatsPullerManager::RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, - int64_t nextPullTimeNs, int64_t intervalNs) { - AutoMutex _l(mLock); - auto& receivers = mReceivers[tagId]; +void StatsPullerManager::RegisterReceiver(int tagId, const ConfigKey& configKey, + wp<PullDataReceiver> receiver, int64_t nextPullTimeNs, + int64_t intervalNs) { + std::lock_guard<std::mutex> _l(mLock); + auto& receivers = mReceivers[{.atomTag = tagId, .configKey = configKey}]; for (auto it = receivers.begin(); it != receivers.end(); it++) { if (it->receiver == receiver) { VLOG("Receiver already registered of %d", (int)receivers.size()); @@ -358,13 +233,15 @@ void StatsPullerManager::RegisterReceiver(int tagId, wp<PullDataReceiver> receiv VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size()); } -void StatsPullerManager::UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver) { - AutoMutex _l(mLock); - if (mReceivers.find(tagId) == mReceivers.end()) { +void StatsPullerManager::UnRegisterReceiver(int tagId, const ConfigKey& configKey, + wp<PullDataReceiver> receiver) { + std::lock_guard<std::mutex> _l(mLock); + auto receiversIt = mReceivers.find({.atomTag = tagId, .configKey = configKey}); + if (receiversIt == mReceivers.end()) { VLOG("Unknown pull code or no receivers: %d", tagId); return; } - auto& receivers = mReceivers.find(tagId)->second; + std::list<ReceiverInfo>& receivers = receiversIt->second; for (auto it = receivers.begin(); it != receivers.end(); it++) { if (receiver == it->receiver) { receivers.erase(it); @@ -374,16 +251,30 @@ void StatsPullerManager::UnRegisterReceiver(int tagId, wp<PullDataReceiver> rece } } +void StatsPullerManager::RegisterPullUidProvider(const ConfigKey& configKey, + wp<PullUidProvider> provider) { + std::lock_guard<std::mutex> _l(mLock); + mPullUidProviders[configKey] = provider; +} + +void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey, + wp<PullUidProvider> provider) { + std::lock_guard<std::mutex> _l(mLock); + const auto& it = mPullUidProviders.find(configKey); + if (it != mPullUidProviders.end() && it->second == provider) { + mPullUidProviders.erase(it); + } +} + void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { - AutoMutex _l(mLock); + std::lock_guard<std::mutex> _l(mLock); int64_t wallClockNs = getWallClockNs(); int64_t minNextPullTimeNs = NO_ALARM_UPDATE; - vector<pair<int, vector<ReceiverInfo*>>> needToPull = - vector<pair<int, vector<ReceiverInfo*>>>(); + vector<pair<const ReceiverKey*, vector<ReceiverInfo*>>> needToPull; for (auto& pair : mReceivers) { - vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>(); + vector<ReceiverInfo*> receivers; if (pair.second.size() != 0) { for (ReceiverInfo& receiverInfo : pair.second) { if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) { @@ -395,18 +286,15 @@ void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { } } if (receivers.size() > 0) { - needToPull.push_back(make_pair(pair.first, receivers)); + needToPull.push_back(make_pair(&pair.first, receivers)); } } } - for (const auto& pullInfo : needToPull) { vector<shared_ptr<LogEvent>> data; - bool pullSuccess = Pull(pullInfo.first, &data); - if (pullSuccess) { - StatsdStats::getInstance().notePullDelay( - pullInfo.first, getElapsedRealtimeNs() - elapsedTimeNs); - } else { + bool pullSuccess = PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey, + elapsedTimeNs, &data); + if (!pullSuccess) { VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs); } @@ -448,7 +336,7 @@ void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { int StatsPullerManager::ForceClearPullerCache() { int totalCleared = 0; for (const auto& pulledAtom : kAllPullAtomInfo) { - totalCleared += pulledAtom.second.puller->ForceClearCache(); + totalCleared += pulledAtom.second->ForceClearCache(); } return totalCleared; } @@ -456,32 +344,45 @@ int StatsPullerManager::ForceClearPullerCache() { int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) { int totalCleared = 0; for (const auto& pulledAtom : kAllPullAtomInfo) { - totalCleared += pulledAtom.second.puller->ClearCacheIfNecessary(timestampNs); + totalCleared += pulledAtom.second->ClearCacheIfNecessary(timestampNs); } return totalCleared; } -void StatsPullerManager::RegisterPullerCallback(int32_t atomTag, - const sp<IStatsPullerCallback>& callback) { - AutoMutex _l(mLock); - // Platform pullers cannot be changed. - if (!isVendorPulledAtom(atomTag)) { - VLOG("RegisterPullerCallback: atom tag %d is not vendor pulled", atomTag); +void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag, + const int64_t coolDownNs, const int64_t timeoutNs, + const vector<int32_t>& additiveFields, + const shared_ptr<IPullAtomCallback>& callback, + bool useUid) { + std::lock_guard<std::mutex> _l(mLock); + VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag); + + if (callback == nullptr) { + ALOGW("SetPullAtomCallback called with null callback for atom %d.", atomTag); return; } - VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag); + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true); - kAllPullAtomInfo[atomTag] = {.puller = new StatsCallbackPuller(atomTag, callback)}; + int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs; + int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs; + + sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs, + actualTimeoutNs, additiveFields); + PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1}; + AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(), + new PullAtomCallbackDeathCookie(this, key, puller)); + kAllPullAtomInfo[key] = puller; } -void StatsPullerManager::UnregisterPullerCallback(int32_t atomTag) { - AutoMutex _l(mLock); - // Platform pullers cannot be changed. - if (!isVendorPulledAtom(atomTag)) { - return; +void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag, + bool useUids) { + std::lock_guard<std::mutex> _l(mLock); + PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1}; + if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) { + StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, + /*registered=*/false); + kAllPullAtomInfo.erase(key); } - StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false); - kAllPullAtomInfo.erase(atomTag); } } // namespace statsd diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h index 6791f6656f09..194a0f5edba8 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -16,40 +16,48 @@ #pragma once -#include <android/os/IStatsCompanionService.h> -#include <android/os/IStatsPullerCallback.h> -#include <binder/IServiceManager.h> +#include <aidl/android/os/IPullAtomCallback.h> +#include <aidl/android/os/IStatsCompanionService.h> #include <utils/RefBase.h> -#include <utils/threads.h> + #include <list> #include <vector> + #include "PullDataReceiver.h" +#include "PullUidProvider.h" #include "StatsPuller.h" #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" +#include "packages/UidMap.h" + +using aidl::android::os::IPullAtomCallback; +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; namespace android { namespace os { namespace statsd { -typedef struct { - // The field numbers of the fields that need to be summed when merging - // isolated uid with host uid. - std::vector<int> additiveFields; - // Minimum time before this puller does actual pull again. - // Pullers can cause significant impact to system health and battery. - // So that we don't pull too frequently. - // If a pull request comes before cooldown, a cached version from previous pull - // will be returned. - int64_t coolDownNs = 1 * NS_PER_SEC; - // The actual puller - sp<StatsPuller> puller; - // Max time allowed to pull this atom. - // We cannot reliably kill a pull thread. So we don't terminate the puller. - // The data is discarded if the pull takes longer than this and mHasGoodData - // marked as false. - int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs; -} PullAtomInfo; +typedef struct PullerKey { + // The uid of the process that registers this puller. + const int uid = -1; + // The atom that this puller is for. + const int atomTag; + + bool operator<(const PullerKey& that) const { + if (uid < that.uid) { + return true; + } + if (uid > that.uid) { + return false; + } + return atomTag < that.atomTag; + }; + + bool operator==(const PullerKey& that) const { + return uid == that.uid && atomTag == that.atomTag; + }; +} PullerKey; class StatsPullerManager : public virtual RefBase { public: @@ -58,13 +66,24 @@ public: virtual ~StatsPullerManager() { } + // Registers a receiver for tagId. It will be pulled on the nextPullTimeNs // and then every intervalNs thereafter. - virtual void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, int64_t nextPullTimeNs, + virtual void RegisterReceiver(int tagId, const ConfigKey& configKey, + wp<PullDataReceiver> receiver, int64_t nextPullTimeNs, int64_t intervalNs); // Stop listening on a tagId. - virtual void UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver); + virtual void UnRegisterReceiver(int tagId, const ConfigKey& configKey, + wp<PullDataReceiver> receiver); + + // Registers a pull uid provider for the config key. When pulling atoms, it will be used to + // determine which uids to pull from. + virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp<PullUidProvider> provider); + + // Unregister a pull uid provider. + virtual void UnregisterPullUidProvider(const ConfigKey& configKey, + wp<PullUidProvider> provider); // Verify if we know how to pull for this matcher bool PullerForMatcherExists(int tagId) const; @@ -78,9 +97,16 @@ public: // Returns false when // 1) the pull fails // 2) pull takes longer than mPullTimeoutNs (intrinsic to puller) + // 3) Either a PullUidProvider was not registered for the config, or the there was no puller + // registered for any of the uids for this atom. // If the metric wants to make any change to the data, like timestamps, they // should make a copy as this data may be shared with multiple metrics. - virtual bool Pull(int tagId, vector<std::shared_ptr<LogEvent>>* data); + virtual bool Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool useUids = true); + + // Same as above, but directly specify the allowed uids to pull from. + virtual bool Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool useUids = true); // Clear pull data cache immediately. int ForceClearPullerCache(); @@ -88,17 +114,31 @@ public: // Clear pull data cache if it is beyond respective cool down time. int ClearPullerCacheIfNecessary(int64_t timestampNs); - void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService); + void SetStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService); - void RegisterPullerCallback(int32_t atomTag, - const sp<IStatsPullerCallback>& callback); + void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs, + const int64_t timeoutNs, const vector<int32_t>& additiveFields, + const shared_ptr<IPullAtomCallback>& callback, + bool useUid = true); - void UnregisterPullerCallback(int32_t atomTag); + void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = true); - static std::map<int, PullAtomInfo> kAllPullAtomInfo; + std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo; private: - sp<IStatsCompanionService> mStatsCompanionService = nullptr; + const static int64_t kMinCoolDownNs = NS_PER_SEC; + const static int64_t kMaxTimeoutNs = 10 * NS_PER_SEC; + shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr; + + // A struct containing an atom id and a Config Key + typedef struct ReceiverKey { + const int atomTag; + const ConfigKey configKey; + + inline bool operator<(const ReceiverKey& that) const { + return atomTag == that.atomTag ? configKey < that.configKey : atomTag < that.atomTag; + } + } ReceiverKey; typedef struct { int64_t nextPullTimeNs; @@ -106,16 +146,34 @@ private: wp<PullDataReceiver> receiver; } ReceiverInfo; - // mapping from simple matcher tagId to receivers - std::map<int, std::list<ReceiverInfo>> mReceivers; + // mapping from Receiver Key to receivers + std::map<ReceiverKey, std::list<ReceiverInfo>> mReceivers; + + // mapping from Config Key to the PullUidProvider for that config + std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders; + + bool PullLocked(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool useUids = true); + + bool PullLocked(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool useUids); // locks for data receiver and StatsCompanionService changes - Mutex mLock; + std::mutex mLock; void updateAlarmLocked(); int64_t mNextPullTimeNs; + // Death recipient that is triggered when the process holding the IPullAtomCallback has died. + ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient; + + /** + * Death recipient callback that is called when a pull atom callback dies. + * The cookie is a pointer to a PullAtomCallbackDeathCookie. + */ + static void pullAtomCallbackDied(void* cookie); + FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); @@ -123,6 +181,8 @@ private: FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); + + FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate); }; } // namespace statsd diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp deleted file mode 100644 index f6a4aeaa3f9e..000000000000 --- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp +++ /dev/null @@ -1,378 +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. - */ - -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include <android/hardware/power/1.0/IPower.h> -#include <android/hardware/power/1.1/IPower.h> -#include <android/hardware/power/stats/1.0/IPowerStats.h> - -#include <fcntl.h> -#include <hardware/power.h> -#include <hardware_legacy/power.h> -#include <inttypes.h> -#include <semaphore.h> -#include <stddef.h> -#include <stdio.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> -#include "external/SubsystemSleepStatePuller.h" -#include "external/StatsPuller.h" - -#include "SubsystemSleepStatePuller.h" -#include "logd/LogEvent.h" -#include "statslog.h" -#include "stats_log_util.h" - -using android::hardware::hidl_vec; -using android::hardware::power::V1_0::IPower; -using android::hardware::power::V1_0::PowerStatePlatformSleepState; -using android::hardware::power::V1_0::PowerStateVoter; -using android::hardware::power::V1_1::PowerStateSubsystem; -using android::hardware::power::V1_1::PowerStateSubsystemSleepState; -using android::hardware::power::stats::V1_0::PowerEntityInfo; -using android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult; -using android::hardware::power::stats::V1_0::PowerEntityStateSpace; - -using android::hardware::Return; -using android::hardware::Void; - -using std::make_shared; -using std::shared_ptr; - -namespace android { -namespace os { -namespace statsd { - -static std::function<bool(vector<shared_ptr<LogEvent>>* data)> gPuller = {}; - -static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr; -static sp<android::hardware::power::V1_1::IPower> gPowerHalV1_1 = nullptr; -static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr; - -static std::unordered_map<uint32_t, std::string> gEntityNames = {}; -static std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> gStateNames = {}; - -static std::mutex gPowerHalMutex; - -// The caller must be holding gPowerHalMutex. -static void deinitPowerStatsLocked() { - gPowerHalV1_0 = nullptr; - gPowerHalV1_1 = nullptr; - gPowerStatsHalV1_0 = nullptr; -} - -struct SubsystemSleepStatePullerDeathRecipient : virtual public hardware::hidl_death_recipient { - virtual void serviceDied(uint64_t cookie, - const wp<android::hidl::base::V1_0::IBase>& who) override { - - // The HAL just died. Reset all handles to HAL services. - std::lock_guard<std::mutex> lock(gPowerHalMutex); - deinitPowerStatsLocked(); - } -}; - -static sp<SubsystemSleepStatePullerDeathRecipient> gDeathRecipient = - new SubsystemSleepStatePullerDeathRecipient(); - -SubsystemSleepStatePuller::SubsystemSleepStatePuller() : - StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) { -} - -// The caller must be holding gPowerHalMutex. -static bool checkResultLocked(const Return<void> &ret, const char* function) { - if (!ret.isOk()) { - ALOGE("%s failed: requested HAL service not available. Description: %s", - function, ret.description().c_str()); - if (ret.isDeadObject()) { - deinitPowerStatsLocked(); - } - return false; - } - return true; -} - -// The caller must be holding gPowerHalMutex. -// gPowerStatsHalV1_0 must not be null -static bool initializePowerStats() { - using android::hardware::power::stats::V1_0::Status; - - // Clear out previous content if we are re-initializing - gEntityNames.clear(); - gStateNames.clear(); - - Return<void> ret; - ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) { - if (status != Status::SUCCESS) { - ALOGE("Error getting power entity info"); - return; - } - - // construct lookup table of powerEntityId to power entity name - for (auto info : infos) { - gEntityNames.emplace(info.powerEntityId, info.powerEntityName); - } - }); - if (!checkResultLocked(ret, __func__)) { - return false; - } - - ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) { - if (status != Status::SUCCESS) { - ALOGE("Error getting state info"); - return; - } - - // construct lookup table of powerEntityId, powerEntityStateId to power entity state name - for (auto stateSpace : stateSpaces) { - std::unordered_map<uint32_t, std::string> stateNames = {}; - for (auto state : stateSpace.states) { - stateNames.emplace(state.powerEntityStateId, - state.powerEntityStateName); - } - gStateNames.emplace(stateSpace.powerEntityId, stateNames); - } - }); - if (!checkResultLocked(ret, __func__)) { - return false; - } - - return (!gEntityNames.empty()) && (!gStateNames.empty()); -} - -// The caller must be holding gPowerHalMutex. -static bool getPowerStatsHalLocked() { - if(gPowerStatsHalV1_0 == nullptr) { - gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService(); - if (gPowerStatsHalV1_0 == nullptr) { - ALOGE("Unable to get power.stats HAL service."); - return false; - } - - // Link death recipient to power.stats service handle - hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0); - if (!linked.isOk()) { - ALOGE("Transaction error in linking to power.stats HAL death: %s", - linked.description().c_str()); - deinitPowerStatsLocked(); - return false; - } else if (!linked) { - ALOGW("Unable to link to power.stats HAL death notifications"); - // We should still continue even though linking failed - } - return initializePowerStats(); - } - return true; -} - -// The caller must be holding gPowerHalMutex. -static bool getIPowerStatsDataLocked(vector<shared_ptr<LogEvent>>* data) { - using android::hardware::power::stats::V1_0::Status; - - if(!getPowerStatsHalLocked()) { - return false; - } - - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - - // Get power entity state residency data - bool success = false; - Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({}, - [&data, &success, wallClockTimestampNs, elapsedTimestampNs] - (auto results, auto status) { - if (status == Status::NOT_SUPPORTED) { - ALOGW("getPowerEntityStateResidencyData is not supported"); - success = false; - return; - } - - for(auto result : results) { - for(auto stateResidency : result.stateResidencyData) { - auto statePtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - statePtr->write(gEntityNames.at(result.powerEntityId)); - statePtr->write(gStateNames.at(result.powerEntityId) - .at(stateResidency.powerEntityStateId)); - statePtr->write(stateResidency.totalStateEntryCount); - statePtr->write(stateResidency.totalTimeInStateMs); - statePtr->init(); - data->emplace_back(statePtr); - } - } - success = true; - }); - // Intentionally not returning early here. - // bool success determines if this succeeded or not. - checkResultLocked(ret, __func__); - - return success; -} - -// The caller must be holding gPowerHalMutex. -static bool getPowerHalLocked() { - if(gPowerHalV1_0 == nullptr) { - gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService(); - if(gPowerHalV1_0 == nullptr) { - ALOGE("Unable to get power HAL service."); - return false; - } - gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0); - - // Link death recipient to power service handle - hardware::Return<bool> linked = gPowerHalV1_0->linkToDeath(gDeathRecipient, 0); - if (!linked.isOk()) { - ALOGE("Transaction error in linking to power HAL death: %s", - linked.description().c_str()); - gPowerHalV1_0 = nullptr; - return false; - } else if (!linked) { - ALOGW("Unable to link to power. death notifications"); - // We should still continue even though linking failed - } - } - return true; -} - -// The caller must be holding gPowerHalMutex. -static bool getIPowerDataLocked(vector<shared_ptr<LogEvent>>* data) { - using android::hardware::power::V1_0::Status; - - if(!getPowerHalLocked()) { - return false; - } - - int64_t wallClockTimestampNs = getWallClockNs(); - int64_t elapsedTimestampNs = getElapsedRealtimeNs(); - Return<void> ret; - ret = gPowerHalV1_0->getPlatformLowPowerStats( - [&data, wallClockTimestampNs, elapsedTimestampNs] - (hidl_vec<PowerStatePlatformSleepState> states, Status status) { - if (status != Status::SUCCESS) return; - - for (size_t i = 0; i < states.size(); i++) { - const PowerStatePlatformSleepState& state = states[i]; - - auto statePtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - statePtr->write(state.name); - statePtr->write(""); - statePtr->write(state.totalTransitions); - statePtr->write(state.residencyInMsecSinceBoot); - statePtr->init(); - data->push_back(statePtr); - VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(), - (long long)state.residencyInMsecSinceBoot, - (long long)state.totalTransitions, - state.supportedOnlyInSuspend ? 1 : 0); - for (const auto& voter : state.voters) { - auto voterPtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - voterPtr->write(state.name); - voterPtr->write(voter.name); - voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot); - voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot); - voterPtr->init(); - data->push_back(voterPtr); - VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(), - voter.name.c_str(), - (long long)voter.totalTimeInMsecVotedForSinceBoot, - (long long)voter.totalNumberOfTimesVotedSinceBoot); - } - } - }); - if (!checkResultLocked(ret, __func__)) { - return false; - } - - // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1 - sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1 = - android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0); - if (gPowerHal_1_1 != nullptr) { - ret = gPowerHal_1_1->getSubsystemLowPowerStats( - [&data, wallClockTimestampNs, elapsedTimestampNs] - (hidl_vec<PowerStateSubsystem> subsystems, Status status) { - if (status != Status::SUCCESS) return; - - if (subsystems.size() > 0) { - for (size_t i = 0; i < subsystems.size(); i++) { - const PowerStateSubsystem& subsystem = subsystems[i]; - for (size_t j = 0; j < subsystem.states.size(); j++) { - const PowerStateSubsystemSleepState& state = - subsystem.states[j]; - auto subsystemStatePtr = make_shared<LogEvent>( - android::util::SUBSYSTEM_SLEEP_STATE, - wallClockTimestampNs, elapsedTimestampNs); - subsystemStatePtr->write(subsystem.name); - subsystemStatePtr->write(state.name); - subsystemStatePtr->write(state.totalTransitions); - subsystemStatePtr->write(state.residencyInMsecSinceBoot); - subsystemStatePtr->init(); - data->push_back(subsystemStatePtr); - VLOG("subsystemstate: %s, %s, %lld, %lld, %lld", - subsystem.name.c_str(), state.name.c_str(), - (long long)state.residencyInMsecSinceBoot, - (long long)state.totalTransitions, - (long long)state.lastEntryTimestampMs); - } - } - } - }); - } - return true; -} - -// The caller must be holding gPowerHalMutex. -std::function<bool(vector<shared_ptr<LogEvent>>* data)> getPullerLocked() { - std::function<bool(vector<shared_ptr<LogEvent>>* data)> ret = {}; - - // First see if power.stats HAL is available. Fall back to power HAL if - // power.stats HAL is unavailable. - if(android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) { - ALOGI("Using power.stats HAL"); - ret = getIPowerStatsDataLocked; - } else if(android::hardware::power::V1_0::IPower::getService() != nullptr) { - ALOGI("Using power HAL"); - ret = getIPowerDataLocked; - } - - return ret; -} - -bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - std::lock_guard<std::mutex> lock(gPowerHalMutex); - - if(!gPuller) { - gPuller = getPullerLocked(); - } - - if(gPuller) { - return gPuller(data); - } - - ALOGE("Unable to load Power Hal or power.stats HAL"); - return false; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/TrainInfoPuller.cpp b/cmds/statsd/src/external/TrainInfoPuller.cpp index 9d0924297912..3837f4a1a517 100644 --- a/cmds/statsd/src/external/TrainInfoPuller.cpp +++ b/cmds/statsd/src/external/TrainInfoPuller.cpp @@ -22,7 +22,7 @@ #include "TrainInfoPuller.h" #include "logd/LogEvent.h" #include "stats_log_util.h" -#include "statslog.h" +#include "statslog_statsd.h" #include "storage/StorageManager.h" using std::make_shared; @@ -33,18 +33,20 @@ namespace os { namespace statsd { TrainInfoPuller::TrainInfoPuller() : - StatsPuller(android::util::TRAIN_INFO) { + StatsPuller(util::TRAIN_INFO) { } bool TrainInfoPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - InstallTrainInfo trainInfo; - bool ret = StorageManager::readTrainInfo(trainInfo); - if (!ret) { - ALOGW("Failed to read train info."); - return false; + vector<InstallTrainInfo> trainInfoList = + StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { + ALOGW("Train info was empty."); + return true; + } + for (InstallTrainInfo& trainInfo : trainInfoList) { + auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); + data->push_back(event); } - auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); - data->push_back(event); return true; } diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp index ccfd666573c4..aa99d0082bdd 100644 --- a/cmds/statsd/src/external/puller_util.cpp +++ b/cmds/statsd/src/external/puller_util.cpp @@ -17,20 +17,13 @@ #define DEBUG false // STOPSHIP if true #include "Log.h" -#include "StatsPullerManager.h" -#include "atoms_info.h" #include "puller_util.h" namespace android { namespace os { namespace statsd { -using std::list; -using std::map; -using std::set; -using std::shared_ptr; -using std::sort; -using std::vector; +using namespace std; /** * Process all data and merge isolated with host if necessary. @@ -54,16 +47,14 @@ using std::vector; * All atoms should be of the same tagId. All fields should be present. */ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap, - int tagId) { - if (StatsPullerManager::kAllPullAtomInfo.find(tagId) == - StatsPullerManager::kAllPullAtomInfo.end()) { - VLOG("Unknown pull atom id %d", tagId); - return; - } - if ((android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) == - android::util::AtomsInfo::kAtomsWithAttributionChain.end()) && - (android::util::AtomsInfo::kAtomsWithUidField.find(tagId) == - android::util::AtomsInfo::kAtomsWithUidField.end())) { + int tagId, const vector<int>& additiveFieldsVec) { + // Check the first LogEvent for attribution chain or a uid field as either all atoms with this + // tagId have them or none of them do. + std::pair<int, int> attrIndexRange; + const bool hasAttributionChain = data[0]->hasAttributionChain(&attrIndexRange); + bool hasUidField = (data[0]->getUidFieldIndex() != -1); + + if (!hasAttributionChain && !hasUidField) { VLOG("No uid or attribution chain to merge, atom %d", tagId); return; } @@ -74,31 +65,23 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const ALOGE("Wrong atom. Expecting %d, got %d", tagId, event->GetTagId()); return; } - if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) != - android::util::AtomsInfo::kAtomsWithAttributionChain.end()) { - for (auto& value : *(event->getMutableValues())) { - if (value.mField.getPosAtDepth(0) > kAttributionField) { - break; - } - if (isAttributionUidField(value)) { - const int hostUid = uidMap->getHostUidOrSelf(value.mValue.int_value); - value.mValue.setInt(hostUid); + if (hasAttributionChain) { + vector<FieldValue>* const fieldValues = event->getMutableValues(); + for (int i = attrIndexRange.first; i <= attrIndexRange.second; i++) { + FieldValue& fieldValue = fieldValues->at(i); + if (isAttributionUidField(fieldValue)) { + const int hostUid = uidMap->getHostUidOrSelf(fieldValue.mValue.int_value); + fieldValue.mValue.setInt(hostUid); } } } else { - auto it = android::util::AtomsInfo::kAtomsWithUidField.find(tagId); - if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { - int uidField = it->second; // uidField is the field number in proto, - // starting from 1 - if (uidField > 0 && (int)event->getValues().size() >= uidField && - (event->getValues())[uidField - 1].mValue.getType() == INT) { - Value& value = (*event->getMutableValues())[uidField - 1].mValue; - const int hostUid = uidMap->getHostUidOrSelf(value.int_value); - value.setInt(hostUid); - } else { - ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); - return; - } + int uidFieldIndex = event->getUidFieldIndex(); + if (uidFieldIndex != -1) { + Value& value = (*event->getMutableValues())[uidFieldIndex].mValue; + const int hostUid = uidMap->getHostUidOrSelf(value.int_value); + value.setInt(hostUid); + } else { + ALOGE("Malformed log, uid not found. %s", event->ToString().c_str()); } } } @@ -120,8 +103,6 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const }); vector<shared_ptr<LogEvent>> mergedData; - const vector<int>& additiveFieldsVec = - StatsPullerManager::kAllPullAtomInfo.find(tagId)->second.additiveFields; const set<int> additiveFields(additiveFieldsVec.begin(), additiveFieldsVec.end()); bool needMerge = true; diff --git a/cmds/statsd/src/external/puller_util.h b/cmds/statsd/src/external/puller_util.h index f703e6c589c1..afcf68ca10ad 100644 --- a/cmds/statsd/src/external/puller_util.h +++ b/cmds/statsd/src/external/puller_util.h @@ -26,7 +26,8 @@ namespace os { namespace statsd { void mapAndMergeIsolatedUidsToHostUid(std::vector<std::shared_ptr<LogEvent>>& data, - const sp<UidMap>& uidMap, int tagId); + const sp<UidMap>& uidMap, int tagId, + const vector<int>& additiveFieldsVec); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 3054b6d2204b..6e89038f4152 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -20,7 +20,7 @@ #include <android/util/ProtoOutputStream.h> #include "../stats_log_util.h" -#include "statslog.h" +#include "statslog_statsd.h" #include "storage/StorageManager.h" namespace android { @@ -38,6 +38,7 @@ using android::util::ProtoOutputStream; using std::lock_guard; using std::shared_ptr; using std::string; +using std::to_string; using std::vector; const int FIELD_ID_BEGIN_TIME = 1; @@ -54,6 +55,7 @@ const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19; const int FIELD_ID_ATOM_STATS_TAG = 1; const int FIELD_ID_ATOM_STATS_COUNT = 2; +const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3; const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1; const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1; @@ -113,13 +115,13 @@ const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID = 1; const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2; const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = { - {android::util::BINDER_CALLS, {6000, 10000}}, - {android::util::LOOPER_STATS, {1500, 2500}}, - {android::util::CPU_TIME_PER_UID_FREQ, {6000, 10000}}, + {util::BINDER_CALLS, {6000, 10000}}, + {util::LOOPER_STATS, {1500, 2500}}, + {util::CPU_TIME_PER_UID_FREQ, {6000, 10000}}, }; StatsdStats::StatsdStats() { - mPushedAtomStats.resize(android::util::kMaxPushedAtomId + 1); + mPushedAtomStats.resize(kMaxPushedAtomId + 1); mStartTimeSec = getWallClockSec(); } @@ -435,9 +437,18 @@ void StatsdStats::notePullDataError(int pullAtomId) { mPulledAtomStats[pullAtomId].dataError++; } -void StatsdStats::notePullTimeout(int pullAtomId) { +void StatsdStats::notePullTimeout(int pullAtomId, + int64_t pullUptimeMillis, + int64_t pullElapsedMillis) { lock_guard<std::mutex> lock(mLock); - mPulledAtomStats[pullAtomId].pullTimeout++; + PulledAtomStats& pulledAtomStats = mPulledAtomStats[pullAtomId]; + pulledAtomStats.pullTimeout++; + + if (pulledAtomStats.pullTimeoutMetadata.size() == kMaxTimestampCount) { + pulledAtomStats.pullTimeoutMetadata.pop_front(); + } + + pulledAtomStats.pullTimeoutMetadata.emplace_back(pullUptimeMillis, pullElapsedMillis); } void StatsdStats::notePullExceedMaxDelay(int pullAtomId) { @@ -448,7 +459,7 @@ void StatsdStats::notePullExceedMaxDelay(int pullAtomId) { void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) { lock_guard<std::mutex> lock(mLock); - if (atomId <= android::util::kMaxPushedAtomId) { + if (atomId <= kMaxPushedAtomId) { mPushedAtomStats[atomId]++; } else { if (mNonPlatformPushedAtomStats.size() < kMaxNonPlatformPushedAtoms) { @@ -471,14 +482,19 @@ void StatsdStats::notePullFailed(int atomId) { mPulledAtomStats[atomId].pullFailed++; } -void StatsdStats::noteStatsCompanionPullFailed(int atomId) { +void StatsdStats::notePullUidProviderNotFound(int atomId) { + lock_guard<std::mutex> lock(mLock); + mPulledAtomStats[atomId].pullUidProviderNotFound++; +} + +void StatsdStats::notePullerNotFound(int atomId) { lock_guard<std::mutex> lock(mLock); - mPulledAtomStats[atomId].statsCompanionPullFailed++; + mPulledAtomStats[atomId].pullerNotFound++; } -void StatsdStats::noteStatsCompanionPullBinderTransactionFailed(int atomId) { +void StatsdStats::notePullBinderCallFailed(int atomId) { lock_guard<std::mutex> lock(mLock); - mPulledAtomStats[atomId].statsCompanionPullBinderTransactionFailed++; + mPulledAtomStats[atomId].binderCallFailCount++; } void StatsdStats::noteEmptyData(int atomId) { @@ -549,6 +565,20 @@ void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayN std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs); } +void StatsdStats::noteAtomError(int atomTag, bool pull) { + lock_guard<std::mutex> lock(mLock); + if (pull) { + mPulledAtomStats[atomTag].atomErrorCount++; + return; + } + + bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end()); + bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize); + if (!full || present) { + mPushedAtomErrorStats[atomTag]++; + } +} + StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) { auto atomMetricStatsIter = mAtomMetricStats.find(metricId); if (atomMetricStatsIter != mAtomMetricStats.end()) { @@ -593,6 +623,7 @@ void StatsdStats::resetInternalLocked() { for (auto& pullStats : mPulledAtomStats) { pullStats.second.totalPull = 0; pullStats.second.totalPullFromCache = 0; + pullStats.second.minPullIntervalSec = LONG_MAX; pullStats.second.avgPullTimeNs = 0; pullStats.second.maxPullTimeNs = 0; pullStats.second.numPullTime = 0; @@ -602,11 +633,18 @@ void StatsdStats::resetInternalLocked() { pullStats.second.dataError = 0; pullStats.second.pullTimeout = 0; pullStats.second.pullExceedMaxDelay = 0; + pullStats.second.pullFailed = 0; + pullStats.second.pullUidProviderNotFound = 0; + pullStats.second.pullerNotFound = 0; pullStats.second.registeredCount = 0; pullStats.second.unregisteredCount = 0; + pullStats.second.atomErrorCount = 0; + pullStats.second.binderCallFailCount = 0; + pullStats.second.pullTimeoutMetadata.clear(); } mAtomMetricStats.clear(); mActivationBroadcastGuardrailStats.clear(); + mPushedAtomErrorStats.clear(); } string buildTimeString(int64_t timeSec) { @@ -617,6 +655,15 @@ string buildTimeString(int64_t timeSec) { return string(timeBuffer); } +int StatsdStats::getPushedAtomErrors(int atomId) const { + const auto& it = mPushedAtomErrorStats.find(atomId); + if (it != mPushedAtomErrorStats.end()) { + return it->second; + } else { + return 0; + } +} + void StatsdStats::dumpStats(int out) const { lock_guard<std::mutex> lock(mLock); time_t t = mStartTimeSec; @@ -721,11 +768,13 @@ void StatsdStats::dumpStats(int out) const { const size_t atomCounts = mPushedAtomStats.size(); for (size_t i = 2; i < atomCounts; i++) { if (mPushedAtomStats[i] > 0) { - dprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]); + dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i], + getPushedAtomErrors((int)i)); } } for (const auto& pair : mNonPlatformPushedAtomStats) { - dprintf(out, "Atom %lu->%d\n", (unsigned long)pair.first, pair.second); + dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second, + getPushedAtomErrors(pair.first)); } dprintf(out, "********Pulled Atom stats***********\n"); @@ -736,14 +785,32 @@ void StatsdStats::dumpStats(int out) const { " (average pull time nanos)%lld, (max pull time nanos)%lld, (average pull delay " "nanos)%lld, " " (max pull delay nanos)%lld, (data error)%ld\n" - " (pull timeout)%ld, (pull exceed max delay)%ld\n" - " (registered count) %ld, (unregistered count) %ld\n", + " (pull timeout)%ld, (pull exceed max delay)%ld" + " (no uid provider count)%ld, (no puller found count)%ld\n" + " (registered count) %ld, (unregistered count) %ld" + " (atom error count) %d\n", (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache, (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec, (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs, (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs, pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay, - pair.second.registeredCount, pair.second.unregisteredCount); + pair.second.pullUidProviderNotFound, pair.second.pullerNotFound, + pair.second.registeredCount, pair.second.unregisteredCount, + pair.second.atomErrorCount); + if (pair.second.pullTimeoutMetadata.size() > 0) { + string uptimeMillis = "(pull timeout system uptime millis) "; + string pullTimeoutMillis = "(pull timeout elapsed time millis) "; + for (const auto& stats : pair.second.pullTimeoutMetadata) { + uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");; + pullTimeoutMillis.append(to_string(stats.pullTimeoutElapsedMillis)).append(","); + } + uptimeMillis.pop_back(); + uptimeMillis.push_back('\n'); + pullTimeoutMillis.pop_back(); + pullTimeoutMillis.push_back('\n'); + dprintf(out, "%s", uptimeMillis.c_str()); + dprintf(out, "%s", pullTimeoutMillis.c_str()); + } } if (mAnomalyAlarmRegisteredStats > 0) { @@ -919,6 +986,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]); + int errors = getPushedAtomErrors(i); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } proto.end(token); } } @@ -928,6 +999,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second); + int errors = getPushedAtomErrors(pair.first); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } proto.end(token); } diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 564b9ee8051c..005048446fc3 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -16,7 +16,6 @@ #pragma once #include "config/ConfigKey.h" -#include "atoms_info.h" #include <gtest/gtest_prod.h> #include <log/log_time.h> @@ -102,7 +101,7 @@ public: // Per atom dimension key size limit static const std::map<int, std::pair<size_t, size_t>> kAtomDimensionKeySizeLimitMap; - const static int kMaxConfigCountPerUid = 10; + const static int kMaxConfigCountPerUid = 20; const static int kMaxAlertCountPerConfig = 100; const static int kMaxConditionCountPerConfig = 300; const static int kMaxMetricCountPerConfig = 1000; @@ -119,6 +118,8 @@ public: const static int kMaxLogSourceCount = 50; + const static int kMaxPullAtomPackages = 100; + // Max memory allowed for storing metrics per configuration. If this limit is exceeded, statsd // drops the metrics data in memory. static const size_t kMaxMetricsBytesPerConfig = 2 * 1024 * 1024; @@ -159,13 +160,20 @@ public: static const long kPullerCacheClearIntervalSec = 1; // Max time to do a pull. - static const int64_t kPullMaxDelayNs = 10 * NS_PER_SEC; + static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC; // Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId. static const int kMaxNonPlatformPushedAtoms = 100; - // Max platform atom tag number. - static const int32_t kMaxPlatformAtomTag = 100000; + // Maximum atom id value that we consider a platform pushed atom. + // This should be updated once highest pushed atom id in atoms.proto approaches this value. + static const int kMaxPushedAtomId = 500; + + // Atom id that is the start of the pulled atoms. + static const int kPullAtomStartTag = 10000; + + // Atom id that is the start of vendor atoms. + static const int kVendorAtomStartTag = 100000; // Vendor pulled atom start id. static const int32_t kVendorPulledAtomStartTag = 150000; @@ -181,6 +189,8 @@ public: static const int64_t kInt64Max = 0x7fffffffffffffffLL; + static const int32_t kMaxLoggedBucketDropEvents = 10; + /** * Report a new config has been received and report the static stats about the config. * @@ -342,7 +352,7 @@ public: /* * Records pull exceeds timeout for the puller. */ - void notePullTimeout(int pullAtomId); + void notePullTimeout(int pullAtomId, int64_t pullUptimeMillis, int64_t pullElapsedMillis); /* * Records pull exceeds max delay for a metric. @@ -361,21 +371,30 @@ public: int32_t lastAtomTag, int32_t uid, int32_t pid); /** - * Records that the pull of an atom has failed + * Records that the pull of an atom has failed. Eg, if the client indicated the pull failed, if + * the pull timed out, or if the outgoing binder call failed. + * This count will only increment if the puller was actually invoked. + * + * It does not include a pull not occurring due to not finding the appropriate + * puller. These cases are covered in other counts. */ void notePullFailed(int atomId); /** - * Records that the pull of StatsCompanionService atom has failed + * Records that the pull of an atom has failed due to not having a uid provider. */ - void noteStatsCompanionPullFailed(int atomId); + void notePullUidProviderNotFound(int atomId); /** - * Records that the pull of a StatsCompanionService atom has failed due to a failed binder - * transaction. This can happen when StatsCompanionService returns too - * much data (the max Binder parcel size is 1MB) + * Records that the pull of an atom has failed due not finding a puller registered by a + * trusted uid. */ - void noteStatsCompanionPullBinderTransactionFailed(int atomId); + void notePullerNotFound(int atomId); + + /** + * Records that the pull has failed due to the outgoing binder call failing. + */ + void notePullBinderCallFailed(int atomId); /** * A pull with no data occurred @@ -450,6 +469,16 @@ public: */ void noteActivationBroadcastGuardrailHit(const int uid); + /** + * Reports that an atom is erroneous or cannot be parsed successfully by + * statsd. An atom tag of 0 indicates that the client did not supply the + * atom id within the encoding. + * + * For pushed atoms only, this call should be preceded by a call to + * noteAtomLogged. + */ + void noteAtomError(int atomTag, bool pull=false); + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue @@ -469,6 +498,14 @@ public: */ void dumpStats(int outFd) const; + typedef struct PullTimeoutMetadata { + int64_t pullTimeoutUptimeMillis; + int64_t pullTimeoutElapsedMillis; + PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) : + pullTimeoutUptimeMillis(uptimeMillis), + pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */} + } PullTimeoutMetadata; + typedef struct { long totalPull = 0; long totalPullFromCache = 0; @@ -483,11 +520,14 @@ public: long pullTimeout = 0; long pullExceedMaxDelay = 0; long pullFailed = 0; - long statsCompanionPullFailed = 0; - long statsCompanionPullBinderTransactionFailed = 0; + long pullUidProviderNotFound = 0; + long pullerNotFound = 0; long emptyData = 0; long registeredCount = 0; long unregisteredCount = 0; + int32_t atomErrorCount = 0; + long binderCallFailCount = 0; + std::list<PullTimeoutMetadata> pullTimeoutMetadata; } PulledAtomStats; typedef struct { @@ -535,6 +575,12 @@ private: // Maps PullAtomId to its stats. The size is capped by the puller atom counts. std::map<int, PulledAtomStats> mPulledAtomStats; + // Stores the number of times a pushed atom was logged erroneously. The + // corresponding counts for pulled atoms are stored in PulledAtomStats. + // The max size of this map is kMaxAtomErrorsStatsSize. + std::map<int, int> mPushedAtomErrorStats; + int kMaxPushedAtomErrorStatsSize = 100; + // Maps metric ID to its stats. The size is capped by the number of metrics. std::map<int64_t, AtomMetricStats> mAtomMetricStats; @@ -602,6 +648,8 @@ private: void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats); + int getPushedAtomErrors(int atomId) const; + /** * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference * will live as long as `this`. @@ -620,6 +668,9 @@ private: FRIEND_TEST(StatsdStatsTest, TestPullAtomStats); FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats); FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit); + FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats); + + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); }; } // namespace statsd diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 1d51ab82efa8..f56fa6221bc9 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -17,12 +17,14 @@ #define DEBUG false // STOPSHIP if true #include "logd/LogEvent.h" -#include "stats_log_util.h" -#include "statslog.h" - -#include <binder/IPCThreadState.h> +#include <android-base/stringprintf.h> +#include <android/binder_ibinder.h> #include <private/android_filesystem_config.h> +#include "annotations.h" +#include "stats_log_util.h" +#include "statslog_statsd.h" + namespace android { namespace os { namespace statsd { @@ -31,160 +33,41 @@ namespace statsd { const int FIELD_ID_EXPERIMENT_ID = 1; using namespace android::util; +using android::base::StringPrintf; using android::util::ProtoOutputStream; using std::string; using std::vector; -LogEvent::LogEvent(log_msg& msg) { - mContext = - create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t)); - mLogdTimestampNs = msg.entry.sec * NS_PER_SEC + msg.entry.nsec; - mLogUid = msg.entry.uid; - init(mContext); - if (mContext) { - // android_log_destroy will set mContext to NULL - android_log_destroy(&mContext); - } -} - -LogEvent::LogEvent(const LogEvent& event) { - mTagId = event.mTagId; - mLogUid = event.mLogUid; - mElapsedTimestampNs = event.mElapsedTimestampNs; - mLogdTimestampNs = event.mLogdTimestampNs; - mValues = event.mValues; -} - -LogEvent::LogEvent(const StatsLogEventWrapper& statsLogEventWrapper, int workChainIndex) { - mTagId = statsLogEventWrapper.getTagId(); - mLogdTimestampNs = statsLogEventWrapper.getWallClockTimeNs(); - mElapsedTimestampNs = statsLogEventWrapper.getElapsedRealTimeNs(); - mLogUid = 0; - int workChainPosOffset = 0; - if (workChainIndex != -1) { - const WorkChain& wc = statsLogEventWrapper.getWorkChains()[workChainIndex]; - // chains are at field 1, level 2 - int depth = 2; - for (int i = 0; i < (int)wc.uids.size(); i++) { - int pos[] = {1, i + 1, 1}; - mValues.push_back(FieldValue(Field(mTagId, pos, depth), Value(wc.uids[i]))); - pos[2]++; - mValues.push_back(FieldValue(Field(mTagId, pos, depth), Value(wc.tags[i]))); - mValues.back().mField.decorateLastPos(2); - } - mValues.back().mField.decorateLastPos(1); - workChainPosOffset = 1; - } - for (int i = 0; i < (int)statsLogEventWrapper.getElements().size(); i++) { - Field field(statsLogEventWrapper.getTagId(), getSimpleField(i + 1 + workChainPosOffset)); - switch (statsLogEventWrapper.getElements()[i].type) { - case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::INT: - mValues.push_back( - FieldValue(field, Value(statsLogEventWrapper.getElements()[i].int_value))); - break; - case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::LONG: - mValues.push_back( - FieldValue(field, Value(statsLogEventWrapper.getElements()[i].long_value))); - break; - case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::FLOAT: - mValues.push_back(FieldValue( - field, Value(statsLogEventWrapper.getElements()[i].float_value))); - break; - case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::DOUBLE: - mValues.push_back(FieldValue( - field, Value(statsLogEventWrapper.getElements()[i].double_value))); - break; - case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::STRING: - mValues.push_back( - FieldValue(field, Value(statsLogEventWrapper.getElements()[i].str_value))); - break; - case android::os::StatsLogValue::STATS_LOG_VALUE_TYPE::STORAGE: - mValues.push_back(FieldValue( - field, Value(statsLogEventWrapper.getElements()[i].storage_value))); - break; - default: - break; - } - } -} - -void LogEvent::createLogEvents(const StatsLogEventWrapper& statsLogEventWrapper, - std::vector<std::shared_ptr<LogEvent>>& logEvents) { - if (statsLogEventWrapper.getWorkChains().size() == 0) { - logEvents.push_back(std::make_shared<LogEvent>(statsLogEventWrapper, -1)); - } else { - for (size_t i = 0; i < statsLogEventWrapper.getWorkChains().size(); i++) { - logEvents.push_back(std::make_shared<LogEvent>(statsLogEventWrapper, i)); - } - } -} - -LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = tagId; - mLogUid = 0; - mContext = create_android_logger(1937006964); // the event tag shared by all stats logs - if (mContext) { - android_log_write_int64(mContext, elapsedTimestampNs); - android_log_write_int32(mContext, tagId); - } -} - -LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = android::util::KEY_VALUE_PAIRS_ATOM; - mLogUid = uid; - - int pos[] = {1, 1, 1}; - - mValues.push_back(FieldValue(Field(mTagId, pos, 0 /* depth */), Value(uid))); - pos[0]++; - for (const auto&itr : int_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 2; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : long_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 3; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : string_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 4; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : float_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 5; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - if (!mValues.empty()) { - mValues.back().mField.decorateLastPos(1); - mValues.at(mValues.size() - 2).mField.decorateLastPos(1); - } +// stats_event.h socket types. Keep in sync. +/* ERRORS */ +#define ERROR_NO_TIMESTAMP 0x1 +#define ERROR_NO_ATOM_ID 0x2 +#define ERROR_OVERFLOW 0x4 +#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 +#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 +#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 +#define ERROR_INVALID_ANNOTATION_ID 0x40 +#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 +#define ERROR_TOO_MANY_ANNOTATIONS 0x100 +#define ERROR_TOO_MANY_FIELDS 0x200 +#define ERROR_INVALID_VALUE_TYPE 0x400 +#define ERROR_STRING_NOT_NULL_TERMINATED 0x800 + +/* TYPE IDS */ +#define INT32_TYPE 0x00 +#define INT64_TYPE 0x01 +#define STRING_TYPE 0x02 +#define LIST_TYPE 0x03 +#define FLOAT_TYPE 0x04 +#define BOOL_TYPE 0x05 +#define BYTE_ARRAY_TYPE 0x06 +#define OBJECT_TYPE 0x07 +#define KEY_VALUE_PAIRS_TYPE 0x08 +#define ATTRIBUTION_CHAIN_TYPE 0x09 +#define ERROR_TYPE 0x0F + +LogEvent::LogEvent(int32_t uid, int32_t pid) + : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) { } LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, @@ -192,8 +75,9 @@ LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requi const std::vector<uint8_t>& experimentIds, int32_t userId) { mLogdTimestampNs = getWallClockNs(); mElapsedTimestampNs = getElapsedRealtimeNs(); - mTagId = android::util::BINARY_PUSH_STATE_CHANGED; - mLogUid = android::IPCThreadState::self()->getCallingUid(); + mTagId = util::BINARY_PUSH_STATE_CHANGED; + mLogUid = AIBinder_getCallingUid(); + mLogPid = AIBinder_getCallingPid(); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode))); @@ -210,7 +94,7 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& trainInfo) { mLogdTimestampNs = wallClockTimestampNs; mElapsedTimestampNs = elapsedTimestampNs; - mTagId = android::util::TRAIN_INFO; + mTagId = util::TRAIN_INFO; mValues.push_back( FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode))); @@ -221,309 +105,320 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); } -LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, timestampNs) { +void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t value = readNextValue<int32_t>(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) { - mLogdTimestampNs = timestampNs; - mTagId = tagId; - mLogUid = uid; - mContext = create_android_logger(1937006964); // the event tag shared by all stats logs - if (mContext) { - android_log_write_int64(mContext, timestampNs); - android_log_write_int32(mContext, tagId); - } +void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int64_t value = readNextValue<int64_t>(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -void LogEvent::init() { - if (mContext) { - const char* buffer; - size_t len = android_log_write_list_buffer(mContext, &buffer); - // turns to reader mode - android_log_context contextForRead = create_android_log_parser(buffer, len); - if (contextForRead) { - init(contextForRead); - // destroy the context to save memory. - // android_log_destroy will set mContext to NULL - android_log_destroy(&contextForRead); - } - android_log_destroy(&mContext); +void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t numBytes = readNextValue<int32_t>(); + if ((uint32_t)numBytes > mRemainingLen) { + mValid = false; + return; } + + string value = string((char*)mBuf, numBytes); + mBuf += numBytes; + mRemainingLen -= numBytes; + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -LogEvent::~LogEvent() { - if (mContext) { - // This is for the case when LogEvent is created using the test interface - // but init() isn't called. - android_log_destroy(&mContext); - } +void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + float value = readNextValue<float>(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -bool LogEvent::write(int32_t value) { - if (mContext) { - return android_log_write_int32(mContext, value) >= 0; - } - return false; +void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + // cast to int32_t because FieldValue does not support bools + int32_t value = (int32_t)readNextValue<uint8_t>(); + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -bool LogEvent::write(uint32_t value) { - if (mContext) { - return android_log_write_int32(mContext, value) >= 0; +void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t numBytes = readNextValue<int32_t>(); + if ((uint32_t)numBytes > mRemainingLen) { + mValid = false; + return; } - return false; + + vector<uint8_t> value(mBuf, mBuf + numBytes); + mBuf += numBytes; + mRemainingLen -= numBytes; + addToValues(pos, depth, value, last); + parseAnnotations(numAnnotations); } -bool LogEvent::write(int64_t value) { - if (mContext) { - return android_log_write_int64(mContext, value) >= 0; +void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + int32_t numPairs = readNextValue<uint8_t>(); + + for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) { + last[1] = (pos[1] == numPairs); + + // parse key + pos[2] = 1; + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); + + // parse value + last[2] = true; + + uint8_t typeInfo = readNextValue<uint8_t>(); + switch (getTypeId(typeInfo)) { + case INT32_TYPE: + pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + case INT64_TYPE: + pos[2] = 3; + parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + case STRING_TYPE: + pos[2] = 4; + parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + case FLOAT_TYPE: + pos[2] = 5; + parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0); + break; + default: + mValid = false; + } } - return false; + + parseAnnotations(numAnnotations); + + pos[1] = pos[2] = 1; + last[1] = last[2] = false; } -bool LogEvent::write(uint64_t value) { - if (mContext) { - return android_log_write_int64(mContext, value) >= 0; +void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, + uint8_t numAnnotations) { + const unsigned int firstUidInChainIndex = mValues.size(); + const int32_t numNodes = readNextValue<uint8_t>(); + for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) { + last[1] = (pos[1] == numNodes); + + // parse uid + pos[2] = 1; + parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); + + // parse tag + pos[2] = 2; + last[2] = true; + parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); } - return false; + // Check if at least one node was successfully parsed. + if (mValues.size() - 1 > firstUidInChainIndex) { + mAttributionChainStartIndex = static_cast<int8_t>(firstUidInChainIndex); + mAttributionChainEndIndex = static_cast<int8_t>(mValues.size() - 1); + } + + parseAnnotations(numAnnotations, firstUidInChainIndex); + + pos[1] = pos[2] = 1; + last[1] = last[2] = false; +} + +// Assumes that mValues is not empty +bool LogEvent::checkPreviousValueType(Type expected) { + return mValues[mValues.size() - 1].mValue.getType() == expected; } -bool LogEvent::write(const string& value) { - if (mContext) { - return android_log_write_string8_len(mContext, value.c_str(), value.length()) >= 0; +void LogEvent::parseIsUidAnnotation(uint8_t annotationType) { + if (mValues.empty() || !checkPreviousValueType(INT) || annotationType != BOOL_TYPE) { + mValid = false; + return; } - return false; + + bool isUid = readNextValue<uint8_t>(); + if (isUid) mUidFieldIndex = static_cast<int8_t>(mValues.size() - 1); + mValues[mValues.size() - 1].mAnnotations.setUidField(isUid); } -bool LogEvent::write(float value) { - if (mContext) { - return android_log_write_float32(mContext, value) >= 0; +void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { + if (!mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; } - return false; + + mTruncateTimestamp = readNextValue<uint8_t>(); } -bool LogEvent::writeKeyValuePairs(int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(uid); - for (const auto& itr : int_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } +void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; + } - for (const auto& itr : long_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } + const bool primaryField = readNextValue<uint8_t>(); + mValues[mValues.size() - 1].mAnnotations.setPrimaryField(primaryField); +} - for (const auto& itr : string_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second.c_str()); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } +void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, + int firstUidInChainIndex) { + if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) { + mValid = false; + return; + } - for (const auto& itr : float_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } + const bool primaryField = readNextValue<uint8_t>(); + mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField); +} - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; +void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; } - return false; + + const bool exclusiveState = readNextValue<uint8_t>(); + mExclusiveStateFieldIndex = static_cast<int8_t>(mValues.size() - 1); + mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState); } -bool LogEvent::write(const std::vector<AttributionNodeInternal>& nodes) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - for (size_t i = 0; i < nodes.size(); ++i) { - if (!write(nodes[i])) { - return false; - } - } - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; +void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != INT32_TYPE) { + mValid = false; + return; } - return false; + + mResetState = readNextValue<int32_t>(); } -bool LogEvent::write(const AttributionNodeInternal& node) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - if (android_log_write_int32(mContext, node.uid()) < 0) { - return false; - } - if (android_log_write_string8(mContext, node.tag().c_str()) < 0) { - return false; - } - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; +void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { + if (mValues.empty() || annotationType != BOOL_TYPE) { + mValid = false; + return; } - return false; + + bool nested = readNextValue<uint8_t>(); + mValues[mValues.size() - 1].mAnnotations.setNested(nested); } -/** - * The elements of each log event are stored as a vector of android_log_list_elements. - * The goal is to do as little preprocessing as possible, because we read a tiny fraction - * of the elements that are written to the log. - * - * The idea here is to read through the log items once, we get as much information we need for - * matching as possible. Because this log will be matched against lots of matchers. - */ -void LogEvent::init(android_log_context context) { - android_log_list_element elem; - int i = 0; - int depth = -1; - int pos[] = {1, 1, 1}; - bool isKeyValuePairAtom = false; - do { - elem = android_log_read_next(context); - switch ((int)elem.type) { - case EVENT_TYPE_INT: - // elem at [0] is EVENT_TYPE_LIST, [1] is the timestamp, [2] is tag id. - if (i == 2) { - mTagId = elem.data.int32; - isKeyValuePairAtom = (mTagId == android::util::KEY_VALUE_PAIRS_ATOM); - } else { - if (depth < 0 || depth > 2) { - return; - } - - mValues.push_back( - FieldValue(Field(mTagId, pos, depth), Value((int32_t)elem.data.int32))); - - pos[depth]++; - } +// firstUidInChainIndex is a default parameter that is only needed when parsing +// annotations for attribution chains. +void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) { + for (uint8_t i = 0; i < numAnnotations; i++) { + uint8_t annotationId = readNextValue<uint8_t>(); + uint8_t annotationType = readNextValue<uint8_t>(); + + switch (annotationId) { + case ANNOTATION_ID_IS_UID: + parseIsUidAnnotation(annotationType); + break; + case ANNOTATION_ID_TRUNCATE_TIMESTAMP: + parseTruncateTimestampAnnotation(annotationType); + break; + case ANNOTATION_ID_PRIMARY_FIELD: + parsePrimaryFieldAnnotation(annotationType); + break; + case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID: + parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex); break; - case EVENT_TYPE_FLOAT: { - if (depth < 0 || depth > 2) { - ALOGE("Depth > 2. Not supported!"); - return; - } - - // Handles the oneof field in KeyValuePair atom. - if (isKeyValuePairAtom && depth == 2) { - pos[depth] = 5; - } - - mValues.push_back(FieldValue(Field(mTagId, pos, depth), Value(elem.data.float32))); - - pos[depth]++; - - } break; - case EVENT_TYPE_STRING: { - if (depth < 0 || depth > 2) { - ALOGE("Depth > 2. Not supported!"); - return; - } - - // Handles the oneof field in KeyValuePair atom. - if (isKeyValuePairAtom && depth == 2) { - pos[depth] = 4; - } - mValues.push_back(FieldValue(Field(mTagId, pos, depth), - Value(string(elem.data.string, elem.len)))); - - pos[depth]++; - - } break; - case EVENT_TYPE_LONG: { - if (i == 1) { - mElapsedTimestampNs = elem.data.int64; - } else { - if (depth < 0 || depth > 2) { - ALOGE("Depth > 2. Not supported!"); - return; - } - // Handles the oneof field in KeyValuePair atom. - if (isKeyValuePairAtom && depth == 2) { - pos[depth] = 3; - } - mValues.push_back( - FieldValue(Field(mTagId, pos, depth), Value((int64_t)elem.data.int64))); - - pos[depth]++; - } - } break; - case EVENT_TYPE_LIST: - depth++; - if (depth > 2) { - ALOGE("Depth > 2. Not supported!"); - return; - } - pos[depth] = 1; + case ANNOTATION_ID_EXCLUSIVE_STATE: + parseExclusiveStateAnnotation(annotationType); + break; + case ANNOTATION_ID_TRIGGER_STATE_RESET: + parseTriggerStateResetAnnotation(annotationType); + break; + case ANNOTATION_ID_STATE_NESTED: + parseStateNestedAnnotation(annotationType); + break; + default: + mValid = false; + return; + } + } +} + +// This parsing logic is tied to the encoding scheme used in StatsEvent.java and +// stats_event.c +bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { + mBuf = buf; + mRemainingLen = (uint32_t)len; + + int32_t pos[] = {1, 1, 1}; + bool last[] = {false, false, false}; + // Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID + uint8_t typeInfo = readNextValue<uint8_t>(); + if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false; + + uint8_t numElements = readNextValue<uint8_t>(); + if (numElements < 2 || numElements > 127) mValid = false; + + typeInfo = readNextValue<uint8_t>(); + if (getTypeId(typeInfo) != INT64_TYPE) mValid = false; + mElapsedTimestampNs = readNextValue<int64_t>(); + numElements--; + + typeInfo = readNextValue<uint8_t>(); + if (getTypeId(typeInfo) != INT32_TYPE) mValid = false; + mTagId = readNextValue<int32_t>(); + numElements--; + parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations + + for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) { + last[0] = (pos[0] == numElements); + + typeInfo = readNextValue<uint8_t>(); + uint8_t typeId = getTypeId(typeInfo); + + switch (typeId) { + case BOOL_TYPE: + parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; - case EVENT_TYPE_LIST_STOP: { - int prevDepth = depth; - depth--; - if (depth >= 0 && depth < 2) { - // Now go back to decorate the previous items that are last at prevDepth. - // So that we can later easily match them with Position=Last matchers. - pos[prevDepth]--; - int path = getEncodedField(pos, prevDepth, false); - for (auto it = mValues.rbegin(); it != mValues.rend(); ++it) { - if (it->mField.getDepth() >= prevDepth && - it->mField.getPath(prevDepth) == path) { - it->mField.decorateLastPos(prevDepth); - } else { - // Safe to break, because the items are in DFS order. - break; - } - } - pos[depth]++; - } + case INT32_TYPE: + parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; - } - case EVENT_TYPE_UNKNOWN: + case INT64_TYPE: + parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case FLOAT_TYPE: + parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case BYTE_ARRAY_TYPE: + parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case STRING_TYPE: + parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case KEY_VALUE_PAIRS_TYPE: + parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case ATTRIBUTION_CHAIN_TYPE: + parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; + case ERROR_TYPE: + /* mErrorBitmask =*/ readNextValue<int32_t>(); + mValid = false; break; default: + mValid = false; break; } - i++; - } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete); - if (isKeyValuePairAtom && mValues.size() > 0) { - mValues[0] = FieldValue(Field(android::util::KEY_VALUE_PAIRS_ATOM, getSimpleField(1)), - Value((int32_t)mLogUid)); } + + if (mRemainingLen != 0) mValid = false; + mBuf = nullptr; + return mValid; +} + +uint8_t LogEvent::getTypeId(uint8_t typeInfo) { + return typeInfo & 0x0F; // type id in lower 4 bytes +} + +uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) { + return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes } int64_t LogEvent::GetLong(size_t key, status_t* err) const { @@ -631,6 +526,26 @@ float LogEvent::GetFloat(size_t key, status_t* err) const { return 0.0; } +std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == STORAGE) { + return value.mValue.storage_value; + } else { + *err = BAD_TYPE; + return vector<uint8_t>(); + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return vector<uint8_t>(); +} + string LogEvent::ToString() const { string result; result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs, @@ -647,6 +562,19 @@ void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); } +bool LogEvent::hasAttributionChain(std::pair<int, int>* indexRange) const { + if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) { + return false; + } + + if (nullptr != indexRange) { + indexRange->first = static_cast<int>(mAttributionChainStartIndex); + indexRange->second = static_cast<int>(mAttributionChainEndIndex); + } + + return true; +} + void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut) { ProtoOutputStream proto; diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index e1b5a0b8f0f5..a5f24603585a 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -18,9 +18,7 @@ #include "FieldValue.h" -#include <android/os/StatsLogEventWrapper.h> #include <android/util/ProtoOutputStream.h> -#include <log/log_read.h> #include <private/android_logger.h> #include <string> @@ -30,75 +28,39 @@ namespace android { namespace os { namespace statsd { -struct AttributionNodeInternal { - void set_uid(int32_t id) { - mUid = id; - } - - void set_tag(const std::string& value) { - mTag = value; - } - - int32_t uid() const { - return mUid; - } - - const std::string& tag() const { - return mTag; - } - - int32_t mUid; - std::string mTag; -}; - struct InstallTrainInfo { int64_t trainVersionCode; std::string trainName; int32_t status; std::vector<int64_t> experimentIds; + bool requiresStaging; + bool rollbackEnabled; + bool requiresLowLatencyMonitor; }; /** - * Wrapper for the log_msg structure. + * This class decodes the structured, serialized encoding of an atom into a + * vector of FieldValues. */ class LogEvent { public: /** - * Read a LogEvent from a log_msg. - */ - explicit LogEvent(log_msg& msg); - - /** - * Creates LogEvent from StatsLogEventWrapper. - */ - static void createLogEvents(const StatsLogEventWrapper& statsLogEventWrapper, - std::vector<std::shared_ptr<LogEvent>>& logEvents); - - /** - * Construct one LogEvent from a StatsLogEventWrapper with the i-th work chain. -1 if no chain. - */ - explicit LogEvent(const StatsLogEventWrapper& statsLogEventWrapper, int workChainIndex); - - /** - * Constructs a LogEvent with synthetic data for testing. Must call init() before reading. + * \param uid user id of the logging caller + * \param pid process id of the logging caller */ - explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs); - - // For testing. The timestamp is used as both elapsed real time and logd timestamp. - explicit LogEvent(int32_t tagId, int64_t timestampNs); - - // For testing. The timestamp is used as both elapsed real time and logd timestamp. - explicit LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid); + explicit LogEvent(int32_t uid, int32_t pid); /** - * Constructs a KeyValuePairsAtom LogEvent from value maps. + * Parses the atomId, timestamp, and vector of values from a buffer + * containing the StatsEvent/AStatsEvent encoding of an atom. + * + * \param buf a buffer that begins at the start of the serialized atom (it + * should not include the android_log_header_t or the StatsEventTag) + * \param len size of the buffer + * + * \return success of the initialization */ - explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map); + bool parseBuffer(uint8_t* buf, size_t len); // Constructs a BinaryPushStateChanged LogEvent from API call. explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, @@ -108,7 +70,7 @@ public: explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& installTrainInfo); - ~LogEvent(); + ~LogEvent() {} /** * Get the timestamp associated with this event. @@ -121,9 +83,17 @@ public: */ inline int GetTagId() const { return mTagId; } - inline uint32_t GetUid() const { - return mLogUid; - } + /** + * Get the uid of the logging client. + * Returns -1 if the uid is unknown/has not been set. + */ + inline int32_t GetUid() const { return mLogUid; } + + /** + * Get the pid of the logging client. + * Returns -1 if the pid is unknown/has not been set. + */ + inline int32_t GetPid() const { return mLogPid; } /** * Get the nth value, starting at 1. @@ -136,24 +106,7 @@ public: const char* GetString(size_t key, status_t* err) const; bool GetBool(size_t key, status_t* err) const; float GetFloat(size_t key, status_t* err) const; - - /** - * Write test data to the LogEvent. This can only be used when the LogEvent is constructed - * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it. - */ - bool write(uint32_t value); - bool write(int32_t value); - bool write(uint64_t value); - bool write(int64_t value); - bool write(const std::string& value); - bool write(float value); - bool write(const std::vector<AttributionNodeInternal>& nodes); - bool write(const AttributionNodeInternal& node); - bool writeKeyValuePairs(int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map); + std::vector<uint8_t> GetStorage(size_t key, status_t* err) const; /** * Return a string representation of this event. @@ -166,12 +119,6 @@ public: void ToProto(android::util::ProtoOutputStream& out) const; /** - * Used with the constructor where tag is passed in. Converts the log_event_list to read mode - * and prepares the list for reading. - */ - void init(); - - /** * Set elapsed timestamp if the original timestamp is missing. */ void setElapsedTimestampNs(int64_t timestampNs) { @@ -197,39 +144,184 @@ public: return &mValues; } + // Default value = false + inline bool shouldTruncateTimestamp() const { + return mTruncateTimestamp; + } + + // Returns the index of the uid field within the FieldValues vector if the + // uid exists. If there is no uid field, returns -1. + // + // If the index within the atom definition is desired, do the following: + // int vectorIndex = LogEvent.getUidFieldIndex(); + // if (vectorIndex != -1) { + // FieldValue& v = LogEvent.getValues()[vectorIndex]; + // int atomIndex = v.mField.getPosAtDepth(0); + // } + // Note that atomIndex is 1-indexed. + inline int getUidFieldIndex() { + return static_cast<int>(mUidFieldIndex); + } + + // Returns whether this LogEvent has an AttributionChain. + // If it does and indexRange is not a nullptr, populate indexRange with the start and end index + // of the AttributionChain within mValues. + bool hasAttributionChain(std::pair<int, int>* indexRange = nullptr) const; + + // Returns the index of the exclusive state field within the FieldValues vector if + // an exclusive state exists. If there is no exclusive state field, returns -1. + // + // If the index within the atom definition is desired, do the following: + // int vectorIndex = LogEvent.getExclusiveStateFieldIndex(); + // if (vectorIndex != -1) { + // FieldValue& v = LogEvent.getValues()[vectorIndex]; + // int atomIndex = v.mField.getPosAtDepth(0); + // } + // Note that atomIndex is 1-indexed. + inline int getExclusiveStateFieldIndex() const { + return static_cast<int>(mExclusiveStateFieldIndex); + } + + // If a reset state is not sent in the StatsEvent, returns -1. Note that a + // reset state is sent if and only if a reset should be triggered. + inline int getResetState() const { + return mResetState; + } + inline LogEvent makeCopy() { return LogEvent(*this); } + template <class T> + status_t updateValue(size_t key, T& value, Type type) { + int field = getSimpleField(key); + for (auto& fieldValue : mValues) { + if (fieldValue.mField.getField() == field) { + if (fieldValue.mValue.getType() == type) { + fieldValue.mValue = Value(value); + return OK; + } else { + return BAD_TYPE; + } + } + } + return BAD_INDEX; + } + + bool isValid() const { + return mValid; + } + private: /** * Only use this if copy is absolutely needed. */ - LogEvent(const LogEvent&); + LogEvent(const LogEvent&) = default; + + void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + + void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1); + void parseIsUidAnnotation(uint8_t annotationType); + void parseTruncateTimestampAnnotation(uint8_t annotationType); + void parsePrimaryFieldAnnotation(uint8_t annotationType); + void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex); + void parseExclusiveStateAnnotation(uint8_t annotationType); + void parseTriggerStateResetAnnotation(uint8_t annotationType); + void parseStateNestedAnnotation(uint8_t annotationType); + bool checkPreviousValueType(Type expected); + + /** + * The below two variables are only valid during the execution of + * parseBuffer. There are no guarantees about the state of these variables + * before/after. + */ + uint8_t* mBuf; + uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed + + bool mValid = true; // stores whether the event we received from the socket is valid /** - * Parses a log_msg into a LogEvent object. + * Side-effects: + * If there is enough space in buffer to read value of type T + * - move mBuf past the value that was just read + * - decrement mRemainingLen by size of T + * Else + * - set mValid to false */ - void init(android_log_context context); + template <class T> + T readNextValue() { + T value; + if (mRemainingLen < sizeof(T)) { + mValid = false; + value = 0; // all primitive types can successfully cast 0 + } else { + // When alignof(T) == 1, hopefully the compiler can optimize away + // this conditional as always true. + if ((reinterpret_cast<uintptr_t>(mBuf) % alignof(T)) == 0) { + // We're properly aligned, and can safely make this assignment. + value = *((T*)mBuf); + } else { + // We need to use memcpy. It's slower, but safe. + memcpy(&value, mBuf, sizeof(T)); + } + mBuf += sizeof(T); + mRemainingLen -= sizeof(T); + } + return value; + } + + template <class T> + void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) { + Field f = Field(mTagId, pos, depth); + // do not decorate last position at depth 0 + for (int i = 1; i < depth; i++) { + if (last[i]) f.decorateLastPos(i); + } + + Value v = Value(value); + mValues.push_back(FieldValue(f, v)); + } + + uint8_t getTypeId(uint8_t typeInfo); + uint8_t getNumAnnotations(uint8_t typeInfo); // The items are naturally sorted in DFS order as we read them. this allows us to do fast // matching. std::vector<FieldValue> mValues; - // This field is used when statsD wants to create log event object and write fields to it. After - // calling init() function, this object would be destroyed to save memory usage. - // When the log event is created from log msg, this field is never initiated. - android_log_context mContext = NULL; - // The timestamp set by the logd. int64_t mLogdTimestampNs; // The elapsed timestamp set by statsd log writer. int64_t mElapsedTimestampNs; - int mTagId; + // The atom tag of the event (defaults to 0 if client does not + // appropriately set the atom id). + int mTagId = 0; + + // The uid of the logging client (defaults to -1). + int32_t mLogUid = -1; + + // The pid of the logging client (defaults to -1). + int32_t mLogPid = -1; + + // Annotations + bool mTruncateTimestamp = false; + int mResetState = -1; - uint32_t mLogUid; + // Indexes within the FieldValue vector can be stored in 7 bits because + // that's the assumption enforced by the encoding used in FieldValue. + int8_t mUidFieldIndex = -1; + int8_t mAttributionChainStartIndex = -1; + int8_t mAttributionChainEndIndex = -1; + int8_t mExclusiveStateFieldIndex = -1; }; void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp index 739c5977a2d0..cd9c4e5b947b 100644 --- a/cmds/statsd/src/main.cpp +++ b/cmds/statsd/src/main.cpp @@ -20,10 +20,9 @@ #include "StatsService.h" #include "socket/StatsSocketListener.h" -#include <binder/IPCThreadState.h> -#include <binder/IServiceManager.h> -#include <binder/ProcessState.h> -#include <hidl/HidlTransportSupport.h> +#include <android/binder_interface_utils.h> +#include <android/binder_process.h> +#include <android/binder_manager.h> #include <utils/Looper.h> #include <stdio.h> @@ -33,31 +32,35 @@ using namespace android; using namespace android::os::statsd; - -/** - * Thread function data. - */ -struct log_reader_thread_data { - sp<StatsService> service; -}; - - -sp<StatsService> gStatsService = nullptr; - -void sigHandler(int sig) { - if (gStatsService != nullptr) { - gStatsService->Terminate(); +using ::ndk::SharedRefBase; +using std::shared_ptr; +using std::make_shared; + +shared_ptr<StatsService> gStatsService = nullptr; +sp<StatsSocketListener> gSocketListener = nullptr; + +void signalHandler(int sig) { + if (sig == SIGPIPE) { + // ShellSubscriber uses SIGPIPE as a signal to detect the end of the + // client process. Don't prematurely exit(1) here. Instead, ignore the + // signal and allow the write call to return EPIPE. + ALOGI("statsd received SIGPIPE. Ignoring signal."); + return; } + + if (gSocketListener != nullptr) gSocketListener->stopListener(); + if (gStatsService != nullptr) gStatsService->Terminate(); ALOGW("statsd terminated on receiving signal %d.", sig); exit(1); } -void registerSigHandler() +void registerSignalHandlers() { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; - sa.sa_handler = sigHandler; + sa.sa_handler = signalHandler; + sigaction(SIGPIPE, &sa, nullptr); sigaction(SIGHUP, &sa, nullptr); sigaction(SIGINT, &sa, nullptr); sigaction(SIGQUIT, &sa, nullptr); @@ -69,35 +72,33 @@ int main(int /*argc*/, char** /*argv*/) { sp<Looper> looper(Looper::prepare(0 /* opts */)); // Set up the binder - sp<ProcessState> ps(ProcessState::self()); - ps->setThreadPoolMaxThreadCount(9); - ps->startThreadPool(); - ps->giveThreadPoolName(); - IPCThreadState::self()->disableBackgroundScheduling(true); + ABinderProcess_setThreadPoolMaxThreadCount(9); + ABinderProcess_startThreadPool(); std::shared_ptr<LogEventQueue> eventQueue = std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/); // Create the service - gStatsService = new StatsService(looper, eventQueue); - if (defaultServiceManager()->addService(String16("stats"), gStatsService, false, - IServiceManager::DUMP_FLAG_PRIORITY_NORMAL | IServiceManager::DUMP_FLAG_PROTO) - != 0) { + gStatsService = SharedRefBase::make<StatsService>(looper, eventQueue); + // TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports + // setting dumpsys priorities. + binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats"); + if (status != STATUS_OK) { ALOGE("Failed to add service as AIDL service"); return -1; } - registerSigHandler(); + registerSignalHandlers(); gStatsService->sayHiToStatsCompanion(); gStatsService->Startup(); - sp<StatsSocketListener> socketListener = new StatsSocketListener(eventQueue); + gSocketListener = new StatsSocketListener(eventQueue); ALOGI("Statsd starts to listen to socket."); // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value - if (socketListener->startListener(600)) { + if (gSocketListener->startListener(600)) { exit(1); } diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp index 2cbe2e96f142..2b4c6a3cbf1e 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -81,18 +81,17 @@ bool combinationMatch(const vector<int>& children, const LogicalOperation& opera return matched; } -bool tryMatchString(const UidMap& uidMap, const Field& field, const Value& value, - const string& str_match) { - if (isAttributionUidField(field, value)) { - int uid = value.int_value; +bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) { + if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) { + int uid = fieldValue.mValue.int_value; auto aidIt = UidMap::sAidToUidMapping.find(str_match); if (aidIt != UidMap::sAidToUidMapping.end()) { return ((int)aidIt->second) == uid; } std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/); return packageNames.find(str_match) != packageNames.end(); - } else if (value.getType() == STRING) { - return value.str_value == str_match; + } else if (fieldValue.mValue.getType() == STRING) { + return fieldValue.mValue.str_value == str_match; } return false; } @@ -228,8 +227,7 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, } case FieldValueMatcher::ValueMatcherCase::kEqString: { for (int i = start; i < end; i++) { - if (tryMatchString(uidMap, values[i].mField, values[i].mValue, - matcher.eq_string())) { + if (tryMatchString(uidMap, values[i], matcher.eq_string())) { return true; } } @@ -240,7 +238,7 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, for (int i = start; i < end; i++) { bool notEqAll = true; for (const auto& str : str_list.str_value()) { - if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) { + if (tryMatchString(uidMap, values[i], str)) { notEqAll = false; break; } @@ -255,7 +253,7 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, const auto& str_list = matcher.eq_any_string(); for (int i = start; i < end; i++) { for (const auto& str : str_list.str_value()) { - if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) { + if (tryMatchString(uidMap, values[i], str)) { return true; } } @@ -357,9 +355,10 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher, bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) { - if (simpleMatcher.field_value_matcher_size() <= 0) { - return event.GetTagId() == simpleMatcher.atom_id(); + if (event.GetTagId() != simpleMatcher.atom_id()) { + return false; } + for (const auto& matcher : simpleMatcher.field_value_matcher()) { if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) { return false; diff --git a/cmds/statsd/src/metadata_util.cpp b/cmds/statsd/src/metadata_util.cpp new file mode 100644 index 000000000000..27ee59b36242 --- /dev/null +++ b/cmds/statsd/src/metadata_util.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 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 "FieldValue.h" +#include "metadata_util.h" + +namespace android { +namespace os { +namespace statsd { + +using google::protobuf::RepeatedPtrField; + +void writeValueToProto(metadata::FieldValue* metadataFieldValue, const Value& value) { + std::string storage_value; + switch (value.getType()) { + case INT: + metadataFieldValue->set_value_int(value.int_value); + break; + case LONG: + metadataFieldValue->set_value_long(value.long_value); + break; + case FLOAT: + metadataFieldValue->set_value_float(value.float_value); + break; + case DOUBLE: + metadataFieldValue->set_value_double(value.double_value); + break; + case STRING: + metadataFieldValue->set_value_str(value.str_value.c_str()); + break; + case STORAGE: // byte array + storage_value = ((char*) value.storage_value.data()); + metadataFieldValue->set_value_storage(storage_value); + break; + default: + break; + } +} + +void writeMetricDimensionKeyToMetadataDimensionKey( + const MetricDimensionKey& metricKey, + metadata::MetricDimensionKey* metadataMetricKey) { + for (const FieldValue& fieldValue : metricKey.getDimensionKeyInWhat().getValues()) { + metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_dimension_key_in_what(); + metadata::Field* metadataField = metadataFieldValue->mutable_field(); + metadataField->set_tag(fieldValue.mField.getTag()); + metadataField->set_field(fieldValue.mField.getField()); + writeValueToProto(metadataFieldValue, fieldValue.mValue); + } + + for (const FieldValue& fieldValue : metricKey.getStateValuesKey().getValues()) { + metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_state_values_key(); + metadata::Field* metadataField = metadataFieldValue->mutable_field(); + metadataField->set_tag(fieldValue.mField.getTag()); + metadataField->set_field(fieldValue.mField.getField()); + writeValueToProto(metadataFieldValue, fieldValue.mValue); + } +} + +void writeFieldValuesFromMetadata( + const RepeatedPtrField<metadata::FieldValue>& repeatedFieldValueList, + std::vector<FieldValue>* fieldValues) { + for (const metadata::FieldValue& metadataFieldValue : repeatedFieldValueList) { + Field field(metadataFieldValue.field().tag(), metadataFieldValue.field().field()); + Value value; + switch (metadataFieldValue.value_case()) { + case metadata::FieldValue::ValueCase::kValueInt: + value = Value(metadataFieldValue.value_int()); + break; + case metadata::FieldValue::ValueCase::kValueLong: + value = Value(metadataFieldValue.value_long()); + break; + case metadata::FieldValue::ValueCase::kValueFloat: + value = Value(metadataFieldValue.value_float()); + break; + case metadata::FieldValue::ValueCase::kValueDouble: + value = Value(metadataFieldValue.value_double()); + break; + case metadata::FieldValue::ValueCase::kValueStr: + value = Value(metadataFieldValue.value_str()); + break; + case metadata::FieldValue::ValueCase::kValueStorage: + value = Value(metadataFieldValue.value_storage()); + break; + default: + break; + } + FieldValue fieldValue(field, value); + fieldValues->emplace_back(field, value); + } +} + +MetricDimensionKey loadMetricDimensionKeyFromProto( + const metadata::MetricDimensionKey& metricDimensionKey) { + std::vector<FieldValue> dimKeyInWhatFieldValues; + writeFieldValuesFromMetadata(metricDimensionKey.dimension_key_in_what(), + &dimKeyInWhatFieldValues); + std::vector<FieldValue> stateValuesFieldValues; + writeFieldValuesFromMetadata(metricDimensionKey.state_values_key(), &stateValuesFieldValues); + + HashableDimensionKey dimKeyInWhat(dimKeyInWhatFieldValues); + HashableDimensionKey stateValues(stateValuesFieldValues); + MetricDimensionKey metricKey(dimKeyInWhat, stateValues); + return metricKey; +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/external/PowerStatsPuller.h b/cmds/statsd/src/metadata_util.h index 6f15bd68fa94..84a39ff872b5 100644 --- a/cmds/statsd/src/external/PowerStatsPuller.h +++ b/cmds/statsd/src/metadata_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2020 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. @@ -13,26 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "HashableDimensionKey.h" -#pragma once - -#include "StatsPuller.h" +#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" // AlertMetadata namespace android { namespace os { namespace statsd { -/** - * Reads hal for power.stats - */ -class PowerStatsPuller : public StatsPuller { -public: - PowerStatsPuller(); +void writeMetricDimensionKeyToMetadataDimensionKey(const MetricDimensionKey& metricKey, + metadata::MetricDimensionKey* metadataMetricKey); -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override; -}; +MetricDimensionKey loadMetricDimensionKeyFromProto( + const metadata::MetricDimensionKey& metricDimensionKey); } // namespace statsd } // namespace os -} // namespace android +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index c023e6f77e7c..573961276e5b 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -18,13 +18,15 @@ #include "Log.h" #include "CountMetricProducer.h" -#include "guardrail/StatsdStats.h" -#include "stats_util.h" -#include "stats_log_util.h" +#include <inttypes.h> #include <limits.h> #include <stdlib.h> +#include "guardrail/StatsdStats.h" +#include "stats_log_util.h" +#include "stats_util.h" + using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_BOOL; using android::util::FIELD_TYPE_FLOAT; @@ -37,6 +39,7 @@ using std::map; using std::string; using std::unordered_map; using std::vector; +using std::shared_ptr; namespace android { namespace os { @@ -48,28 +51,31 @@ const int FIELD_ID_COUNT_METRICS = 5; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for CountMetricDataWrapper const int FIELD_ID_DATA = 1; // for CountMetricData const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_DIMENSION_IN_CONDITION = 2; +const int FIELD_ID_SLICE_BY_STATE = 6; const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5; // for CountBucketInfo const int FIELD_ID_COUNT = 3; const int FIELD_ID_BUCKET_NUM = 4; const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; -CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric, - const int conditionIndex, - const sp<ConditionWizard>& wizard, - const int64_t timeBaseNs, const int64_t startTimeNs) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard) { +CountMetricProducer::CountMetricProducer( + const ConfigKey& key, const CountMetric& metric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, + const int64_t timeBaseNs, const int64_t startTimeNs, + const unordered_map<int, shared_ptr<Activation>>& eventActivationMap, + const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap, + const vector<int>& slicedStateAtoms, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, + eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap) { if (metric.has_bucket()) { mBucketSizeNs = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; @@ -82,12 +88,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); } - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); - - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); - } + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); if (metric.links().size() > 0) { for (const auto& link : metric.links()) { @@ -100,7 +101,13 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } flushIfNeededLocked(startTimeNs); // Adjust start for partial bucket @@ -114,6 +121,14 @@ CountMetricProducer::~CountMetricProducer() { VLOG("~CountMetricProducer() called"); } +void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { + VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d", + (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), + oldState.mValue.int_value, newState.mValue.int_value); +} + void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { if (mCurrentSlicedCounter == nullptr || mCurrentSlicedCounter->size() == 0) { @@ -124,10 +139,9 @@ void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { (unsigned long)mCurrentSlicedCounter->size()); if (verbose) { for (const auto& it : *mCurrentSlicedCounter) { - fprintf(out, "\t(what)%s\t(condition)%s %lld\n", - it.first.getDimensionKeyInWhat().toString().c_str(), - it.first.getDimensionKeyInCondition().toString().c_str(), - (unsigned long long)it.second); + fprintf(out, "\t(what)%s\t(state)%s %lld\n", + it.first.getDimensionKeyInWhat().toString().c_str(), + it.first.getStateValuesKey().toString().c_str(), (unsigned long long)it.second); } } } @@ -171,13 +185,6 @@ void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } - } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS); @@ -195,22 +202,16 @@ void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); protoOutput->end(dimensionToken); - - if (dimensionKey.hasDimensionKeyInCondition()) { - uint64_t dimensionInConditionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); - writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), - str_set, protoOutput); - protoOutput->end(dimensionInConditionToken); - } } else { writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - if (dimensionKey.hasDimensionKeyInCondition()) { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(), - FIELD_ID_DIMENSION_LEAF_IN_CONDITION, - str_set, protoOutput); - } + } + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); } // Then fill bucket_info (CountBucketInfo). for (const auto& bucket : counter.second) { @@ -266,6 +267,7 @@ bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { ALOGE("CountMetric %lld dropping data for dimension key %s", (long long)mMetricId, newKey.toString().c_str()); + StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); return true; } } @@ -275,12 +277,12 @@ bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { void CountMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) { + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map<int, HashableDimensionKey>& statePrimaryKeys) { int64_t eventTimeNs = event.GetElapsedTimestampNs(); flushIfNeededLocked(eventTimeNs); - if (condition == false) { + if (!condition) { return; } diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index b4a910c6f410..f05fb061ccc1 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -17,15 +17,16 @@ #ifndef COUNT_METRIC_PRODUCER_H #define COUNT_METRIC_PRODUCER_H -#include <unordered_map> - #include <android/util/ProtoOutputStream.h> #include <gtest/gtest_prod.h> -#include "../anomaly/AnomalyTracker.h" -#include "../condition/ConditionTracker.h" -#include "../matchers/matcher_util.h" + +#include <unordered_map> + #include "MetricProducer.h" +#include "anomaly/AnomalyTracker.h" +#include "condition/ConditionTracker.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "matchers/matcher_util.h" #include "stats_util.h" namespace android { @@ -40,17 +41,27 @@ struct CountBucket { class CountMetricProducer : public MetricProducer { public: - CountMetricProducer(const ConfigKey& key, const CountMetric& countMetric, - const int conditionIndex, const sp<ConditionWizard>& wizard, - const int64_t timeBaseNs, const int64_t startTimeNs); + CountMetricProducer( + const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, + const int64_t timeBaseNs, const int64_t startTimeNs, + const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {}, + const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& + eventDeactivationMap = {}, + const vector<int>& slicedStateAtoms = {}, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {}); virtual ~CountMetricProducer(); + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) override; + protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) override; + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map<int, HashableDimensionKey>& statePrimaryKeys) override; private: @@ -99,9 +110,11 @@ private: FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition); FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition); FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); - FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade); - FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket); FRIEND_TEST(CountMetricProducerTest, TestFirstBucket); + FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit); + + FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket); + FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 96fbf7fb5ebe..e9b043876d3d 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -36,6 +36,7 @@ using android::util::ProtoOutputStream; using std::string; using std::unordered_map; using std::vector; +using std::shared_ptr; namespace android { namespace os { @@ -47,30 +48,32 @@ const int FIELD_ID_DURATION_METRICS = 6; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for DurationMetricDataWrapper const int FIELD_ID_DATA = 1; // for DurationMetricData const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_DIMENSION_IN_CONDITION = 2; const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5; +const int FIELD_ID_SLICE_BY_STATE = 6; // for DurationBucketInfo const int FIELD_ID_DURATION = 3; const int FIELD_ID_BUCKET_NUM = 4; const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; -DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const DurationMetric& metric, - const int conditionIndex, const size_t startIndex, - const size_t stopIndex, const size_t stopAllIndex, - const bool nesting, - const sp<ConditionWizard>& wizard, - const FieldMatcher& internalDimensions, - const int64_t timeBaseNs, const int64_t startTimeNs) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard), +DurationMetricProducer::DurationMetricProducer( + const ConfigKey& key, const DurationMetric& metric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, const size_t startIndex, + const size_t stopIndex, const size_t stopAllIndex, const bool nesting, + const sp<ConditionWizard>& wizard, const FieldMatcher& internalDimensions, + const int64_t timeBaseNs, const int64_t startTimeNs, + const unordered_map<int, shared_ptr<Activation>>& eventActivationMap, + const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap, + const vector<int>& slicedStateAtoms, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, + eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap), mAggregationType(metric.aggregation_type()), mStartIndex(startIndex), mStopIndex(stopIndex), @@ -100,12 +103,7 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat ALOGE("Position ANY in dimension_in_what not supported."); } - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); - } - - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); if (metric.links().size() > 0) { for (const auto& link : metric.links()) { @@ -115,19 +113,23 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); mMetric2ConditionLinks.push_back(mc); } + mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); mUnSlicedPartCondition = ConditionState::kUnknown; + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } + mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions); - if (mWizard != nullptr && mConditionTrackerIndex >= 0) { - mSameConditionDimensionsInTracker = - mWizard->equalOutputDimensions(mConditionTrackerIndex, mDimensionsInCondition); - if (mMetric2ConditionLinks.size() == 1) { - mHasLinksToAllConditionDimensionsInTracker = - mWizard->equalOutputDimensions(mConditionTrackerIndex, - mMetric2ConditionLinks.begin()->conditionFields); - } + if (mWizard != nullptr && mConditionTrackerIndex >= 0 && + mMetric2ConditionLinks.size() == 1) { + mHasLinksToAllConditionDimensionsInTracker = mWizard->equalOutputDimensions( + mConditionTrackerIndex, mMetric2ConditionLinks.begin()->conditionFields); } flushIfNeededLocked(startTimeNs); // Adjust start for partial bucket @@ -158,33 +160,58 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker( return anomalyTracker; } +void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, + const FieldValue& newState) { + // Check if this metric has a StateMap. If so, map the new state value to + // the correct state group id. + FieldValue newStateCopy = newState; + mapStateValue(atomId, &newStateCopy); + + flushIfNeededLocked(eventTimeNs); + + // Each duration tracker is mapped to a different whatKey (a set of values from the + // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the + // state change event are a subset of the tracker's whatKey field values. + // + // Ex. For a duration metric dimensioned on uid and tag: + // DurationTracker1 whatKey = uid: 1001, tag: 1 + // DurationTracker2 whatKey = uid: 1002, tag 1 + // + // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state + // change. + for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { + if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) { + continue; + } + whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy); + } +} + unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( const MetricDimensionKey& eventKey) const { switch (mAggregationType) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); case DurationMetric_AggregationType_MAX_SPARSE: return make_unique<MaxDurationTracker>( - mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, - mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum, - mTimeBaseNs, mBucketSizeNs, mConditionSliced, - mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); + mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested, + mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs, + mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers); } } // SlicedConditionChange optimization case 1: // 1. If combination condition, logical operation is AND, only one sliced child predicate. -// 2. No condition in dimension -// 3. The links covers all dimension fields in the sliced child condition predicate. +// 2. The links covers all dimension fields in the sliced child condition predicate. void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool condition, const int64_t eventTime) { if (mMetric2ConditionLinks.size() != 1 || - !mHasLinksToAllConditionDimensionsInTracker || - !mDimensionsInCondition.empty()) { + !mHasLinksToAllConditionDimensionsInTracker) { return; } @@ -213,15 +240,11 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool conditio mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, &trueConditionDimensions); for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { HashableDimensionKey linkedConditionDimensionKey; - getDimensionForCondition(whatIt.first.getValues(), - mMetric2ConditionLinks[0], + getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0], &linkedConditionDimensionKey); if (trueConditionDimensions.find(linkedConditionDimensionKey) != trueConditionDimensions.end()) { - for (auto& condIt : whatIt.second) { - condIt.second->onConditionChanged( - currentUnSlicedPartCondition, eventTime); - } + whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime); } } } else { @@ -229,109 +252,15 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool conditio if (currentUnSlicedPartCondition) { for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { HashableDimensionKey linkedConditionDimensionKey; - getDimensionForCondition(whatIt.first.getValues(), - mMetric2ConditionLinks[0], + getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0], &linkedConditionDimensionKey); if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) != dimensionsChangedToTrue->end()) { - for (auto& condIt : whatIt.second) { - condIt.second->onConditionChanged(true, eventTime); - } + whatIt.second->onConditionChanged(true, eventTime); } if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) != dimensionsChangedToFalse->end()) { - for (auto& condIt : whatIt.second) { - condIt.second->onConditionChanged(false, eventTime); - } - } - } - } - } -} - - -// SlicedConditionChange optimization case 2: -// 1. If combination condition, logical operation is AND, only one sliced child predicate. -// 2. Has dimensions_in_condition and it equals to the output dimensions of the sliced predicate. -void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt2(bool condition, - const int64_t eventTime) { - if (mMetric2ConditionLinks.size() > 1 || !mSameConditionDimensionsInTracker) { - return; - } - - auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex); - auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex); - - bool currentUnSlicedPartCondition = true; - if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) { - ConditionState unslicedPartState = - mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex); - // When the unsliced part is still false, return directly. - if (mUnSlicedPartCondition == ConditionState::kFalse && - unslicedPartState == ConditionState::kFalse) { - return; - } - mUnSlicedPartCondition = unslicedPartState; - currentUnSlicedPartCondition = mUnSlicedPartCondition > 0; - } - - const std::set<HashableDimensionKey>* trueDimensionsToProcess = nullptr; - const std::set<HashableDimensionKey>* falseDimensionsToProcess = nullptr; - - std::set<HashableDimensionKey> currentTrueConditionDimensions; - if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr || - (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) { - mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, ¤tTrueConditionDimensions); - trueDimensionsToProcess = ¤tTrueConditionDimensions; - } else if (currentUnSlicedPartCondition) { - // Handles the condition change from the sliced predicate. If the unsliced condition state - // is not true, not need to do anything. - trueDimensionsToProcess = dimensionsChangedToTrue; - falseDimensionsToProcess = dimensionsChangedToFalse; - } - - if (trueDimensionsToProcess == nullptr && falseDimensionsToProcess == nullptr) { - return; - } - - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - if (falseDimensionsToProcess != nullptr) { - for (const auto& changedDim : *falseDimensionsToProcess) { - auto condIt = whatIt.second.find(changedDim); - if (condIt != whatIt.second.end()) { - condIt->second->onConditionChanged(false, eventTime); - } - } - } - if (trueDimensionsToProcess != nullptr) { - HashableDimensionKey linkedConditionDimensionKey; - if (!trueDimensionsToProcess->empty() && mMetric2ConditionLinks.size() == 1) { - getDimensionForCondition(whatIt.first.getValues(), - mMetric2ConditionLinks[0], - &linkedConditionDimensionKey); - } - for (auto& trueDim : *trueDimensionsToProcess) { - auto condIt = whatIt.second.find(trueDim); - if (condIt != whatIt.second.end()) { - condIt->second->onConditionChanged( - currentUnSlicedPartCondition, eventTime); - } else { - if (mMetric2ConditionLinks.size() == 0 || - trueDim.contains(linkedConditionDimensionKey)) { - if (!whatIt.second.empty()) { - auto newEventKey = MetricDimensionKey(whatIt.first, trueDim); - if (hitGuardRailLocked(newEventKey)) { - continue; - } - unique_ptr<DurationTracker> newTracker = - whatIt.second.begin()->second->clone(eventTime); - if (newTracker != nullptr) { - newTracker->setEventKey(newEventKey); - newTracker->onConditionChanged(true, eventTime); - whatIt.second[trueDim] = std::move(newTracker); - } - } - } + whatIt.second->onConditionChanged(false, eventTime); } } } @@ -341,85 +270,14 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt2(bool conditio void DurationMetricProducer::onSlicedConditionMayChangeInternalLocked(bool overallCondition, const int64_t eventTimeNs) { bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex); - if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker && - mDimensionsInCondition.empty()) { + if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker) { onSlicedConditionMayChangeLocked_opt1(overallCondition, eventTimeNs); return; } - if (changeDimTrackable && mSameConditionDimensionsInTracker && - mMetric2ConditionLinks.size() <= 1) { - onSlicedConditionMayChangeLocked_opt2(overallCondition, eventTimeNs); - return; - } - // Now for each of the on-going event, check if the condition has changed for them. for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onSlicedConditionMayChange(overallCondition, eventTimeNs); - } - } - - if (mDimensionsInCondition.empty()) { - return; - } - - if (mMetric2ConditionLinks.empty()) { - std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet; - mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - &conditionDimensionsKeySet); - for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (const auto& pair : whatIt.second) { - conditionDimensionsKeySet.erase(pair.first); - } - } - for (const auto& conditionDimension : conditionDimensionsKeySet) { - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - if (!whatIt.second.empty()) { - auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension); - if (hitGuardRailLocked(newEventKey)) { - continue; - } - unique_ptr<DurationTracker> newTracker = - whatIt.second.begin()->second->clone(eventTimeNs); - if (newTracker != nullptr) { - newTracker->setEventKey(MetricDimensionKey(newEventKey)); - newTracker->onSlicedConditionMayChange(overallCondition, eventTimeNs); - whatIt.second[conditionDimension] = std::move(newTracker); - } - } - } - } - } else { - for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - ConditionKey conditionKey; - for (const auto& link : mMetric2ConditionLinks) { - getDimensionForCondition(whatIt.first.getValues(), link, - &conditionKey[link.conditionId]); - } - std::unordered_set<HashableDimensionKey> conditionDimensionsKeys; - mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionsKeys); - - for (const auto& conditionDimension : conditionDimensionsKeys) { - if (!whatIt.second.empty() && - whatIt.second.find(conditionDimension) == whatIt.second.end()) { - auto newEventKey = MetricDimensionKey(whatIt.first, conditionDimension); - if (hitGuardRailLocked(newEventKey)) { - continue; - } - auto newTracker = whatIt.second.begin()->second->clone(eventTimeNs); - if (newTracker != nullptr) { - newTracker->setEventKey(newEventKey); - newTracker->onSlicedConditionMayChange(overallCondition, eventTimeNs); - whatIt.second[conditionDimension] = std::move(newTracker); - } - } - } - } + whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs); } } @@ -453,18 +311,14 @@ void DurationMetricProducer::onActiveStateChangedLocked(const int64_t& eventTime } for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onConditionChanged(mIsActive, eventTimeNs); - } + whatIt.second->onConditionChanged(mIsActive, eventTimeNs); } } else if (mIsActive) { flushIfNeededLocked(eventTimeNs); onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs); } else { // mConditionSliced == true && !mIsActive for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onConditionChanged(mIsActive, eventTimeNs); - } + whatIt.second->onConditionChanged(mIsActive, eventTimeNs); } } } @@ -480,9 +334,7 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, flushIfNeededLocked(eventTime); for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onConditionChanged(conditionMet, eventTime); - } + whatIt.second->onConditionChanged(conditionMet, eventTime); } } @@ -526,12 +378,6 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS); @@ -551,22 +397,16 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); protoOutput->end(dimensionToken); - - if (dimensionKey.hasDimensionKeyInCondition()) { - uint64_t dimensionInConditionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); - writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), - str_set, protoOutput); - protoOutput->end(dimensionInConditionToken); - } } else { writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - if (dimensionKey.hasDimensionKeyInCondition()) { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(), - FIELD_ID_DIMENSION_LEAF_IN_CONDITION, - str_set, protoOutput); - } + } + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); } // Then fill bucket_info (DurationBucketInfo). for (const auto& bucket : pair.second) { @@ -614,19 +454,11 @@ void DurationMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs const int64_t& nextBucketStartTimeNs) { for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin(); whatIt != mCurrentSlicedDurationTrackerMap.end();) { - for (auto it = whatIt->second.begin(); it != whatIt->second.end();) { - if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) { - VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(), - it->first.toString().c_str()); - it = whatIt->second.erase(it); - } else { - ++it; - } - } - if (whatIt->second.empty()) { + if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) { + VLOG("erase bucket for key %s", whatIt->first.toString().c_str()); whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt); } else { - whatIt++; + ++whatIt; } } StatsdStats::getInstance().noteBucketCount(mMetricId); @@ -642,34 +474,15 @@ void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { (unsigned long)mCurrentSlicedDurationTrackerMap.size()); if (verbose) { for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (const auto& slice : whatIt.second) { - fprintf(out, "\t(what)%s\t(condition)%s\n", whatIt.first.toString().c_str(), - slice.first.toString().c_str()); - slice.second->dumpStates(out, verbose); - } + fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str()); + whatIt.second->dumpStates(out, verbose); } } } bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat()); - if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - auto condIt = whatIt->second.find(newKey.getDimensionKeyInCondition()); - if (condIt != whatIt->second.end()) { - return false; - } - if (whatIt->second.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = whatIt->second.size() + 1; - StatsdStats::getInstance().noteMetricDimensionInConditionSize( - mConfigKey, mMetricId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("DurationMetric %lld dropping data for condition dimension key %s", - (long long)mMetricId, newKey.getDimensionKeyInCondition().toString().c_str()); - return true; - } - } - } else { + if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1; @@ -679,6 +492,7 @@ bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { ALOGE("DurationMetric %lld dropping data for what dimension key %s", (long long)mMetricId, newKey.getDimensionKeyInWhat().toString().c_str()); + StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); return true; } } @@ -690,46 +504,36 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { const auto& whatKey = eventKey.getDimensionKeyInWhat(); - const auto& condKey = eventKey.getDimensionKeyInCondition(); - auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { if (hitGuardRailLocked(eventKey)) { return; } - mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey); - } else { - if (whatIt->second.find(condKey) == whatIt->second.end()) { - if (hitGuardRailLocked(eventKey)) { - return; - } - mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey); - } + mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey); } - auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(condKey); + auto it = mCurrentSlicedDurationTrackerMap.find(whatKey); if (mUseWhatDimensionAsInternalDimension) { - it->second->noteStart(whatKey, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys); return; } if (mInternalDimensions.empty()) { - it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, - event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(), + conditionKeys); } else { HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY; filterValues(mInternalDimensions, event.getValues(), &dimensionKey); - it->second->noteStart( - dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys); + it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(), + conditionKeys); } } void DurationMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKeys, bool condition, - const LogEvent& event) { + const ConditionKey& conditionKeys, bool condition, const LogEvent& event, + const map<int, HashableDimensionKey>& statePrimaryKeys) { ALOGW("Not used in duration tracker."); } @@ -747,18 +551,49 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, // Handles Stopall events. if (matcherIndex == mStopAllIndex) { for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->noteStopAll(event.GetElapsedTimestampNs()); - } + whatIt.second->noteStopAll(event.GetElapsedTimestampNs()); } return; } - HashableDimensionKey dimensionInWhat; + HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY; if (!mDimensionsInWhat.empty()) { filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); - } else { - dimensionInWhat = DEFAULT_DIMENSION_KEY; + } + + // Stores atom id to primary key pairs for each state atom that the metric is + // sliced by. + std::map<int, HashableDimensionKey> statePrimaryKeys; + + // For states with primary fields, use MetricStateLinks to get the primary + // field values from the log event. These values will form a primary key + // that will be used to query StateTracker for the correct state value. + for (const auto& stateLink : mMetric2StateLinks) { + getDimensionForState(event.getValues(), stateLink, + &statePrimaryKeys[stateLink.stateAtomId]); + } + + // For each sliced state, query StateTracker for the state value using + // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. + // + // Expected functionality: for any case where the MetricStateLinks are + // initialized incorrectly (ex. # of state links != # of primary fields, no + // links are provided for a state with primary fields, links are provided + // in the wrong order, etc.), StateTracker will simply return kStateUnknown + // when queried using an incorrect key. + HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY; + for (auto atomId : mSlicedStateAtoms) { + FieldValue value; + if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { + // found a primary key for this state, query using the key + queryStateValue(atomId, statePrimaryKeys[atomId], &value); + } else { + // if no MetricStateLinks exist for this state atom, + // query using the default dimension key (empty HashableDimensionKey) + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + } + mapStateValue(atomId, &value); + stateValuesKey.addValue(value); } // Handles Stop events. @@ -766,9 +601,7 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, if (mUseWhatDimensionAsInternalDimension) { auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - for (const auto& condIt : whatIt->second) { - condIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false); - } + whatIt->second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false); } return; } @@ -780,62 +613,31 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - for (const auto& condIt : whatIt->second) { - condIt.second->noteStop( - internalDimensionKey, event.GetElapsedTimestampNs(), false); - } + whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false); } return; } bool condition; ConditionKey conditionKey; - std::unordered_set<HashableDimensionKey> dimensionKeysInCondition; if (mConditionSliced) { for (const auto& link : mMetric2ConditionLinks) { getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]); } auto conditionState = - mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &dimensionKeysInCondition); + mWizard->query(mConditionTrackerIndex, conditionKey, + !mHasLinksToAllConditionDimensionsInTracker); condition = conditionState == ConditionState::kTrue; - if (mDimensionsInCondition.empty() && condition) { - dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); - } } else { // TODO: The unknown condition state is not handled here, we should fix it. condition = mCondition == ConditionState::kTrue; - if (condition) { - dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); - } } condition = condition && mIsActive; - if (dimensionKeysInCondition.empty()) { - handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), - conditionKey, condition, event); - } else { - auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); - // If the what dimension is already there, we should update all the trackers even - // the condition is false. - if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - for (const auto& condIt : whatIt->second) { - const bool cond = dimensionKeysInCondition.find(condIt.first) != - dimensionKeysInCondition.end() && condition; - handleStartEvent(MetricDimensionKey(dimensionInWhat, condIt.first), - conditionKey, cond, event); - dimensionKeysInCondition.erase(condIt.first); - } - } - for (const auto& conditionDimension : dimensionKeysInCondition) { - handleStartEvent(MetricDimensionKey(dimensionInWhat, conditionDimension), conditionKey, - condition, event); - } - } + handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition, + event); } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 56c9fd68eac5..bfe1010c89de 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -38,24 +38,33 @@ namespace statsd { class DurationMetricProducer : public MetricProducer { public: - DurationMetricProducer(const ConfigKey& key, const DurationMetric& durationMetric, - const int conditionIndex, const size_t startIndex, - const size_t stopIndex, const size_t stopAllIndex, const bool nesting, - const sp<ConditionWizard>& wizard, - const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs); + DurationMetricProducer( + const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, const size_t startIndex, + const size_t stopIndex, const size_t stopAllIndex, const bool nesting, + const sp<ConditionWizard>& wizard, const FieldMatcher& internalDimensions, + const int64_t timeBaseNs, const int64_t startTimeNs, + const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {}, + const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {}, + const vector<int>& slicedStateAtoms = {}, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {}); virtual ~DurationMetricProducer(); sp<AnomalyTracker> addAnomalyTracker(const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) override; + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) override; + protected: void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override; void onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKeys, bool condition, - const LogEvent& event) override; + const ConditionKey& conditionKeys, bool condition, const LogEvent& event, + const std::map<int, HashableDimensionKey>& statePrimaryKeys) override; private: void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys, @@ -127,13 +136,12 @@ private: std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets; // The duration trackers in the current bucket. - std::unordered_map<HashableDimensionKey, - std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>> + std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>> mCurrentSlicedDurationTrackerMap; // Helper function to create a duration tracker given the metric aggregation type. std::unique_ptr<DurationTracker> createDurationTracker( - const MetricDimensionKey& eventKey) const; + const MetricDimensionKey& eventKey) const; // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; @@ -146,12 +154,14 @@ private: FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState); - FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade); - FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket); - FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade); - FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket); FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket); + + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, + TestSumDurationWithSplitInFollowingBucket); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 96133bd0a38d..dc0036a687f3 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -36,6 +36,7 @@ using std::map; using std::string; using std::unordered_map; using std::vector; +using std::shared_ptr; namespace android { namespace os { @@ -51,11 +52,16 @@ const int FIELD_ID_DATA = 1; const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1; const int FIELD_ID_ATOMS = 2; -EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric& metric, - const int conditionIndex, - const sp<ConditionWizard>& wizard, - const int64_t startTimeNs) - : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) { +EventMetricProducer::EventMetricProducer( + const ConfigKey& key, const EventMetric& metric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, + const int64_t startTimeNs, + const unordered_map<int, shared_ptr<Activation>>& eventActivationMap, + const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap, + const vector<int>& slicedStateAtoms, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) + : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, initialConditionCache, wizard, + eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap) { if (metric.links().size() > 0) { for (const auto& link : metric.links()) { Metric2Condition mc; @@ -138,16 +144,15 @@ void EventMetricProducer::onConditionChangedLocked(const bool conditionMet, void EventMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) { + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map<int, HashableDimensionKey>& statePrimaryKeys) { if (!condition) { return; } uint64_t wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); - const int64_t elapsedTimeNs = truncateTimestampIfNecessary( - event.GetTagId(), event.GetElapsedTimestampNs()); + const int64_t elapsedTimeNs = truncateTimestampIfNecessary(event); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_ELAPSED_TIMESTAMP_NANOS, (long long) elapsedTimeNs); uint64_t eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h index 74e6bc845c04..bfb2de36fad4 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -33,17 +33,23 @@ namespace statsd { class EventMetricProducer : public MetricProducer { public: - EventMetricProducer(const ConfigKey& key, const EventMetric& eventMetric, - const int conditionIndex, const sp<ConditionWizard>& wizard, - const int64_t startTimeNs); + EventMetricProducer( + const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, + const int64_t startTimeNs, + const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {}, + const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& + eventDeactivationMap = {}, + const vector<int>& slicedStateAtoms = {}, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {}); virtual ~EventMetricProducer(); private: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) override; + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map<int, HashableDimensionKey>& statePrimaryKeys) override; void onDumpReportLocked(const int64_t dumpTimeNs, const bool include_current_partial_bucket, diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 4f437d1af51a..020f4b638f4d 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -46,19 +46,21 @@ const int FIELD_ID_GAUGE_METRICS = 8; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for GaugeMetricDataWrapper const int FIELD_ID_DATA = 1; const int FIELD_ID_SKIPPED = 2; +// for SkippedBuckets const int FIELD_ID_SKIPPED_START_MILLIS = 3; const int FIELD_ID_SKIPPED_END_MILLIS = 4; +const int FIELD_ID_SKIPPED_DROP_EVENT = 5; +// for DumpEvent Proto +const int FIELD_ID_BUCKET_DROP_REASON = 1; +const int FIELD_ID_DROP_TIME = 2; // for GaugeMetricData const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_DIMENSION_IN_CONDITION = 2; const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5; // for GaugeBucketInfo const int FIELD_ID_ATOM = 3; const int FIELD_ID_ELAPSED_ATOM_TIMESTAMP = 4; @@ -68,11 +70,15 @@ const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 8; GaugeMetricProducer::GaugeMetricProducer( const ConfigKey& key, const GaugeMetric& metric, const int conditionIndex, - const sp<ConditionWizard>& wizard, const int whatMatcherIndex, - const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int triggerAtomId, - const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs, - const sp<StatsPullerManager>& pullerManager) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard), + const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, + const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard, + const int pullTagId, const int triggerAtomId, const int atomId, const int64_t timeBaseNs, + const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager, + const unordered_map<int, shared_ptr<Activation>>& eventActivationMap, + const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard, + eventActivationMap, eventDeactivationMap, /*slicedStateAtoms=*/{}, + /*stateGroupMap=*/{}), mWhatMatcherIndex(whatMatcherIndex), mEventMatcherWizard(matcherWizard), mPullerManager(pullerManager), @@ -113,10 +119,6 @@ GaugeMetricProducer::GaugeMetricProducer( mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); } - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); - } - if (metric.links().size() > 0) { for (const auto& link : metric.links()) { Metric2Condition mc; @@ -125,19 +127,18 @@ GaugeMetricProducer::GaugeMetricProducer( translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); mMetric2ConditionLinks.push_back(mc); } + mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); flushIfNeededLocked(startTimeNs); // Kicks off the puller immediately. if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - mPullerManager->RegisterReceiver(mPullTagId, this, getCurrentBucketEndTimeNs(), + mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(), mBucketSizeNs); } - // Adjust start for partial bucket + // Adjust start for partial first bucket and then pull if needed mCurrentBucketStartTimeNs = startTimeNs; VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d", @@ -148,7 +149,7 @@ GaugeMetricProducer::GaugeMetricProducer( GaugeMetricProducer::~GaugeMetricProducer() { VLOG("~GaugeMetricProducer() called"); if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { - mPullerManager->UnRegisterReceiver(mPullTagId, this); + mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this); } } @@ -162,10 +163,9 @@ void GaugeMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { (unsigned long)mCurrentSlicedBucket->size()); if (verbose) { for (const auto& it : *mCurrentSlicedBucket) { - fprintf(out, "\t(what)%s\t(condition)%s %d atoms\n", - it.first.getDimensionKeyInWhat().toString().c_str(), - it.first.getDimensionKeyInCondition().toString().c_str(), - (int)it.second.size()); + fprintf(out, "\t(what)%s\t(states)%s %d atoms\n", + it.first.getDimensionKeyInWhat().toString().c_str(), + it.first.getStateValuesKey().toString().c_str(), (int)it.second.size()); } } } @@ -192,7 +192,7 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked()); - if (mPastBuckets.empty()) { + if (mPastBuckets.empty() && mSkippedBuckets.empty()) { return; } @@ -207,23 +207,25 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS); - for (const auto& pair : mSkippedBuckets) { + for (const auto& skippedBucket : mSkippedBuckets) { uint64_t wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS, - (long long)(NanoToMillis(pair.first))); + (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs))); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS, - (long long)(NanoToMillis(pair.second))); + (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs))); + + for (const auto& dropEvent : skippedBucket.dropEvents) { + uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SKIPPED_DROP_EVENT); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long) (NanoToMillis(dropEvent.dropTimeNs))); + protoOutput->end(dropEventToken); + } protoOutput->end(wrapperToken); } @@ -240,22 +242,9 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); protoOutput->end(dimensionToken); - - if (dimensionKey.hasDimensionKeyInCondition()) { - uint64_t dimensionInConditionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); - writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), - str_set, protoOutput); - protoOutput->end(dimensionInConditionToken); - } } else { writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - if (dimensionKey.hasDimensionKeyInCondition()) { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(), - FIELD_ID_DIMENSION_LEAF_IN_CONDITION, - str_set, protoOutput); - } } // Then fill bucket_info (GaugeBucketInfo). @@ -282,11 +271,9 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->end(atomsToken); } for (const auto& atom : bucket.mGaugeAtoms) { - const int64_t elapsedTimestampNs = - truncateTimestampIfNecessary(mAtomId, atom.mElapsedTimestamps); - protoOutput->write( - FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_ELAPSED_ATOM_TIMESTAMP, - (long long)elapsedTimestampNs); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | + FIELD_ID_ELAPSED_ATOM_TIMESTAMP, + (long long)atom.mElapsedTimestampNs); } } protoOutput->end(bucketInfoToken); @@ -335,18 +322,17 @@ void GaugeMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { return; } vector<std::shared_ptr<LogEvent>> allData; - if (!mPullerManager->Pull(mPullTagId, &allData)) { + if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) { ALOGE("Gauge Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); return; } const int64_t pullDelayNs = getElapsedRealtimeNs() - timestampNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); if (pullDelayNs > mMaxPullDelayNs) { ALOGE("Pull finish too late for atom %d", mPullTagId); StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); return; } - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); for (const auto& data : allData) { LogEvent localCopy = data->makeCopy(); localCopy.setElapsedTimestampNs(timestampNs); @@ -429,6 +415,13 @@ void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven if (!pullSuccess || allData.size() == 0) { return; } + const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); + if (pullDelayNs > mMaxPullDelayNs) { + ALOGE("Pull finish too late for atom %d", mPullTagId); + StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); + return; + } for (const auto& data : allData) { if (mEventMatcherWizard->matchLogEvent( *data, mWhatMatcherIndex) == MatchingState::kMatched) { @@ -449,6 +442,7 @@ bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { if (newTupleCount > mDimensionHardLimit) { ALOGE("GaugeMetric %lld dropping data for dimension key %s", (long long)mMetricId, newKey.toString().c_str()); + StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); return true; } } @@ -458,8 +452,8 @@ bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { void GaugeMetricProducer::onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) { + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map<int, HashableDimensionKey>& statePrimaryKeys) { if (condition == false) { return; } @@ -488,7 +482,9 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( if ((*mCurrentSlicedBucket)[eventKey].size() >= mGaugeAtomsPerDimensionLimit) { return; } - GaugeAtom gaugeAtom(getGaugeFields(event), eventTimeNs); + + const int64_t truncatedElapsedTimestampNs = truncateTimestampIfNecessary(event); + GaugeAtom gaugeAtom(getGaugeFields(event), truncatedElapsedTimestampNs); (*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom); // Anomaly detection on gauge metric only works when there is one numeric // field specified. @@ -558,16 +554,16 @@ void GaugeMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, const int64_t& nextBucketStartTimeNs) { int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); + int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs; GaugeBucket info; info.mBucketStartNs = mCurrentBucketStartTimeNs; - if (eventTimeNs < fullBucketEndTimeNs) { - info.mBucketEndNs = eventTimeNs; - } else { - info.mBucketEndNs = fullBucketEndTimeNs; - } + info.mBucketEndNs = bucketEndTime; - if (info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs) { + // Add bucket to mPastBuckets if bucket is large enough. + // Otherwise, drop the bucket data and add bucket metadata to mSkippedBuckets. + bool isBucketLargeEnough = info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; + if (isBucketLargeEnough) { for (const auto& slice : *mCurrentSlicedBucket) { info.mGaugeAtoms = slice.second; auto& bucketList = mPastBuckets[slice.first]; @@ -576,7 +572,13 @@ void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, slice.first.toString().c_str()); } } else { - mSkippedBuckets.emplace_back(info.mBucketStartNs, info.mBucketEndNs); + mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs; + mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime; + if (!maxDropEventsReached()) { + mCurrentSkippedBucket.dropEvents.emplace_back( + buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL)); + } + mSkippedBuckets.emplace_back(mCurrentSkippedBucket); } // If we have anomaly trackers, we need to update the partial bucket values. @@ -595,6 +597,7 @@ void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, StatsdStats::getInstance().noteBucketCount(mMetricId); mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>(); mCurrentBucketStartTimeNs = nextBucketStartTimeNs; + mCurrentSkippedBucket.reset(); } size_t GaugeMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index a612adf8a38b..2fc772b6b641 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -35,10 +35,10 @@ namespace statsd { struct GaugeAtom { GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t elapsedTimeNs) - : mFields(fields), mElapsedTimestamps(elapsedTimeNs) { + : mFields(fields), mElapsedTimestampNs(elapsedTimeNs) { } std::shared_ptr<vector<FieldValue>> mFields; - int64_t mElapsedTimestamps; + int64_t mElapsedTimestampNs; }; struct GaugeBucket { @@ -56,13 +56,16 @@ typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>> // producer always reports the guage at the earliest time of the bucket when the condition is met. class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver { public: - GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric, - const int conditionIndex, const sp<ConditionWizard>& conditionWizard, - const int whatMatcherIndex, - const sp<EventMatcherWizard>& matcherWizard, - const int pullTagId, const int triggerAtomId, const int atomId, - const int64_t timeBaseNs, const int64_t startTimeNs, - const sp<StatsPullerManager>& pullerManager); + GaugeMetricProducer( + const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, + const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex, + const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, + const int triggerAtomId, const int atomId, const int64_t timeBaseNs, + const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager, + const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {}, + const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& + eventDeactivationMap = {}); virtual ~GaugeMetricProducer(); @@ -71,18 +74,23 @@ public: bool pullSuccess, int64_t originalPullTimeNs) override; // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. - void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) override { + void notifyAppUpgrade(const int64_t& eventTimeNs) override { std::lock_guard<std::mutex> lock(mMutex); if (!mSplitBucketForAppUpgrade) { return; } - if (eventTimeNs > getCurrentBucketEndTimeNs()) { - // Flush full buckets on the normal path up to the latest bucket boundary. - flushIfNeededLocked(eventTimeNs); + flushLocked(eventTimeNs); + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + pullAndMatchEventsLocked(eventTimeNs); } - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + + // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. + void onStatsdInitCompleted(const int64_t& eventTimeNs) override { + std::lock_guard<std::mutex> lock(mMutex); + + flushLocked(eventTimeNs); if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { pullAndMatchEventsLocked(eventTimeNs); } @@ -91,8 +99,8 @@ public: protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) override; + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map<int, HashableDimensionKey>& statePrimaryKeys) override; private: void onDumpReportLocked(const int64_t dumpTimeNs, @@ -156,9 +164,6 @@ private: // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket). std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly; - // Pairs of (elapsed start, elapsed end) denoting buckets that were skipped. - std::list<std::pair<int64_t, int64_t>> mSkippedBuckets; - const int64_t mMinBucketSizeNs; // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map @@ -191,13 +196,14 @@ private: FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition); FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition); FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition); - FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade); - FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade); FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled); FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection); FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket); FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger); FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput); + + FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents); + FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 36434eb7ceb4..fe143e496373 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -16,8 +16,12 @@ #define DEBUG false // STOPSHIP if true #include "Log.h" + #include "MetricProducer.h" +#include "../guardrail/StatsdStats.h" +#include "state/StateTracker.h" + using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_ENUM; using android::util::FIELD_TYPE_INT32; @@ -39,6 +43,34 @@ const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX = 1; const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2; const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3; +MetricProducer::MetricProducer( + const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs, + const int conditionIndex, const vector<ConditionState>& initialConditionCache, + const sp<ConditionWizard>& wizard, + const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap, + const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& + eventDeactivationMap, + const vector<int>& slicedStateAtoms, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) + : mMetricId(metricId), + mConfigKey(key), + mTimeBaseNs(timeBaseNs), + mCurrentBucketStartTimeNs(timeBaseNs), + mCurrentBucketNum(0), + mCondition(initialCondition(conditionIndex, initialConditionCache)), + mConditionTrackerIndex(conditionIndex), + mConditionSliced(false), + mWizard(wizard), + mContainANYPositionInDimensionsInWhat(false), + mSliceByPositionALL(false), + mHasLinksToAllConditionDimensionsInTracker(false), + mEventActivationMap(eventActivationMap), + mEventDeactivationMap(eventDeactivationMap), + mIsActive(mEventActivationMap.empty()), + mSlicedStateAtoms(slicedStateAtoms), + mStateGroupMap(stateGroupMap) { +} + void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) { if (!mIsActive) { return; @@ -51,38 +83,59 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo bool condition; ConditionKey conditionKey; - std::unordered_set<HashableDimensionKey> dimensionKeysInCondition; if (mConditionSliced) { for (const auto& link : mMetric2ConditionLinks) { getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]); } auto conditionState = - mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &dimensionKeysInCondition); + mWizard->query(mConditionTrackerIndex, conditionKey, + !mHasLinksToAllConditionDimensionsInTracker); condition = (conditionState == ConditionState::kTrue); } else { // TODO: The unknown condition state is not handled here, we should fix it. condition = mCondition == ConditionState::kTrue; } - if (mDimensionsInCondition.empty() && condition) { - dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); + // Stores atom id to primary key pairs for each state atom that the metric is + // sliced by. + std::map<int32_t, HashableDimensionKey> statePrimaryKeys; + + // For states with primary fields, use MetricStateLinks to get the primary + // field values from the log event. These values will form a primary key + // that will be used to query StateTracker for the correct state value. + for (const auto& stateLink : mMetric2StateLinks) { + getDimensionForState(event.getValues(), stateLink, + &statePrimaryKeys[stateLink.stateAtomId]); + } + + // For each sliced state, query StateTracker for the state value using + // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY. + // + // Expected functionality: for any case where the MetricStateLinks are + // initialized incorrectly (ex. # of state links != # of primary fields, no + // links are provided for a state with primary fields, links are provided + // in the wrong order, etc.), StateTracker will simply return kStateUnknown + // when queried using an incorrect key. + HashableDimensionKey stateValuesKey; + for (auto atomId : mSlicedStateAtoms) { + FieldValue value; + if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) { + // found a primary key for this state, query using the key + queryStateValue(atomId, statePrimaryKeys[atomId], &value); + } else { + // if no MetricStateLinks exist for this state atom, + // query using the default dimension key (empty HashableDimensionKey) + queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value); + } + mapStateValue(atomId, &value); + stateValuesKey.addValue(value); } HashableDimensionKey dimensionInWhat; filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); - MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY); - for (const auto& conditionDimensionKey : dimensionKeysInCondition) { - metricKey.setDimensionKeyInCondition(conditionDimensionKey); - onMatchedLogEventInternalLocked( - matcherIndex, metricKey, conditionKey, condition, event); - } - if (dimensionKeysInCondition.empty()) { - onMatchedLogEventInternalLocked( - matcherIndex, metricKey, conditionKey, condition, event); - } + MetricDimensionKey metricKey(dimensionInWhat, stateValuesKey); + onMatchedLogEventInternalLocked(matcherIndex, metricKey, conditionKey, condition, event, + statePrimaryKeys); } bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) { @@ -110,24 +163,6 @@ void MetricProducer::flushIfExpire(int64_t elapsedTimestampNs) { } } -void MetricProducer::addActivation(int activationTrackerIndex, const ActivationType& activationType, - int64_t ttl_seconds, int deactivationTrackerIndex) { - std::lock_guard<std::mutex> lock(mMutex); - // When a metric producer does not depend on any activation, its mIsActive is true. - // Therefore, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not - // change. - if (mEventActivationMap.empty()) { - mIsActive = false; - } - std::shared_ptr<Activation> activation = - std::make_shared<Activation>(activationType, ttl_seconds * NS_PER_SEC); - mEventActivationMap.emplace(activationTrackerIndex, activation); - if (-1 != deactivationTrackerIndex) { - auto& deactivationList = mEventDeactivationMap[deactivationTrackerIndex]; - deactivationList.push_back(activation); - } -} - void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) { auto it = mEventActivationMap.find(activationTrackerIndex); if (it == mEventActivationMap.end()) { @@ -231,6 +266,56 @@ void MetricProducer::writeActiveMetricToProtoOutputStream( } } +void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value) { + if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) { + value->mValue = Value(StateTracker::kStateUnknown); + value->mField.setTag(atomId); + ALOGW("StateTracker not found for state atom %d", atomId); + return; + } +} + +void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) { + // check if there is a state map for this atom + auto atomIt = mStateGroupMap.find(atomId); + if (atomIt == mStateGroupMap.end()) { + return; + } + auto valueIt = atomIt->second.find(value->mValue.int_value); + if (valueIt == atomIt->second.end()) { + // state map exists, but value was not put in a state group + // so set mValue to kStateUnknown + // TODO(tsaichristine): handle incomplete state maps + value->mValue.setInt(StateTracker::kStateUnknown); + } else { + // set mValue to group_id + value->mValue.setLong(valueIt->second); + } +} + +HashableDimensionKey MetricProducer::getUnknownStateKey() { + HashableDimensionKey stateKey; + for (auto atom : mSlicedStateAtoms) { + FieldValue fieldValue; + fieldValue.mField.setTag(atom); + fieldValue.mValue.setInt(StateTracker::kStateUnknown); + stateKey.addValue(fieldValue); + } + return stateKey; +} + +DropEvent MetricProducer::buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason) { + DropEvent event; + event.reason = reason; + event.dropTimeNs = dropTimeNs; + return event; +} + +bool MetricProducer::maxDropEventsReached() { + return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index c77bc0135d86..be4cd6724bb1 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -28,6 +28,8 @@ #include "config/ConfigKey.h" #include "matchers/matcher_util.h" #include "packages/PackageInfoListener.h" +#include "state/StateListener.h" +#include "state/StateManager.h" namespace android { namespace os { @@ -67,61 +69,101 @@ enum DumpLatency { NO_TIME_CONSTRAINTS = 2 }; +// Keep this in sync with BucketDropReason enum in stats_log.proto +enum BucketDropReason { + // For ValueMetric, a bucket is dropped during a dump report request iff + // current bucket should be included, a pull is needed (pulled metric and + // condition is true), and we are under fast time constraints. + DUMP_REPORT_REQUESTED = 1, + EVENT_IN_WRONG_BUCKET = 2, + CONDITION_UNKNOWN = 3, + PULL_FAILED = 4, + PULL_DELAYED = 5, + DIMENSION_GUARDRAIL_REACHED = 6, + MULTIPLE_BUCKETS_SKIPPED = 7, + // Not an invalid bucket case, but the bucket is dropped. + BUCKET_TOO_SMALL = 8, + // Not an invalid bucket case, but the bucket is skipped. + NO_DATA = 9 +}; + +struct Activation { + Activation(const ActivationType& activationType, const int64_t ttlNs) + : ttl_ns(ttlNs), + start_ns(0), + state(ActivationState::kNotActive), + activationType(activationType) {} + + const int64_t ttl_ns; + int64_t start_ns; + ActivationState state; + const ActivationType activationType; +}; + +struct DropEvent { + // Reason for dropping the bucket and/or marking the bucket invalid. + BucketDropReason reason; + // The timestamp of the drop event. + int64_t dropTimeNs; +}; + +struct SkippedBucket { + // Start time of the dropped bucket. + int64_t bucketStartTimeNs; + // End time of the dropped bucket. + int64_t bucketEndTimeNs; + // List of events that invalidated this bucket. + std::vector<DropEvent> dropEvents; + + void reset() { + bucketStartTimeNs = 0; + bucketEndTimeNs = 0; + dropEvents.clear(); + } +}; + // A MetricProducer is responsible for compute one single metrics, creating stats log report, and // writing the report to dropbox. MetricProducers should respond to package changes as required in // PackageInfoListener, but if none of the metrics are slicing by package name, then the update can // be a no-op. -class MetricProducer : public virtual android::RefBase { +class MetricProducer : public virtual android::RefBase, public virtual StateListener { public: MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs, - const int conditionIndex, const sp<ConditionWizard>& wizard) - : mMetricId(metricId), - mConfigKey(key), - mTimeBaseNs(timeBaseNs), - mCurrentBucketStartTimeNs(timeBaseNs), - mCurrentBucketNum(0), - mCondition(initialCondition(conditionIndex)), - mConditionSliced(false), - mWizard(wizard), - mConditionTrackerIndex(conditionIndex), - mContainANYPositionInDimensionsInWhat(false), - mSliceByPositionALL(false), - mSameConditionDimensionsInTracker(false), - mHasLinksToAllConditionDimensionsInTracker(false), - mIsActive(true) { - } + const int conditionIndex, const vector<ConditionState>& initialConditionCache, + const sp<ConditionWizard>& wizard, + const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap, + const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& + eventDeactivationMap, + const vector<int>& slicedStateAtoms, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap); virtual ~MetricProducer(){}; - ConditionState initialCondition(const int conditionIndex) const { - return conditionIndex >= 0 ? ConditionState::kUnknown : ConditionState::kTrue; + ConditionState initialCondition(const int conditionIndex, + const vector<ConditionState>& initialConditionCache) const { + return conditionIndex >= 0 ? initialConditionCache[conditionIndex] : ConditionState::kTrue; } /** - * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we - * first call the standard flushing code to flush up to the latest full bucket. Then we call - * the flush again when the end timestamp is forced to be now, and then after flushing, update - * the start timestamp to be now. + * Force a partial bucket split on app upgrade */ - virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) { + virtual void notifyAppUpgrade(const int64_t& eventTimeNs) { std::lock_guard<std::mutex> lock(mMutex); - - if (eventTimeNs > getCurrentBucketEndTimeNs()) { - // Flush full buckets on the normal path up to the latest bucket boundary. - flushIfNeededLocked(eventTimeNs); - } - // Now flush a partial bucket. - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); - // Don't update the current bucket number so that the anomaly tracker knows this bucket - // is a partial bucket and can merge it with the previous bucket. + flushLocked(eventTimeNs); }; - void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) { + void notifyAppRemoved(const int64_t& eventTimeNs) { // Force buckets to split on removal also. - notifyAppUpgrade(eventTimeNs, apk, uid, 0); + notifyAppUpgrade(eventTimeNs); }; + /** + * Force a partial bucket split on boot complete. + */ + virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) { + std::lock_guard<std::mutex> lock(mMutex); + flushLocked(eventTimeNs); + } // Consume the parsed stats log entry that already matched the "what" of the metric. void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) { std::lock_guard<std::mutex> lock(mMutex); @@ -143,6 +185,10 @@ public: return mConditionSliced; }; + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState){}; + // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp. // This method clears all the past buckets. void onDumpReport(const int64_t dumpTimeNs, @@ -161,9 +207,9 @@ public: return clearPastBucketsLocked(dumpTimeNs); } - void dumpStates(FILE* out, bool verbose) const { + void prepareFirstBucket() { std::lock_guard<std::mutex> lock(mMutex); - dumpStatesLocked(out, verbose); + prepareFirstBucketLocked(); } // Returns the memory in bytes currently used to store this metric's data. Does not change @@ -173,34 +219,9 @@ public: return byteSizeLocked(); } - /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */ - virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert, - const sp<AlarmMonitor>& anomalyAlarmMonitor) { - std::lock_guard<std::mutex> lock(mMutex); - sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey); - if (anomalyTracker != nullptr) { - mAnomalyTrackers.push_back(anomalyTracker); - } - return anomalyTracker; - } - - int64_t getBuckeSizeInNs() const { - std::lock_guard<std::mutex> lock(mMutex); - return mBucketSizeNs; - } - - // Only needed for unit-testing to override guardrail. - void setBucketSize(int64_t bucketSize) { - mBucketSizeNs = bucketSize; - } - - inline const int64_t& getMetricId() const { - return mMetricId; - } - - void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) { + void dumpStates(FILE* out, bool verbose) const { std::lock_guard<std::mutex> lock(mMutex); - loadActiveMetricLocked(activeMetric, currentTimeNs); + dumpStatesLocked(out, verbose); } // Let MetricProducer drop in-memory data to save memory. @@ -212,9 +233,9 @@ public: dropDataLocked(dropTimeNs); } - // For test only. - inline int64_t getCurrentBucketNum() const { - return mCurrentBucketNum; + void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) { + std::lock_guard<std::mutex> lock(mMutex); + loadActiveMetricLocked(activeMetric, currentTimeNs); } void activate(int activationTrackerIndex, int64_t elapsedTimestampNs) { @@ -232,59 +253,49 @@ public: return isActiveLocked(); } - void addActivation(int activationTrackerIndex, const ActivationType& activationType, - int64_t ttl_seconds, int deactivationTrackerIndex = -1); - - void prepareFirstBucket() { - std::lock_guard<std::mutex> lock(mMutex); - prepareFirstBucketLocked(); - } - void flushIfExpire(int64_t elapsedTimestampNs); void writeActiveMetricToProtoOutputStream( int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); -protected: - virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0; - virtual void onSlicedConditionMayChangeLocked(bool overallCondition, - const int64_t eventTime) = 0; - virtual void onDumpReportLocked(const int64_t dumpTimeNs, - const bool include_current_partial_bucket, - const bool erase_data, - const DumpLatency dumpLatency, - std::set<string> *str_set, - android::util::ProtoOutputStream* protoOutput) = 0; - virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0; - virtual size_t byteSizeLocked() const = 0; - virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0; - bool evaluateActiveStateLocked(int64_t elapsedTimestampNs); + // Start: getters/setters + inline const int64_t& getMetricId() const { + return mMetricId; + } - void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs); - void cancelEventActivationLocked(int deactivationTrackerIndex); + // For test only. + inline int64_t getCurrentBucketNum() const { + return mCurrentBucketNum; + } - inline bool isActiveLocked() const { - return mIsActive; + int64_t getBucketSizeInNs() const { + std::lock_guard<std::mutex> lock(mMutex); + return mBucketSizeNs; } - void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs); + inline const std::vector<int> getSlicedStateAtoms() { + std::lock_guard<std::mutex> lock(mMutex); + return mSlicedStateAtoms; + } - virtual void prepareFirstBucketLocked() {}; + /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */ + virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert, + const sp<AlarmMonitor>& anomalyAlarmMonitor) { + std::lock_guard<std::mutex> lock(mMutex); + sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey); + if (anomalyTracker != nullptr) { + mAnomalyTrackers.push_back(anomalyTracker); + } + return anomalyTracker; + } + // End: getters/setters +protected: /** - * Flushes the current bucket if the eventTime is after the current bucket's end time. This will - also flush the current partial bucket in memory. + * Flushes the current bucket if the eventTime is after the current bucket's end time. */ virtual void flushIfNeededLocked(const int64_t& eventTime){}; /** - * Flushes all the data including the current partial bucket. - */ - virtual void flushLocked(const int64_t& eventTimeNs) { - flushIfNeededLocked(eventTimeNs); - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); - }; - - /** * For metrics that aggregate (ie, every metric producer except for EventMetricProducer), * we need to be able to flush the current buckets on demand (ie, end the current bucket and * start new bucket). If this function is called when eventTimeNs is greater than the current @@ -297,12 +308,66 @@ protected: virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs, const int64_t& nextBucketStartTimeNs) {}; + /** + * Flushes all the data including the current partial bucket. + */ + virtual void flushLocked(const int64_t& eventTimeNs) { + flushIfNeededLocked(eventTimeNs); + flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + + /* + * Individual metrics can implement their own business logic here. All pre-processing is done. + * + * [matcherIndex]: the index of the matcher which matched this event. This is interesting to + * DurationMetric, because it has start/stop/stop_all 3 matchers. + * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have + * dimensions, it will be DEFAULT_DIMENSION_KEY + * [conditionKey]: the keys of conditions which should be used to query the condition for this + * target event (from MetricConditionLink). This is passed to individual metrics + * because DurationMetric needs it to be cached. + * [condition]: whether condition is met. If condition is sliced, this is the result coming from + * query with ConditionWizard; If condition is not sliced, this is the + * nonSlicedCondition. + * [event]: the log event, just in case the metric needs its data, e.g., EventMetric. + */ + virtual void onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map<int, HashableDimensionKey>& statePrimaryKeys) = 0; + + // Consume the parsed stats log entry that already matched the "what" of the metric. + virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event); + virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0; + virtual void onSlicedConditionMayChangeLocked(bool overallCondition, + const int64_t eventTime) = 0; + virtual void onDumpReportLocked(const int64_t dumpTimeNs, + const bool include_current_partial_bucket, + const bool erase_data, + const DumpLatency dumpLatency, + std::set<string> *str_set, + android::util::ProtoOutputStream* protoOutput) = 0; + virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0; + virtual void prepareFirstBucketLocked(){}; + virtual size_t byteSizeLocked() const = 0; + virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0; + virtual void dropDataLocked(const int64_t dropTimeNs) = 0; + void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs); + void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs); + void cancelEventActivationLocked(int deactivationTrackerIndex); + + bool evaluateActiveStateLocked(int64_t elapsedTimestampNs); + virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) { if (!mIsActive) { flushLocked(eventTimeNs); } } + inline bool isActiveLocked() const { + return mIsActive; + } + // Convenience to compute the current bucket's end time, which is always aligned with the // start time of the metric. int64_t getCurrentBucketEndTimeNs() const { @@ -313,7 +378,25 @@ protected: return (endNs - mTimeBaseNs) / mBucketSizeNs - 1; } - virtual void dropDataLocked(const int64_t dropTimeNs) = 0; + // Query StateManager for original state value using the queryKey. + // The field and value are output. + void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* value); + + // If a state map exists for the given atom, replace the original state + // value with the group id mapped to the value. + // If no state map exists, keep the original state value. + void mapStateValue(const int32_t atomId, FieldValue* value); + + // Returns a HashableDimensionKey with unknown state value for each state + // atom. + HashableDimensionKey getUnknownStateKey(); + + DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason); + + // Returns true if the number of drop events in the current bucket has + // exceeded the maximum number allowed, which is currently capped at 10. + bool maxDropEventsReached(); const int64_t mMetricId; @@ -335,21 +418,17 @@ protected: ConditionState mCondition; + int mConditionTrackerIndex; + bool mConditionSliced; sp<ConditionWizard> mWizard; - int mConditionTrackerIndex; - - vector<Matcher> mDimensionsInWhat; // The dimensions_in_what defined in statsd_config - vector<Matcher> mDimensionsInCondition; // The dimensions_in_condition defined in statsd_config - bool mContainANYPositionInDimensionsInWhat; + bool mSliceByPositionALL; - // True iff the condition dimensions equal to the sliced dimensions in the simple condition - // tracker. This field is always false for combinational condition trackers. - bool mSameConditionDimensionsInTracker; + vector<Matcher> mDimensionsInWhat; // The dimensions_in_what defined in statsd_config // True iff the metric to condition links cover all dimension fields in the condition tracker. // This field is always false for combinational condition trackers. @@ -359,43 +438,8 @@ protected: std::vector<sp<AnomalyTracker>> mAnomalyTrackers; - /* - * Individual metrics can implement their own business logic here. All pre-processing is done. - * - * [matcherIndex]: the index of the matcher which matched this event. This is interesting to - * DurationMetric, because it has start/stop/stop_all 3 matchers. - * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have - * dimensions, it will be DEFAULT_DIMENSION_KEY - * [conditionKey]: the keys of conditions which should be used to query the condition for this - * target event (from MetricConditionLink). This is passed to individual metrics - * because DurationMetric needs it to be cached. - * [condition]: whether condition is met. If condition is sliced, this is the result coming from - * query with ConditionWizard; If condition is not sliced, this is the - * nonSlicedCondition. - * [event]: the log event, just in case the metric needs its data, e.g., EventMetric. - */ - virtual void onMatchedLogEventInternalLocked( - const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) = 0; - - // Consume the parsed stats log entry that already matched the "what" of the metric. - virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event); - mutable std::mutex mMutex; - struct Activation { - Activation(const ActivationType& activationType, const int64_t ttlNs) - : ttl_ns(ttlNs), - start_ns(0), - state(ActivationState::kNotActive), - activationType(activationType) {} - - const int64_t ttl_ns; - int64_t start_ns; - ActivationState state; - const ActivationType activationType; - }; // When the metric producer has multiple activations, these activations are ORed to determine // whether the metric producer is ready to generate metrics. std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap; @@ -405,12 +449,37 @@ protected: bool mIsActive; + // The slice_by_state atom ids defined in statsd_config. + const std::vector<int32_t> mSlicedStateAtoms; + + // Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>). + const std::unordered_map<int32_t, std::unordered_map<int, int64_t>> mStateGroupMap; + + // MetricStateLinks defined in statsd_config that link fields in the state + // atom to fields in the "what" atom. + std::vector<Metric2State> mMetric2StateLinks; + + SkippedBucket mCurrentSkippedBucket; + // Buckets that were invalidated and had their data dropped. + std::vector<SkippedBucket> mSkippedBuckets; + + FRIEND_TEST(CountMetricE2eTest, TestSlicedState); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); + FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); @@ -424,6 +493,13 @@ protected: FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActivationTypes); FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); + + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + + FRIEND_TEST(MetricsManagerTest, TestInitialConditions); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 1fb2b1ce5e14..60de1a24cce5 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -21,18 +21,16 @@ #include <private/android_filesystem_config.h> #include "CountMetricProducer.h" -#include "atoms_info.h" #include "condition/CombinationConditionTracker.h" #include "condition/SimpleConditionTracker.h" #include "guardrail/StatsdStats.h" #include "matchers/CombinationLogMatchingTracker.h" #include "matchers/SimpleLogMatchingTracker.h" #include "metrics_manager_util.h" -#include "stats_util.h" +#include "state/StateManager.h" #include "stats_log_util.h" -#include "statslog.h" - -#include <private/android_filesystem_config.h> +#include "stats_util.h" +#include "statslog_statsd.h" using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_INT32; @@ -71,6 +69,9 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, mTtlEndNs(-1), mLastReportTimeNs(currentTimeNs), mLastReportWallClockNs(getWallClockNs()), + mPullerManager(pullerManager), + mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(), + config.whitelisted_atom_ids().end()), mShouldPersistHistory(config.persist_locally()) { // Init the ttl end timestamp. refreshTtl(timeBaseNs); @@ -81,12 +82,13 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers, mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap, mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap, - mMetricIndexesWithActivation, mNoReportMetricIds); + mAlertTrackerMap, mMetricIndexesWithActivation, mNoReportMetricIds); mHashStringsInReport = config.hash_strings_in_metric_report(); mVersionStringsInReport = config.version_strings_in_metric_report(); mInstallerInReport = config.installer_in_metric_report(); + // Init allowed pushed atom uids. if (config.allowed_log_source_size() == 0) { mConfigValid = false; ALOGE("Log source whitelist is empty! This config won't get any data. Suggest adding at " @@ -109,6 +111,40 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, } } + // Init default allowed pull atom uids. + int numPullPackages = 0; + for (const string& pullSource : config.default_pull_packages()) { + auto it = UidMap::sAidToUidMapping.find(pullSource); + if (it != UidMap::sAidToUidMapping.end()) { + numPullPackages++; + mDefaultPullUids.insert(it->second); + } else { + ALOGE("Default pull atom packages must be in sAidToUidMapping"); + mConfigValid = false; + } + } + // Init per-atom pull atom packages. + for (const PullAtomPackages& pullAtomPackages : config.pull_atom_packages()) { + int32_t atomId = pullAtomPackages.atom_id(); + for (const string& pullPackage : pullAtomPackages.packages()) { + numPullPackages++; + auto it = UidMap::sAidToUidMapping.find(pullPackage); + if (it != UidMap::sAidToUidMapping.end()) { + mPullAtomUids[atomId].insert(it->second); + } else { + mPullAtomPackages[atomId].insert(pullPackage); + } + } + } + if (numPullPackages > StatsdStats::kMaxPullAtomPackages) { + ALOGE("Too many sources in default_pull_packages and pull_atom_packages. This is likely to " + "be an error in the config"); + mConfigValid = false; + } else { + initPullAtomSources(); + } + mPullerManager->RegisterPullUidProvider(mConfigKey, this); + // Store the sub-configs used. for (const auto& annotation : config.annotation()) { mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32()); @@ -149,6 +185,13 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, } MetricsManager::~MetricsManager() { + for (auto it : mAllMetricProducers) { + for (int atomId : it->getSlicedStateAtoms()) { + StateManager::getInstance().unregisterListener(atomId, it); + } + } + mPullerManager->UnregisterPullUidProvider(mConfigKey, this); + VLOG("~MetricsManager()"); } @@ -168,6 +211,20 @@ void MetricsManager::initLogSourceWhiteList() { } } +void MetricsManager::initPullAtomSources() { + std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex); + mCombinedPullAtomUids.clear(); + for (const auto& [atomId, uids] : mPullAtomUids) { + mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end()); + } + for (const auto& [atomId, packages] : mPullAtomPackages) { + for (const string& pkg : packages) { + set<int32_t> uids = mUidMap->getAppUid(pkg); + mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end()); + } + } +} + bool MetricsManager::isConfigValid() const { return mConfigValid; } @@ -175,43 +232,81 @@ bool MetricsManager::isConfigValid() const { void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, const int64_t version) { // Inform all metric producers. - for (auto it : mAllMetricProducers) { - it->notifyAppUpgrade(eventTimeNs, apk, uid, version); + for (const auto& it : mAllMetricProducers) { + it->notifyAppUpgrade(eventTimeNs); } // check if we care this package - if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) { - return; + if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { + // We will re-initialize the whole list because we don't want to keep the multi mapping of + // UID<->pkg inside MetricsManager to reduce the memory usage. + initLogSourceWhiteList(); + } + + for (const auto& it : mPullAtomPackages) { + if (it.second.find(apk) != it.second.end()) { + initPullAtomSources(); + return; + } } - // We will re-initialize the whole list because we don't want to keep the multi mapping of - // UID<->pkg inside MetricsManager to reduce the memory usage. - initLogSourceWhiteList(); } void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) { // Inform all metric producers. - for (auto it : mAllMetricProducers) { - it->notifyAppRemoved(eventTimeNs, apk, uid); + for (const auto& it : mAllMetricProducers) { + it->notifyAppRemoved(eventTimeNs); } // check if we care this package - if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) { - return; + if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { + // We will re-initialize the whole list because we don't want to keep the multi mapping of + // UID<->pkg inside MetricsManager to reduce the memory usage. + initLogSourceWhiteList(); + } + + for (const auto& it : mPullAtomPackages) { + if (it.second.find(apk) != it.second.end()) { + initPullAtomSources(); + return; + } } - // We will re-initialize the whole list because we don't want to keep the multi mapping of - // UID<->pkg inside MetricsManager to reduce the memory usage. - initLogSourceWhiteList(); } void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) { // Purposefully don't inform metric producers on a new snapshot // because we don't need to flush partial buckets. // This occurs if a new user is added/removed or statsd crashes. + initPullAtomSources(); + if (mAllowedPkg.size() == 0) { return; } initLogSourceWhiteList(); } +void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) { + // Inform all metric producers. + for (const auto& it : mAllMetricProducers) { + it->onStatsdInitCompleted(eventTimeNs); + } +} + +void MetricsManager::init() { + for (const auto& producer : mAllMetricProducers) { + producer->prepareFirstBucket(); + } +} + +vector<int32_t> MetricsManager::getPullAtomUids(int32_t atomId) { + std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex); + vector<int32_t> uids; + const auto& it = mCombinedPullAtomUids.find(atomId); + if (it != mCombinedPullAtomUids.end()) { + uids.insert(uids.end(), it->second.begin(), it->second.end()); + } + uids.insert(uids.end(), mDefaultPullUids.begin(), mDefaultPullUids.end()); + return uids; +} + void MetricsManager::dumpStates(FILE* out, bool verbose) { fprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str()); { @@ -265,16 +360,18 @@ void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs, protoOutput->end(token); } - mLastReportTimeNs = dumpTimeStampNs; - mLastReportWallClockNs = getWallClockNs(); + // Do not update the timestamps when data is not cleared to avoid timestamps from being + // misaligned. + if (erase_data) { + mLastReportTimeNs = dumpTimeStampNs; + mLastReportWallClockNs = getWallClockNs(); + } VLOG("=========================Metric Reports End=========================="); } bool MetricsManager::checkLogCredentials(const LogEvent& event) { - if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) != - android::util::AtomsInfo::kWhitelistedAtoms.end()) - { + if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) { return true; } std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex); @@ -286,18 +383,21 @@ bool MetricsManager::checkLogCredentials(const LogEvent& event) { } bool MetricsManager::eventSanityCheck(const LogEvent& event) { - if (event.GetTagId() == android::util::APP_BREADCRUMB_REPORTED) { + if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) { // Check that app breadcrumb reported fields are valid. status_t err = NO_ERROR; // Uid is 3rd from last field and must match the caller's uid, // unless that caller is statsd itself (statsd is allowed to spoof uids). long appHookUid = event.GetLong(event.size()-2, &err); - if (err != NO_ERROR ) { + if (err != NO_ERROR) { VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid"); return false; } - int32_t loggerUid = event.GetUid(); + + // Because the uid within the LogEvent may have been mapped from + // isolated to host, map the loggerUid similarly before comparing. + int32_t loggerUid = mUidMap->getHostUidOrSelf(event.GetUid()); if (loggerUid != appHookUid && loggerUid != AID_STATSD) { VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d", appHookUid, loggerUid); @@ -306,21 +406,21 @@ bool MetricsManager::eventSanityCheck(const LogEvent& event) { // The state must be from 0,3. This part of code must be manually updated. long appHookState = event.GetLong(event.size(), &err); - if (err != NO_ERROR ) { + if (err != NO_ERROR) { VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field"); return false; } else if (appHookState < 0 || appHookState > 3) { VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState); return false; } - } else if (event.GetTagId() == android::util::DAVEY_OCCURRED) { + } else if (event.GetTagId() == util::DAVEY_OCCURRED) { // Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp. // Check that the davey duration is reasonable. Max length check is for privacy. status_t err = NO_ERROR; // Uid is the first field provided. long jankUid = event.GetLong(1, &err); - if (err != NO_ERROR ) { + if (err != NO_ERROR) { VLOG("Davey occurred had error when parsing the uid"); return false; } @@ -332,7 +432,7 @@ bool MetricsManager::eventSanityCheck(const LogEvent& event) { } long duration = event.GetLong(event.size(), &err); - if (err != NO_ERROR ) { + if (err != NO_ERROR) { VLOG("Davey occurred had error when parsing the duration"); return false; } else if (duration > 100000) { @@ -555,8 +655,40 @@ void MetricsManager::writeActiveConfigToProtoOutputStream( } } +bool MetricsManager::writeMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadata* statsMetadata) { + bool metadataWritten = false; + metadata::ConfigKey* configKey = statsMetadata->mutable_config_key(); + configKey->set_config_id(mConfigKey.GetId()); + configKey->set_uid(mConfigKey.GetUid()); + for (const auto& anomalyTracker : mAllAnomalyTrackers) { + metadata::AlertMetadata* alertMetadata = statsMetadata->add_alert_metadata(); + bool alertWritten = anomalyTracker->writeAlertMetadataToProto(currentWallClockTimeNs, + systemElapsedTimeNs, alertMetadata); + if (!alertWritten) { + statsMetadata->mutable_alert_metadata()->RemoveLast(); + } + metadataWritten |= alertWritten; + } + return metadataWritten; +} - +void MetricsManager::loadMetadata(const metadata::StatsMetadata& metadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs) { + for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) { + int64_t alertId = alertMetadata.alert_id(); + auto it = mAlertTrackerMap.find(alertId); + if (it == mAlertTrackerMap.end()) { + ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId); + continue; + } + mAllAnomalyTrackers[it->second]->loadAlertMetadata(alertMetadata, + currentWallClockTimeNs, + systemElapsedTimeNs); + } +} } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 34d47d426235..ad30a88c5d19 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -23,6 +23,7 @@ #include "config/ConfigKey.h" #include "external/StatsPullerManager.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" #include "logd/LogEvent.h" #include "matchers/LogMatchingTracker.h" #include "metrics/MetricProducer.h" @@ -35,7 +36,7 @@ namespace os { namespace statsd { // A MetricsManager is responsible for managing metrics from one single config source. -class MetricsManager : public virtual android::RefBase { +class MetricsManager : public virtual android::RefBase, public virtual PullUidProvider { public: MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const int64_t timeBaseNs, const int64_t currentTimeNs, const sp<UidMap>& uidMap, @@ -69,6 +70,12 @@ public: void onUidMapReceived(const int64_t& eventTimeNs); + void onStatsdInitCompleted(const int64_t& elapsedTimeNs); + + void init(); + + vector<int32_t> getPullAtomUids(int32_t atomId) override; + bool shouldWriteToDisk() const { return mNoReportMetricIds.size() != mAllMetricProducers.size(); } @@ -139,6 +146,14 @@ public: void writeActiveConfigToProtoOutputStream( int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto); + // Returns true if at least one piece of metadata is written. + bool writeMetadataToProto(int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs, + metadata::StatsMetadata* statsMetadata); + + void loadMetadata(const metadata::StatsMetadata& metadata, + int64_t currentWallClockTimeNs, + int64_t systemElapsedTimeNs); private: // For test only. inline int64_t getTtlEndNs() const { return mTtlEndNs; } @@ -159,6 +174,8 @@ private: int64_t mLastReportTimeNs; int64_t mLastReportWallClockNs; + sp<StatsPullerManager> mPullerManager; + // The uid log sources from StatsdConfig. std::vector<int32_t> mAllowedUid; @@ -169,13 +186,29 @@ private: // Logs from uids that are not in the list will be ignored to avoid spamming. std::set<int32_t> mAllowedLogSources; + // To guard access to mAllowedLogSources + mutable std::mutex mAllowedLogSourcesMutex; + + const std::set<int32_t> mWhitelistedAtomIds; + + // We can pull any atom from these uids. + std::set<int32_t> mDefaultPullUids; + + // Uids that specific atoms can pull from. + // This is a map<atom id, set<uids>> + std::map<int32_t, std::set<int32_t>> mPullAtomUids; + + // Packages that specific atoms can be pulled from. + std::map<int32_t, std::set<std::string>> mPullAtomPackages; + + // All uids to pull for this atom. NOTE: Does not include the default uids for memory. + std::map<int32_t, std::set<int32_t>> mCombinedPullAtomUids; + // Contains the annotations passed in with StatsdConfig. std::list<std::pair<const int64_t, const int32_t>> mAnnotations; const bool mShouldPersistHistory; - // To guard access to mAllowedLogSources - mutable std::mutex mAllowedLogSourcesMutex; // All event tags that are interesting to my metrics. std::set<int> mTagIds; @@ -230,10 +263,16 @@ private: // Maps deactivation triggering event to MetricProducers. std::unordered_map<int, std::vector<int>> mDeactivationAtomTrackerToMetricMap; + // Maps AlertIds to the index of the corresponding AnomalyTracker stored in mAllAnomalyTrackers. + // The map is used in LoadMetadata to more efficiently lookup AnomalyTrackers from an AlertId. + std::unordered_map<int64_t, int> mAlertTrackerMap; + std::vector<int> mMetricIndexesWithActivation; void initLogSourceWhiteList(); + void initPullAtomSources(); + // The metrics that don't need to be uploaded or even reported. std::set<int64_t> mNoReportMetricIds; @@ -253,23 +292,12 @@ private: FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation); FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition); FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); - FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); - FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition); - - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition); - FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk); + FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets); FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period); @@ -282,6 +310,8 @@ private: FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); + FRIEND_TEST(MetricsManagerTest, TestLogSources); + FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); @@ -289,12 +319,31 @@ private: TestActivationOnBootMultipleActivationsDifferentActivationTypes); FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart); + FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(CountMetricE2eTest, TestSlicedState); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); + FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); + FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); FRIEND_TEST(DurationMetricE2eTest, TestWithActivation); FRIEND_TEST(DurationMetricE2eTest, TestWithCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition); FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset); + FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); + + FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm); + FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index fa310dc707ec..5987a723a421 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -46,19 +46,22 @@ const int FIELD_ID_VALUE_METRICS = 7; const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; -const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; const int FIELD_ID_IS_ACTIVE = 14; // for ValueMetricDataWrapper const int FIELD_ID_DATA = 1; const int FIELD_ID_SKIPPED = 2; +// for SkippedBuckets const int FIELD_ID_SKIPPED_START_MILLIS = 3; const int FIELD_ID_SKIPPED_END_MILLIS = 4; +const int FIELD_ID_SKIPPED_DROP_EVENT = 5; +// for DumpEvent Proto +const int FIELD_ID_BUCKET_DROP_REASON = 1; +const int FIELD_ID_DROP_TIME = 2; // for ValueMetricData const int FIELD_ID_DIMENSION_IN_WHAT = 1; -const int FIELD_ID_DIMENSION_IN_CONDITION = 2; const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; -const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5; +const int FIELD_ID_SLICE_BY_STATE = 6; // for ValueBucketInfo const int FIELD_ID_VALUE_INDEX = 1; const int FIELD_ID_VALUE_LONG = 2; @@ -75,10 +78,17 @@ const Value ZERO_DOUBLE((int64_t)0); // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently ValueMetricProducer::ValueMetricProducer( const ConfigKey& key, const ValueMetric& metric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int64_t timeBaseNs, - const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager) - : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, conditionWizard), + const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager, + const unordered_map<int, shared_ptr<Activation>>& eventActivationMap, + const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap, + const vector<int>& slicedStateAtoms, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, + conditionWizard, eventActivationMap, eventDeactivationMap, slicedStateAtoms, + stateGroupMap), mWhatMatcherIndex(whatMatcherIndex), mEventMatcherWizard(matcherWizard), mPullerManager(pullerManager), @@ -100,11 +110,11 @@ ValueMetricProducer::ValueMetricProducer( mSkipZeroDiffOutput(metric.skip_zero_diff_output()), mUseZeroDefaultBase(metric.use_zero_default_base()), mHasGlobalBase(false), - mCurrentBucketIsInvalid(false), + mCurrentBucketIsSkipped(false), mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC : StatsdStats::kPullMaxDelayNs), mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()), - // Condition timer will be set in prepareFirstBucketLocked. + // Condition timer will be set later within the constructor after pulling events mConditionTimer(false, timeBaseNs) { int64_t bucketSizeMills = 0; if (metric.has_bucket()) { @@ -120,10 +130,7 @@ ValueMetricProducer::ValueMetricProducer( if (metric.has_dimensions_in_what()) { translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat); mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - } - - if (metric.has_dimensions_in_condition()) { - translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition); + mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); } if (metric.links().size() > 0) { @@ -134,11 +141,16 @@ ValueMetricProducer::ValueMetricProducer( translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields); mMetric2ConditionLinks.push_back(mc); } + mConditionSliced = true; } - mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || - HasPositionALL(metric.dimensions_in_condition()); + for (const auto& stateLink : metric.state_link()) { + Metric2State ms; + ms.stateAtomId = stateLink.state_atom_id(); + translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields); + translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields); + mMetric2StateLinks.push_back(ms); + } int64_t numBucketsForward = calcBucketsForwardCount(startTimeNs); mCurrentBucketNum += numBucketsForward; @@ -146,7 +158,7 @@ ValueMetricProducer::ValueMetricProducer( flushIfNeededLocked(startTimeNs); if (mIsPulled) { - mPullerManager->RegisterReceiver(mPullTagId, this, getCurrentBucketEndTimeNs(), + mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(), mBucketSizeNs); } @@ -155,6 +167,11 @@ ValueMetricProducer::ValueMetricProducer( // Adjust start for partial bucket mCurrentBucketStartTimeNs = startTimeNs; mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs); + + // Now that activations are processed, start the condition timer if needed. + mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue, + mCurrentBucketStartTimeNs); + VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs); } @@ -162,18 +179,48 @@ ValueMetricProducer::ValueMetricProducer( ValueMetricProducer::~ValueMetricProducer() { VLOG("~ValueMetricProducer() called"); if (mIsPulled) { - mPullerManager->UnRegisterReceiver(mPullTagId, this); + mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this); } } -void ValueMetricProducer::prepareFirstBucketLocked() { - // Kicks off the puller immediately if condition is true and diff based. - if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) { - pullAndMatchEventsLocked(mCurrentBucketStartTimeNs, mCondition); +void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { + VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d", + (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), + oldState.mValue.int_value, newState.mValue.int_value); + + // If old and new states are in the same StateGroup, then we do not need to + // pull for this state change. + FieldValue oldStateCopy = oldState; + FieldValue newStateCopy = newState; + mapStateValue(atomId, &oldStateCopy); + mapStateValue(atomId, &newStateCopy); + if (oldStateCopy == newStateCopy) { + return; } - // Now that activations are processed, start the condition timer if needed. - mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue, - mCurrentBucketStartTimeNs); + + // If condition is not true or metric is not active, we do not need to pull + // for this state change. + if (mCondition != ConditionState::kTrue || !mIsActive) { + return; + } + + bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs; + if (isEventLate) { + VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, + (long long)mCurrentBucketStartTimeNs); + invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); + return; + } + mStateChangePrimaryKey.first = atomId; + mStateChangePrimaryKey.second = primaryKey; + if (mIsPulled) { + pullAndMatchEventsLocked(eventTimeNs); + } + mStateChangePrimaryKey.first = 0; + mStateChangePrimaryKey.second = DEFAULT_DIMENSION_KEY; + flushIfNeededLocked(eventTimeNs); } void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, @@ -183,11 +230,9 @@ void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition void ValueMetricProducer::dropDataLocked(const int64_t dropTimeNs) { StatsdStats::getInstance().noteBucketDropped(mMetricId); - // We are going to flush the data without doing a pull first so we need to invalidte the data. - bool pullNeeded = mIsPulled && mCondition == ConditionState::kTrue; - if (pullNeeded) { - invalidateCurrentBucket(); - } + + // The current partial bucket is not flushed and does not require a pull, + // so the data is still valid. flushIfNeededLocked(dropTimeNs); clearPastBucketsLocked(dropTimeNs); } @@ -213,10 +258,10 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, if (pullNeeded) { switch (dumpLatency) { case FAST: - invalidateCurrentBucket(); + invalidateCurrentBucket(dumpTimeNs, BucketDropReason::DUMP_REPORT_REQUESTED); break; case NO_TIME_CONSTRAINTS: - pullAndMatchEventsLocked(dumpTimeNs, mCondition); + pullAndMatchEventsLocked(dumpTimeNs); break; } } @@ -238,23 +283,26 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, writeDimensionPathToProto(mDimensionsInWhat, protoOutput); protoOutput->end(dimenPathToken); } - if (!mDimensionsInCondition.empty()) { - uint64_t dimenPathToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_CONDITION); - writeDimensionPathToProto(mDimensionsInCondition, protoOutput); - protoOutput->end(dimenPathToken); - } } uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS); - for (const auto& pair : mSkippedBuckets) { + for (const auto& skippedBucket : mSkippedBuckets) { uint64_t wrapperToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS, - (long long)(NanoToMillis(pair.first))); + (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs))); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS, - (long long)(NanoToMillis(pair.second))); + (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs))); + for (const auto& dropEvent : skippedBucket.dropEvents) { + uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SKIPPED_DROP_EVENT); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, + (long long)(NanoToMillis(dropEvent.dropTimeNs))); + ; + protoOutput->end(dropEventToken); + } protoOutput->end(wrapperToken); } @@ -270,21 +318,17 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); protoOutput->end(dimensionToken); - if (dimensionKey.hasDimensionKeyInCondition()) { - uint64_t dimensionInConditionToken = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION); - writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), str_set, - protoOutput); - protoOutput->end(dimensionInConditionToken); - } } else { writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(), FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput); - if (dimensionKey.hasDimensionKeyInCondition()) { - writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInCondition(), - FIELD_ID_DIMENSION_LEAF_IN_CONDITION, str_set, - protoOutput); - } + } + + // Then fill slice_by_state. + for (auto state : dimensionKey.getStateValuesKey().getValues()) { + uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SLICE_BY_STATE); + writeStateToProto(state, protoOutput); + protoOutput->end(stateToken); } // Then fill bucket_info (ValueBucketInfo). @@ -306,7 +350,7 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS, (long long)bucket.mConditionTrueNs); } - for (int i = 0; i < (int)bucket.valueIndex.size(); i ++) { + for (int i = 0; i < (int)bucket.valueIndex.size(); i++) { int index = bucket.valueIndex[i]; const Value& value = bucket.values[i]; uint64_t valueToken = protoOutput->start( @@ -341,23 +385,34 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, } } -void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase() { - if (!mCurrentBucketIsInvalid) { - // Only report once per invalid bucket. +void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs, + const BucketDropReason reason) { + if (!mCurrentBucketIsSkipped) { + // Only report to StatsdStats once per invalid bucket. StatsdStats::getInstance().noteInvalidatedBucket(mMetricId); } - mCurrentBucketIsInvalid = true; + + skipCurrentBucket(dropTimeNs, reason); } -void ValueMetricProducer::invalidateCurrentBucket() { - invalidateCurrentBucketWithoutResetBase(); +void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs, + const BucketDropReason reason) { + invalidateCurrentBucketWithoutResetBase(dropTimeNs, reason); resetBase(); } +void ValueMetricProducer::skipCurrentBucket(const int64_t dropTimeNs, + const BucketDropReason reason) { + if (!maxDropEventsReached()) { + mCurrentSkippedBucket.dropEvents.emplace_back(buildDropEvent(dropTimeNs, reason)); + } + mCurrentBucketIsSkipped = true; +} + void ValueMetricProducer::resetBase() { - for (auto& slice : mCurrentSlicedBucket) { - for (auto& interval : slice.second) { - interval.hasBase = false; + for (auto& slice : mCurrentBaseInfo) { + for (auto& baseInfo : slice.second) { + baseInfo.hasBase = false; } } mHasGlobalBase = false; @@ -369,9 +424,10 @@ void ValueMetricProducer::resetBase() { // - ConditionTimer tracks changes based on AND of condition and active state. void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) { bool isEventTooLate = eventTimeNs < mCurrentBucketStartTimeNs; - if (ConditionState::kTrue == mCondition && isEventTooLate) { + if (isEventTooLate) { // Drop bucket because event arrived too late, ie. we are missing data for this bucket. - invalidateCurrentBucket(); + StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); + invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); } // Call parent method once we've verified the validity of current bucket. @@ -384,7 +440,7 @@ void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) // Pull on active state changes. if (!isEventTooLate) { if (mIsPulled) { - pullAndMatchEventsLocked(eventTimeNs, mCondition); + pullAndMatchEventsLocked(eventTimeNs); } // When active state changes from true to false, clear diff base but don't // reset other counters as we may accumulate more value in the bucket. @@ -404,65 +460,80 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, ConditionState newCondition = condition ? ConditionState::kTrue : ConditionState::kFalse; bool isEventTooLate = eventTimeNs < mCurrentBucketStartTimeNs; - if (mIsActive) { - if (isEventTooLate) { - VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, - (long long)mCurrentBucketStartTimeNs); - StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId); - invalidateCurrentBucket(); - } else { - if (mCondition == ConditionState::kUnknown) { - // If the condition was unknown, we mark the bucket as invalid since the bucket will - // contain partial data. For instance, the condition change might happen close to - // the end of the bucket and we might miss lots of data. - // - // We still want to pull to set the base. - invalidateCurrentBucket(); - } + // If the config is not active, skip the event. + if (!mIsActive) { + mCondition = isEventTooLate ? ConditionState::kUnknown : newCondition; + return; + } - // Pull on condition changes. - bool conditionChanged = - (mCondition == ConditionState::kTrue && newCondition == ConditionState::kFalse) - || (mCondition == ConditionState::kFalse && - newCondition == ConditionState::kTrue); - // We do not need to pull when we go from unknown to false. - // - // We also pull if the condition was already true in order to be able to flush the - // bucket at the end if needed. - // - // onConditionChangedLocked might happen on bucket boundaries if this is called before - // #onDataPulled. - if (mIsPulled && (conditionChanged || condition)) { - pullAndMatchEventsLocked(eventTimeNs, newCondition); - } + // If the event arrived late, mark the bucket as invalid and skip the event. + if (isEventTooLate) { + VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, + (long long)mCurrentBucketStartTimeNs); + StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); + StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId); + invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); + mCondition = ConditionState::kUnknown; + mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + return; + } - // When condition change from true to false, clear diff base but don't - // reset other counters as we may accumulate more value in the bucket. - if (mUseDiff && mCondition == ConditionState::kTrue - && newCondition == ConditionState::kFalse) { - resetBase(); - } - } + // If the previous condition was unknown, mark the bucket as invalid + // because the bucket will contain partial data. For example, the condition + // change might happen close to the end of the bucket and we might miss a + // lot of data. + // + // We still want to pull to set the base. + if (mCondition == ConditionState::kUnknown) { + invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); } - mCondition = isEventTooLate ? initialCondition(mConditionTrackerIndex) : newCondition; + // Pull and match for the following condition change cases: + // unknown/false -> true - condition changed + // true -> false - condition changed + // true -> true - old condition was true so we can flush the bucket at the + // end if needed. + // + // We don’t need to pull for unknown -> false or false -> false. + // + // onConditionChangedLocked might happen on bucket boundaries if this is + // called before #onDataPulled. + if (mIsPulled && + (newCondition == ConditionState::kTrue || mCondition == ConditionState::kTrue)) { + pullAndMatchEventsLocked(eventTimeNs); + } - if (mIsActive) { - flushIfNeededLocked(eventTimeNs); - mConditionTimer.onConditionChanged(mCondition, eventTimeNs); + // For metrics that use diff, when condition changes from true to false, + // clear diff base but don't reset other counts because we may accumulate + // more value in the bucket. + if (mUseDiff && + (mCondition == ConditionState::kTrue && newCondition == ConditionState::kFalse)) { + resetBase(); } + + // Update condition state after pulling. + mCondition = newCondition; + + flushIfNeededLocked(eventTimeNs); + mConditionTimer.onConditionChanged(mCondition, eventTimeNs); } -void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs, - ConditionState condition) { +void ValueMetricProducer::prepareFirstBucketLocked() { + // Kicks off the puller immediately if condition is true and diff based. + if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) { + pullAndMatchEventsLocked(mCurrentBucketStartTimeNs); + } +} + +void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { vector<std::shared_ptr<LogEvent>> allData; - if (!mPullerManager->Pull(mPullTagId, &allData)) { + if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) { ALOGE("Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); - invalidateCurrentBucket(); + invalidateCurrentBucket(timestampNs, BucketDropReason::PULL_FAILED); return; } - accumulateEvents(allData, timestampNs, timestampNs, condition); + accumulateEvents(allData, timestampNs, timestampNs); } int64_t ValueMetricProducer::calcPreviousBucketEndTime(const int64_t currentTimeNs) { @@ -475,33 +546,33 @@ int64_t ValueMetricProducer::calcPreviousBucketEndTime(const int64_t currentTime void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData, bool pullSuccess, int64_t originalPullTimeNs) { std::lock_guard<std::mutex> lock(mMutex); - if (mCondition == ConditionState::kTrue) { - // If the pull failed, we won't be able to compute a diff. - if (!pullSuccess) { - invalidateCurrentBucket(); + if (mCondition == ConditionState::kTrue) { + // If the pull failed, we won't be able to compute a diff. + if (!pullSuccess) { + invalidateCurrentBucket(originalPullTimeNs, BucketDropReason::PULL_FAILED); + } else { + bool isEventLate = originalPullTimeNs < getCurrentBucketEndTimeNs(); + if (isEventLate) { + // If the event is late, we are in the middle of a bucket. Just + // process the data without trying to snap the data to the nearest bucket. + accumulateEvents(allData, originalPullTimeNs, originalPullTimeNs); } else { - bool isEventLate = originalPullTimeNs < getCurrentBucketEndTimeNs(); - if (isEventLate) { - // If the event is late, we are in the middle of a bucket. Just - // process the data without trying to snap the data to the nearest bucket. - accumulateEvents(allData, originalPullTimeNs, originalPullTimeNs, mCondition); - } else { - // For scheduled pulled data, the effective event time is snap to the nearest - // bucket end. In the case of waking up from a deep sleep state, we will - // attribute to the previous bucket end. If the sleep was long but not very - // long, we will be in the immediate next bucket. Previous bucket may get a - // larger number as we pull at a later time than real bucket end. - // - // If the sleep was very long, we skip more than one bucket before sleep. In - // this case, if the diff base will be cleared and this new data will serve as - // new diff base. - int64_t bucketEndTime = calcPreviousBucketEndTime(originalPullTimeNs) - 1; - StatsdStats::getInstance().noteBucketBoundaryDelayNs( - mMetricId, originalPullTimeNs - bucketEndTime); - accumulateEvents(allData, originalPullTimeNs, bucketEndTime, mCondition); - } + // For scheduled pulled data, the effective event time is snap to the nearest + // bucket end. In the case of waking up from a deep sleep state, we will + // attribute to the previous bucket end. If the sleep was long but not very + // long, we will be in the immediate next bucket. Previous bucket may get a + // larger number as we pull at a later time than real bucket end. + // + // If the sleep was very long, we skip more than one bucket before sleep. In + // this case, if the diff base will be cleared and this new data will serve as + // new diff base. + int64_t bucketEndTime = calcPreviousBucketEndTime(originalPullTimeNs) - 1; + StatsdStats::getInstance().noteBucketBoundaryDelayNs( + mMetricId, originalPullTimeNs - bucketEndTime); + accumulateEvents(allData, originalPullTimeNs, bucketEndTime); } } + } // We can probably flush the bucket. Since we used bucketEndTime when calling // #onMatchedLogEventInternalLocked, the current bucket will not have been flushed. @@ -509,18 +580,18 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven } void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData, - int64_t originalPullTimeNs, int64_t eventElapsedTimeNs, - ConditionState condition) { + int64_t originalPullTimeNs, int64_t eventElapsedTimeNs) { bool isEventLate = eventElapsedTimeNs < mCurrentBucketStartTimeNs; if (isEventLate) { VLOG("Skip bucket end pull due to late arrival: %lld vs %lld", (long long)eventElapsedTimeNs, (long long)mCurrentBucketStartTimeNs); StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); - invalidateCurrentBucket(); + invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET); return; } - const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs; + const int64_t elapsedRealtimeNs = getElapsedRealtimeNs(); + const int64_t pullDelayNs = elapsedRealtimeNs - originalPullTimeNs; StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); if (pullDelayNs > mMaxPullDelayNs) { ALOGE("Pull finish too late for atom %d, longer than %lld", mPullTagId, @@ -528,15 +599,10 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); // We are missing one pull from the bucket which means we will not have a complete view of // what's going on. - invalidateCurrentBucket(); + invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::PULL_DELAYED); return; } - if (allData.size() == 0) { - VLOG("Data pulled is empty"); - StatsdStats::getInstance().noteEmptyData(mPullTagId); - } - mMatchedMetricDimensionKeys.clear(); for (const auto& data : allData) { LogEvent localCopy = data->makeCopy(); @@ -546,14 +612,19 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log onMatchedLogEventLocked(mWhatMatcherIndex, localCopy); } } - // If the new pulled data does not contains some keys we track in our intervals, we need to - // reset the base. + // If a key that is: + // 1. Tracked in mCurrentSlicedBucket and + // 2. A superset of the current mStateChangePrimaryKey + // was not found in the new pulled data (i.e. not in mMatchedDimensionInWhatKeys) + // then we need to reset the base. for (auto& slice : mCurrentSlicedBucket) { - bool presentInPulledData = mMatchedMetricDimensionKeys.find(slice.first) - != mMatchedMetricDimensionKeys.end(); - if (!presentInPulledData) { - for (auto& interval : slice.second) { - interval.hasBase = false; + const auto& whatKey = slice.first.getDimensionKeyInWhat(); + bool presentInPulledData = + mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end(); + if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) { + auto it = mCurrentBaseInfo.find(whatKey); + for (auto& baseInfo : it->second) { + baseInfo.hasBase = false; } } } @@ -567,7 +638,7 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log // incorrectly compute the diff when mUseZeroDefaultBase is true since an existing key // might be missing from mCurrentSlicedBucket. if (hasReachedGuardRailLimit()) { - invalidateCurrentBucket(); + invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::DIMENSION_GUARDRAIL_REACHED); mCurrentSlicedBucket.clear(); } } @@ -582,10 +653,10 @@ void ValueMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { if (verbose) { for (const auto& it : mCurrentSlicedBucket) { for (const auto& interval : it.second) { - fprintf(out, "\t(what)%s\t(condition)%s (value)%s\n", - it.first.getDimensionKeyInWhat().toString().c_str(), - it.first.getDimensionKeyInCondition().toString().c_str(), - interval.value.toString().c_str()); + fprintf(out, "\t(what)%s\t(states)%s (value)%s\n", + it.first.getDimensionKeyInWhat().toString().c_str(), + it.first.getStateValuesKey().toString().c_str(), + interval.value.toString().c_str()); } } } @@ -653,6 +724,7 @@ bool getDoubleOrLong(const LogEvent& event, const Matcher& matcher, Value& ret) ret.setDouble(value.mValue.double_value); break; default: + return false; break; } return true; @@ -661,17 +733,30 @@ bool getDoubleOrLong(const LogEvent& event, const Matcher& matcher, Value& ret) return false; } -void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIndex, - const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, - bool condition, const LogEvent& event) { +void ValueMetricProducer::onMatchedLogEventInternalLocked( + const size_t matcherIndex, const MetricDimensionKey& eventKey, + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const map<int, HashableDimensionKey>& statePrimaryKeys) { + auto whatKey = eventKey.getDimensionKeyInWhat(); + auto stateKey = eventKey.getStateValuesKey(); + + // Skip this event if a state changed occurred for a different primary key. + auto it = statePrimaryKeys.find(mStateChangePrimaryKey.first); + // Check that both the atom id and the primary key are equal. + if (it != statePrimaryKeys.end() && it->second != mStateChangePrimaryKey.second) { + VLOG("ValueMetric skip event with primary key %s because state change primary key " + "is %s", + it->second.toString().c_str(), mStateChangePrimaryKey.second.toString().c_str()); + return; + } + int64_t eventTimeNs = event.GetElapsedTimestampNs(); if (eventTimeNs < mCurrentBucketStartTimeNs) { VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, (long long)mCurrentBucketStartTimeNs); return; } - mMatchedMetricDimensionKeys.insert(eventKey); + mMatchedMetricDimensionKeys.insert(whatKey); if (!mIsPulled) { // We cannot flush without doing a pull first. @@ -689,17 +774,35 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn bool shouldSkipForPulledMetric = mIsPulled && !mUseDiff && mCondition != ConditionState::kTrue; if (shouldSkipForPushMetric || shouldSkipForPulledMetric) { - VLOG("ValueMetric skip event because condition is false"); + VLOG("ValueMetric skip event because condition is false and we are not using diff (for " + "pulled metric)"); return; } if (hitGuardRailLocked(eventKey)) { return; } - vector<Interval>& multiIntervals = mCurrentSlicedBucket[eventKey]; - if (multiIntervals.size() < mFieldMatchers.size()) { + + vector<BaseInfo>& baseInfos = mCurrentBaseInfo[whatKey]; + if (baseInfos.size() < mFieldMatchers.size()) { + VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); + baseInfos.resize(mFieldMatchers.size()); + } + + for (BaseInfo& baseInfo : baseInfos) { + if (!baseInfo.hasCurrentState) { + baseInfo.currentState = getUnknownStateKey(); + baseInfo.hasCurrentState = true; + } + } + + // We need to get the intervals stored with the previous state key so we can + // close these value intervals. + const auto oldStateKey = baseInfos[0].currentState; + vector<Interval>& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)]; + if (intervals.size() < mFieldMatchers.size()) { VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size()); - multiIntervals.resize(mFieldMatchers.size()); + intervals.resize(mFieldMatchers.size()); } // We only use anomaly detection under certain cases. @@ -712,9 +815,12 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn for (int i = 0; i < (int)mFieldMatchers.size(); i++) { const Matcher& matcher = mFieldMatchers[i]; - Interval& interval = multiIntervals[i]; + BaseInfo& baseInfo = baseInfos[i]; + Interval& interval = intervals[i]; interval.valueIndex = i; Value value; + baseInfo.hasCurrentState = true; + baseInfo.currentState = stateKey; if (!getDoubleOrLong(event, matcher, value)) { VLOG("Failed to get value %d from event %s", i, event.ToString().c_str()); StatsdStats::getInstance().noteBadValueType(mMetricId); @@ -723,60 +829,61 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn interval.seenNewData = true; if (mUseDiff) { - if (!interval.hasBase) { + if (!baseInfo.hasBase) { if (mHasGlobalBase && mUseZeroDefaultBase) { // The bucket has global base. This key does not. // Optionally use zero as base. - interval.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE); - interval.hasBase = true; + baseInfo.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE); + baseInfo.hasBase = true; } else { // no base. just update base and return. - interval.base = value; - interval.hasBase = true; + baseInfo.base = value; + baseInfo.hasBase = true; // If we're missing a base, do not use anomaly detection on incomplete data useAnomalyDetection = false; - // Continue (instead of return) here in order to set interval.base and - // interval.hasBase for other intervals + // Continue (instead of return) here in order to set baseInfo.base and + // baseInfo.hasBase for other baseInfos continue; } } + Value diff; switch (mValueDirection) { case ValueMetric::INCREASING: - if (value >= interval.base) { - diff = value - interval.base; + if (value >= baseInfo.base) { + diff = value - baseInfo.base; } else if (mUseAbsoluteValueOnReset) { diff = value; } else { VLOG("Unexpected decreasing value"); StatsdStats::getInstance().notePullDataError(mPullTagId); - interval.base = value; + baseInfo.base = value; // If we've got bad data, do not use anomaly detection useAnomalyDetection = false; continue; } break; case ValueMetric::DECREASING: - if (interval.base >= value) { - diff = interval.base - value; + if (baseInfo.base >= value) { + diff = baseInfo.base - value; } else if (mUseAbsoluteValueOnReset) { diff = value; } else { VLOG("Unexpected increasing value"); StatsdStats::getInstance().notePullDataError(mPullTagId); - interval.base = value; + baseInfo.base = value; // If we've got bad data, do not use anomaly detection useAnomalyDetection = false; continue; } break; case ValueMetric::ANY: - diff = value - interval.base; + diff = value - baseInfo.base; break; default: break; } - interval.base = value; + baseInfo.base = value; value = diff; } @@ -806,7 +913,7 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn // Only trigger the tracker if all intervals are correct if (useAnomalyDetection) { // TODO: propgate proper values down stream when anomaly support doubles - long wholeBucketVal = multiIntervals[0].value.long_value; + long wholeBucketVal = intervals[0].value.long_value; auto prev = mCurrentFullBucket.find(eventKey); if (prev != mCurrentFullBucket.end()) { wholeBucketVal += prev->second; @@ -823,7 +930,7 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn void ValueMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs(); if (eventTimeNs < currentBucketEndTimeNs) { - VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs, + VLOG("eventTime is %lld, less than current bucket end time %lld", (long long)eventTimeNs, (long long)(currentBucketEndTimeNs)); return; } @@ -844,25 +951,39 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, const int64_t& nextBucketStartTimeNs) { if (mCondition == ConditionState::kUnknown) { StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId); + invalidateCurrentBucketWithoutResetBase(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); } + VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, + (int)mCurrentSlicedBucket.size()); + + int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); + int64_t bucketEndTime = fullBucketEndTimeNs; int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); - if (numBucketsForward > 1) { + + // Skip buckets if this is a pulled metric or a pushed metric that is diffed. + if (numBucketsForward > 1 && (mIsPulled || mUseDiff)) { + VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId); // Something went wrong. Maybe the device was sleeping for a long time. It is better // to mark the current bucket as invalid. The last pull might have been successful through. - invalidateCurrentBucketWithoutResetBase(); + invalidateCurrentBucketWithoutResetBase(eventTimeNs, + BucketDropReason::MULTIPLE_BUCKETS_SKIPPED); + // End the bucket at the next bucket start time so the entire interval is skipped. + bucketEndTime = nextBucketStartTimeNs; + } else if (eventTimeNs < fullBucketEndTimeNs) { + bucketEndTime = eventTimeNs; } - VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, - (int)mCurrentSlicedBucket.size()); - int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); - int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs; // Close the current bucket. int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime); bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; - if (isBucketLargeEnough && !mCurrentBucketIsInvalid) { + if (!isBucketLargeEnough) { + skipCurrentBucket(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL); + } + if (!mCurrentBucketIsSkipped) { + bool bucketHasData = false; // The current bucket is large enough to keep. for (const auto& slice : mCurrentSlicedBucket) { ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second); @@ -871,13 +992,34 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, if (bucket.valueIndex.size() > 0) { auto& bucketList = mPastBuckets[slice.first]; bucketList.push_back(bucket); + bucketHasData = true; } } - } else { - mSkippedBuckets.emplace_back(mCurrentBucketStartTimeNs, bucketEndTime); + if (!bucketHasData) { + skipCurrentBucket(eventTimeNs, BucketDropReason::NO_DATA); + } + } + + if (mCurrentBucketIsSkipped) { + mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs; + mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime; + mSkippedBuckets.emplace_back(mCurrentSkippedBucket); } - appendToFullBucket(eventTimeNs, fullBucketEndTimeNs); + // This means that the current bucket was not flushed before a forced bucket split. + // This can happen if an app update or a dump report with include_current_partial_bucket is + // requested before we get a chance to flush the bucket due to receiving new data, either from + // the statsd socket or the StatsPullerManager. + if (bucketEndTime < nextBucketStartTimeNs) { + SkippedBucket bucketInGap; + bucketInGap.bucketStartTimeNs = bucketEndTime; + bucketInGap.bucketEndTimeNs = nextBucketStartTimeNs; + bucketInGap.dropEvents.emplace_back( + buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA)); + mSkippedBuckets.emplace_back(bucketInGap); + } + + appendToFullBucket(eventTimeNs > fullBucketEndTimeNs); initCurrentSlicedBucket(nextBucketStartTimeNs); // Update the condition timer again, in case we skipped buckets. mConditionTimer.newBucketStart(nextBucketStartTimeNs); @@ -927,22 +1069,24 @@ void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) } else { it++; } + // TODO(b/157655103): remove mCurrentBaseInfo entries when obsolete } - mCurrentBucketIsInvalid = false; + mCurrentBucketIsSkipped = false; + mCurrentSkippedBucket.reset(); + // If we do not have a global base when the condition is true, // we will have incomplete bucket for the next bucket. if (mUseDiff && !mHasGlobalBase && mCondition) { - mCurrentBucketIsInvalid = false; + mCurrentBucketIsSkipped = false; } mCurrentBucketStartTimeNs = nextBucketStartTimeNs; VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); } -void ValueMetricProducer::appendToFullBucket(int64_t eventTimeNs, int64_t fullBucketEndTimeNs) { - bool isFullBucketReached = eventTimeNs > fullBucketEndTimeNs; - if (mCurrentBucketIsInvalid) { +void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) { + if (mCurrentBucketIsSkipped) { if (isFullBucketReached) { // If the bucket is invalid, we ignore the full bucket since it contains invalid data. mCurrentFullBucket.clear(); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 784ac64880a3..b359af745c91 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -50,12 +50,18 @@ struct ValueBucket { // - an alarm set to the end of the bucket class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver { public: - ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric, - const int conditionIndex, const sp<ConditionWizard>& conditionWizard, - const int whatMatcherIndex, - const sp<EventMatcherWizard>& matcherWizard, - const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs, - const sp<StatsPullerManager>& pullerManager); + ValueMetricProducer( + const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex, + const vector<ConditionState>& initialConditionCache, + const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex, + const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, + const int64_t timeBaseNs, const int64_t startTimeNs, + const sp<StatsPullerManager>& pullerManager, + const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {}, + const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& + eventDeactivationMap = {}, + const vector<int>& slicedStateAtoms = {}, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {}); virtual ~ValueMetricProducer(); @@ -64,23 +70,34 @@ public: bool pullSuccess, int64_t originalPullTimeNs) override; // ValueMetric needs special logic if it's a pulled atom. - void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) override { + void notifyAppUpgrade(const int64_t& eventTimeNs) override { std::lock_guard<std::mutex> lock(mMutex); if (!mSplitBucketForAppUpgrade) { return; } - if (mIsPulled && mCondition) { - pullAndMatchEventsLocked(eventTimeNs, mCondition); + if (mIsPulled && mCondition == ConditionState::kTrue) { + pullAndMatchEventsLocked(eventTimeNs); } flushCurrentBucketLocked(eventTimeNs, eventTimeNs); }; + // ValueMetric needs special logic if it's a pulled atom. + void onStatsdInitCompleted(const int64_t& eventTimeNs) override { + std::lock_guard<std::mutex> lock(mMutex); + if (mIsPulled && mCondition == ConditionState::kTrue) { + pullAndMatchEventsLocked(eventTimeNs); + } + flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + + void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) override; + protected: void onMatchedLogEventInternalLocked( const size_t matcherIndex, const MetricDimensionKey& eventKey, - const ConditionKey& conditionKey, bool condition, - const LogEvent& event) override; + const ConditionKey& conditionKey, bool condition, const LogEvent& event, + const std::map<int, HashableDimensionKey>& statePrimaryKeys) override; private: void onDumpReportLocked(const int64_t dumpTimeNs, @@ -125,8 +142,15 @@ private: int64_t calcBucketsForwardCount(const int64_t& eventTimeNs) const; // Mark the data as invalid. - void invalidateCurrentBucket(); - void invalidateCurrentBucketWithoutResetBase(); + void invalidateCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason); + + void invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs, + const BucketDropReason reason); + + // Skips the current bucket without notifying StatsdStats of the skipped bucket. + // This should only be called from #flushCurrentBucketLocked. Otherwise, a future event that + // causes the bucket to be invalidated will not notify StatsdStats. + void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason); const int mWhatMatcherIndex; @@ -138,7 +162,10 @@ private: std::vector<Matcher> mFieldMatchers; // Value fields for matching. - std::set<MetricDimensionKey> mMatchedMetricDimensionKeys; + std::set<HashableDimensionKey> mMatchedMetricDimensionKeys; + + // Holds the atom id, primary key pair from a state change. + pair<int32_t, HashableDimensionKey> mStateChangePrimaryKey; // tagId for pulled data. -1 if this is not pulled const int mPullTagId; @@ -150,10 +177,6 @@ private: typedef struct { // Index in multi value aggregation. int valueIndex; - // Holds current base value of the dimension. Take diff and update if necessary. - Value base; - // Whether there is a base to diff to. - bool hasBase; // Current value, depending on the aggregation type. Value value; // Number of samples collected. @@ -165,34 +188,46 @@ private: bool seenNewData = false; } Interval; + typedef struct { + // Holds current base value of the dimension. Take diff and update if necessary. + Value base; + // Whether there is a base to diff to. + bool hasBase; + // Last seen state value(s). + HashableDimensionKey currentState; + // Whether this dimensions in what key has a current state key. + bool hasCurrentState; + } BaseInfo; + std::unordered_map<MetricDimensionKey, std::vector<Interval>> mCurrentSlicedBucket; + std::unordered_map<HashableDimensionKey, std::vector<BaseInfo>> mCurrentBaseInfo; + std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket; // Save the past buckets and we can clear when the StatsLogReport is dumped. std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets; - // Pairs of (elapsed start, elapsed end) denoting buckets that were skipped. - std::list<std::pair<int64_t, int64_t>> mSkippedBuckets; - const int64_t mMinBucketSizeNs; // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const MetricDimensionKey& newKey); + bool hasReachedGuardRailLimit() const; bool hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey); - void pullAndMatchEventsLocked(const int64_t timestampNs, ConditionState condition); + void pullAndMatchEventsLocked(const int64_t timestampNs); void accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData, - int64_t originalPullTimeNs, int64_t eventElapsedTimeNs, - ConditionState condition); + int64_t originalPullTimeNs, int64_t eventElapsedTimeNs); ValueBucket buildPartialBucket(int64_t bucketEndTime, const std::vector<Interval>& intervals); + void initCurrentSlicedBucket(int64_t nextBucketStartTimeNs); - void appendToFullBucket(int64_t eventTimeNs, int64_t fullBucketEndTimeNs); + + void appendToFullBucket(const bool isFullBucketReached); // Reset diff base and mHasGlobalBase void resetBase(); @@ -225,11 +260,9 @@ private: // diff against. bool mHasGlobalBase; - // Invalid bucket. There was a problem in collecting data in the current bucket so we cannot - // trust any of the data in this bucket. - // - // For instance, one pull failed. - bool mCurrentBucketIsInvalid; + // This is to track whether or not the bucket is skipped for any of the reasons listed in + // BucketDropReason, many of which make the bucket potentially invalid. + bool mCurrentBucketIsSkipped; const int64_t mMaxPullDelayNs; @@ -239,12 +272,10 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection); FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange); - FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2); - FRIEND_TEST(ValueMetricProducerTest, TestBucketIncludingUnknownConditionIsInvalid); FRIEND_TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet); FRIEND_TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime); FRIEND_TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged); @@ -253,14 +284,8 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled); FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition); FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket); - FRIEND_TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid); - FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit); - FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed); - FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed); - FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed); FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff); FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff); - FRIEND_TEST(ValueMetricProducerTest, TestPartialBucketCreated); FRIEND_TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries); FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse); FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue); @@ -271,15 +296,12 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering); - FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade); - FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse); FRIEND_TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMax); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMin); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum); FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithCondition); - FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade); FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition); FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded); FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange); @@ -288,9 +310,28 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate); FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput); FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedState); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMap); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithCondition); FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); + + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit); + FRIEND_TEST(ValueMetricProducerTest_BucketDrop, + TestInvalidBucketWhenAccumulateEventWrongBucket); + + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPushedEvents); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse); + friend class ValueMetricProducerTestHelper; }; diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 081e61ed21fa..8d59d1362919 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -56,11 +56,19 @@ struct DurationBucket { int64_t mDuration; }; +struct DurationValues { + // Recorded duration for current partial bucket. + int64_t mDuration; + + // Sum of past partial bucket durations in current full bucket. + // Used for anomaly detection. + int64_t mDurationFullBucket; +}; + class DurationTracker { public: DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - const std::vector<Matcher>& dimensionInCondition, bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers) @@ -70,11 +78,9 @@ public: mWizard(wizard), mConditionTrackerIndex(conditionIndex), mBucketSizeNs(bucketSizeNs), - mDimensionInCondition(dimensionInCondition), mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), - mDurationFullBucket(0), mCurrentBucketNum(currentBucketNum), mStartTimeNs(startTimeNs), mConditionSliced(conditionSliced), @@ -83,10 +89,8 @@ public: virtual ~DurationTracker(){}; - virtual unique_ptr<DurationTracker> clone(const int64_t eventTime) = 0; - - virtual void noteStart(const HashableDimensionKey& key, bool condition, - const int64_t eventTime, const ConditionKey& conditionKey) = 0; + virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, + const ConditionKey& conditionKey) = 0; virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime, const bool stopAll) = 0; virtual void noteStopAll(const int64_t eventTime) = 0; @@ -94,6 +98,9 @@ public: virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0; virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0; + virtual void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) = 0; + // Flush stale buckets if needed, and return true if the tracker has no on-going duration // events, so that the owner can safely remove the tracker. virtual bool flushIfNeeded( @@ -112,9 +119,12 @@ public: // Dump internal states for debugging virtual void dumpStates(FILE* out, bool verbose) const = 0; - void setEventKey(const MetricDimensionKey& eventKey) { - mEventKey = eventKey; - } + virtual int64_t getCurrentStateKeyDuration() const = 0; + + virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0; + + // Replace old value with new value for the given state atom. + virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0; protected: int64_t getCurrentBucketEndTimeNs() const { @@ -143,10 +153,11 @@ protected: } } - void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) { + void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey, + const int64_t& bucketValue, const int64_t& bucketNum) { for (auto& anomalyTracker : mAnomalyTrackers) { if (anomalyTracker != nullptr) { - anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum); + anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum); } } } @@ -167,6 +178,10 @@ protected: return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs; } + void setEventKey(const MetricDimensionKey& eventKey) { + mEventKey = eventKey; + } + // A reference to the DurationMetricProducer's config key. const ConfigKey& mConfigKey; @@ -180,15 +195,14 @@ protected: const int64_t mBucketSizeNs; - const std::vector<Matcher>& mDimensionInCondition; - const bool mNested; int64_t mCurrentBucketStartTimeNs; int64_t mDuration; // current recorded duration result (for partial bucket) - int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket. + // Recorded duration results for each state key in the current partial bucket. + std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap; int64_t mCurrentBucketNum; @@ -196,7 +210,6 @@ protected: const bool mConditionSliced; - bool mSameConditionDimensionsInTracker; bool mHasLinksToAllConditionDimensionsInTracker; std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers; diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 6868b8c24c71..ee4e1672411f 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -26,37 +26,14 @@ namespace statsd { MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, - const vector<Matcher>& dimensionInCondition, bool nesting, + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers) { - if (mWizard != nullptr) { - mSameConditionDimensionsInTracker = - mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition); - } -} - -unique_ptr<DurationTracker> MaxDurationTracker::clone(const int64_t eventTime) { - auto clonedTracker = make_unique<MaxDurationTracker>(*this); - for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end();) { - if (it->second.state != kStopped) { - it->second.lastStartTime = eventTime; - it->second.lastDuration = 0; - it++; - } else { - it = clonedTracker->mInfos.erase(it); - } - } - if (clonedTracker->mInfos.empty()) { - return nullptr; - } else { - return clonedTracker; - } + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers) { } bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -113,7 +90,6 @@ void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool conditi } } - void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime, bool forceStop) { VLOG("MaxDuration: key %s stop", key.toString().c_str()); @@ -252,22 +228,21 @@ void MaxDurationTracker::onSlicedConditionMayChange(bool overallCondition, if (pair.second.state == kStopped) { continue; } - std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; ConditionState conditionState = mWizard->query( - mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionKeySet); - bool conditionMet = - (conditionState == ConditionState::kTrue) && - (mDimensionInCondition.size() == 0 || - conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) != - conditionDimensionKeySet.end()); + mConditionTrackerIndex, pair.second.conditionKeys, + !mHasLinksToAllConditionDimensionsInTracker); + bool conditionMet = (conditionState == ConditionState::kTrue); + VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet); noteConditionChanged(pair.first, conditionMet, timestamp); } } +void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) { for (auto& pair : mInfos) { noteConditionChanged(pair.first, condition, timestamp); @@ -337,6 +312,20 @@ void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); } +int64_t MaxDurationTracker::getCurrentStateKeyDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const { + ALOGE("MaxDurationTracker does not handle sliced state changes."); + return -1; +} + +void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + ALOGE("MaxDurationTracker does not handle sliced state changes."); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 8e8f2cd6c582..2891c6e1138a 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -30,7 +30,7 @@ class MaxDurationTracker : public DurationTracker { public: MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, int conditionIndex, - const std::vector<Matcher>& dimensionInCondition, bool nesting, + bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, @@ -38,8 +38,6 @@ public: MaxDurationTracker(const MaxDurationTracker& tracker) = default; - unique_ptr<DurationTracker> clone(const int64_t eventTime) override; - void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const int64_t eventTime, @@ -56,10 +54,19 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // Returns true if at least one of the mInfos is started. bool anyStarted(); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 956383a99eea..0d49bbc269a3 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -26,27 +26,15 @@ using std::pair; OringDurationTracker::OringDurationTracker( const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, - sp<ConditionWizard> wizard, int conditionIndex, const vector<Matcher>& dimensionInCondition, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink, - const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) - : DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting, - currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs, - conditionSliced, fullLink, anomalyTrackers), + sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, + bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers) + : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, + currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink, + anomalyTrackers), mStarted(), mPaused() { mLastStartTime = 0; - if (mWizard != nullptr) { - mSameConditionDimensionsInTracker = - mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition); - } -} - -unique_ptr<DurationTracker> OringDurationTracker::clone(const int64_t eventTime) { - auto clonedTracker = make_unique<OringDurationTracker>(*this); - clonedTracker->mLastStartTime = eventTime; - clonedTracker->mDuration = 0; - return clonedTracker; } bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -101,10 +89,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 mConditionKeyMap.erase(key); } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); - VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); } } @@ -123,10 +115,14 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 void OringDurationTracker::noteStopAll(const int64_t timestamp) { if (!mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime, - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s", + (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } stopAnomalyAlarm(timestamp); @@ -157,21 +153,36 @@ bool OringDurationTracker::flushCurrentBucket( // Process the current bucket. if (mStarted.size() > 0) { - mDuration += (currentBucketEndTimeNs - mLastStartTime); - } - if (mDuration > 0) { - DurationBucket current_info; - current_info.mBucketStartNs = mCurrentBucketStartTimeNs; - current_info.mBucketEndNs = currentBucketEndTimeNs; - current_info.mDuration = mDuration; - (*output)[mEventKey].push_back(current_info); - mDurationFullBucket += mDuration; - VLOG(" duration: %lld", (long long)current_info.mDuration); + // Calculate the duration for the current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (currentBucketEndTimeNs - mLastStartTime); } - if (eventTimeNs > fullBucketEnd) { - // End of full bucket, can send to anomaly tracker now. - addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum); - mDurationFullBucket = 0; + // Store DurationBucket info for each whatKey, stateKey pair. + // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the + // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to + // store durations for each stateKey, so we need to flush the bucket by creating a + // DurationBucket for each stateKey. + for (auto& durationIt : mStateKeyDurationMap) { + if (durationIt.second.mDuration > 0) { + DurationBucket current_info; + current_info.mBucketStartNs = mCurrentBucketStartTimeNs; + current_info.mBucketEndNs = currentBucketEndTimeNs; + current_info.mDuration = durationIt.second.mDuration; + (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)] + .push_back(current_info); + + durationIt.second.mDurationFullBucket += durationIt.second.mDuration; + VLOG(" duration: %lld", (long long)current_info.mDuration); + } + + if (eventTimeNs > fullBucketEnd) { + // End of full bucket, can send to anomaly tracker now. + addPastBucketToAnomalyTrackers( + MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first), + getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum); + durationIt.second.mDurationFullBucket = 0; + } + durationIt.second.mDuration = 0; } if (mStarted.size() > 0) { @@ -180,20 +191,19 @@ bool OringDurationTracker::flushCurrentBucket( info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1); info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs; info.mDuration = mBucketSizeNs; + // Full duration buckets are attributed to the current stateKey. (*output)[mEventKey].push_back(info); // Safe to send these buckets to anomaly tracker since they must be full buckets. // If it's a partial bucket, numBucketsForward would be 0. - addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i); + addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i); VLOG(" add filling bucket with duration %lld", (long long)info.mDuration); } } else { if (numBucketsForward >= 2) { - addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1); + addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1); } } - mDuration = 0; - if (numBucketsForward > 0) { mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs; mCurrentBucketNum += numBucketsForward; @@ -227,17 +237,10 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, ++it; continue; } - std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; ConditionState conditionState = mWizard->query(mConditionTrackerIndex, condIt->second, - mDimensionInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionKeySet); - if (conditionState != ConditionState::kTrue || - (mDimensionInCondition.size() != 0 && - conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) == - conditionDimensionKeySet.end())) { + !mHasLinksToAllConditionDimensionsInTracker); + if (conditionState != ConditionState::kTrue) { startedToPaused.push_back(*it); it = mStarted.erase(it); VLOG("Key %s started -> paused", key.toString().c_str()); @@ -247,10 +250,14 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, } if (mStarted.empty()) { - mDuration += (timestamp - mLastStartTime); - VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime), - (long long)mDuration); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); + VLOG("record duration %lld, total duration %lld for state key %s", + (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(), + mEventKey.getStateValuesKey().toString().c_str()); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } @@ -262,17 +269,10 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, ++it; continue; } - std::unordered_set<HashableDimensionKey> conditionDimensionKeySet; ConditionState conditionState = mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key], - mDimensionInCondition, - !mSameConditionDimensionsInTracker, - !mHasLinksToAllConditionDimensionsInTracker, - &conditionDimensionKeySet); - if (conditionState == ConditionState::kTrue && - (mDimensionInCondition.size() == 0 || - conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) != - conditionDimensionKeySet.end())) { + !mHasLinksToAllConditionDimensionsInTracker); + if (conditionState == ConditionState::kTrue) { pausedToStarted.push_back(*it); it = mPaused.erase(it); VLOG("Key %s paused -> started", key.toString().c_str()); @@ -313,10 +313,13 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } else { if (!mStarted.empty()) { VLOG("Condition false, all paused"); - mDuration += (timestamp - mLastStartTime); + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += + (timestamp - mLastStartTime); mPaused.insert(mStarted.begin(), mStarted.end()); mStarted.clear(); - detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket); + detectAndDeclareAnomaly( + timestamp, mCurrentBucketNum, + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration()); } } if (mStarted.empty()) { @@ -324,6 +327,23 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } } +void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) { + // Nothing needs to be done on a state change if we have not seen a start + // event, the metric is currently not active, or condition is false. + // For these cases, no keys are being tracked in mStarted, so update + // the current state key and return. + if (mStarted.empty()) { + updateCurrentStateKey(atomId, newState); + return; + } + // Add the current duration length to the previous state key and then update + // the last start time and current state key. + mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime); + mLastStartTime = timestamp; + updateCurrentStateKey(atomId, newState); +} + int64_t OringDurationTracker::predictAnomalyTimestampNs( const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const { @@ -333,12 +353,13 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( // The timestamp of the current bucket end. const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs(); - // The past duration ns for the current bucket. - int64_t currentBucketPastNs = mDuration + mDurationFullBucket; + // The past duration ns for the current bucket of the current stateKey. + int64_t currentStateBucketPastNs = + getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration(); // As we move into the future, old buckets get overwritten (so their old data is erased). // Sum of past durations. Will change as we overwrite old buckets. - int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); + int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey); // The refractory period end timestamp for dimension mEventKey. const int64_t refractoryPeriodEndNs = @@ -397,7 +418,7 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { - pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs)); + pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs)); } } @@ -407,7 +428,34 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); - fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); + fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration()); +} + +int64_t OringDurationTracker::getCurrentStateKeyDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDuration; + } +} + +int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const { + auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey()); + if (it == mStateKeyDurationMap.end()) { + return 0; + } else { + return it->second.mDurationFullBucket; + } +} + +void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) { + HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey(); + for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) { + if (stateValuesKey->getValues()[i].mField.getTag() == atomId) { + stateValuesKey->mutableValue(i)->mValue = newState.mValue; + } + } } } // namespace statsd diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index e46616969116..bd8017a7decd 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -28,16 +28,13 @@ class OringDurationTracker : public DurationTracker { public: OringDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard, - int conditionIndex, const std::vector<Matcher>& dimensionInCondition, - bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum, - int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, - bool fullLink, + int conditionIndex, bool nesting, int64_t currentBucketStartNs, + int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, + bool conditionSliced, bool fullLink, const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers); OringDurationTracker(const OringDurationTracker& tracker) = default; - unique_ptr<DurationTracker> clone(const int64_t eventTime) override; - void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const int64_t eventTime, @@ -47,6 +44,9 @@ public: void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override; void onConditionChanged(bool condition, const int64_t timestamp) override; + void onStateChanged(const int64_t timestamp, const int32_t atomId, + const FieldValue& newState) override; + bool flushCurrentBucket( const int64_t& eventTimeNs, std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override; @@ -58,6 +58,12 @@ public: const int64_t currentTimestamp) const override; void dumpStates(FILE* out, bool verbose) const override; + int64_t getCurrentStateKeyDuration() const override; + + int64_t getCurrentStateKeyFullBucketDuration() const override; + + void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState); + private: // We don't need to keep track of individual durations. The information that's needed is: // 1) which keys are started. We record the first start time. diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 45c3c69b04b2..8917c36bb608 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -19,24 +19,24 @@ #include "metrics_manager_util.h" -#include "../condition/CombinationConditionTracker.h" -#include "../condition/SimpleConditionTracker.h" -#include "../condition/StateTracker.h" -#include "../external/StatsPullerManager.h" -#include "../matchers/CombinationLogMatchingTracker.h" -#include "../matchers/SimpleLogMatchingTracker.h" -#include "../matchers/EventMatcherWizard.h" -#include "../metrics/CountMetricProducer.h" -#include "../metrics/DurationMetricProducer.h" -#include "../metrics/EventMetricProducer.h" -#include "../metrics/GaugeMetricProducer.h" -#include "../metrics/ValueMetricProducer.h" - -#include "atoms_info.h" -#include "stats_util.h" - #include <inttypes.h> +#include "FieldValue.h" +#include "MetricProducer.h" +#include "condition/CombinationConditionTracker.h" +#include "condition/SimpleConditionTracker.h" +#include "external/StatsPullerManager.h" +#include "matchers/CombinationLogMatchingTracker.h" +#include "matchers/EventMatcherWizard.h" +#include "matchers/SimpleLogMatchingTracker.h" +#include "metrics/CountMetricProducer.h" +#include "metrics/DurationMetricProducer.h" +#include "metrics/EventMetricProducer.h" +#include "metrics/GaugeMetricProducer.h" +#include "metrics/ValueMetricProducer.h" +#include "state/StateManager.h" +#include "stats_util.h" + using std::set; using std::unordered_map; using std::vector; @@ -136,6 +136,105 @@ bool handleMetricWithConditions( return true; } +// Initializes state data structures for a metric. +// input: +// [config]: the input config +// [stateIds]: the slice_by_state ids for this metric +// [stateAtomIdMap]: this map contains the mapping from all state ids to atom ids +// [allStateGroupMaps]: this map contains the mapping from state ids and state +// values to state group ids for all states +// output: +// [slicedStateAtoms]: a vector of atom ids of all the slice_by_states +// [stateGroupMap]: this map should contain the mapping from states ids and state +// values to state group ids for all states that this metric +// is interested in +bool handleMetricWithStates( + const StatsdConfig& config, const ::google::protobuf::RepeatedField<int64_t>& stateIds, + const unordered_map<int64_t, int>& stateAtomIdMap, + const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps, + vector<int>& slicedStateAtoms, + unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) { + for (const auto& stateId : stateIds) { + auto it = stateAtomIdMap.find(stateId); + if (it == stateAtomIdMap.end()) { + ALOGW("cannot find State %" PRId64 " in the config", stateId); + return false; + } + int atomId = it->second; + slicedStateAtoms.push_back(atomId); + + auto stateIt = allStateGroupMaps.find(stateId); + if (stateIt != allStateGroupMaps.end()) { + stateGroupMap[atomId] = stateIt->second; + } + } + return true; +} + +bool handleMetricWithStateLink(const FieldMatcher& stateMatcher, + const vector<Matcher>& dimensionsInWhat) { + vector<Matcher> stateMatchers; + translateFieldMatcher(stateMatcher, &stateMatchers); + + return subsetDimensions(stateMatchers, dimensionsInWhat); +} + +// Validates a metricActivation and populates state. +// EventActivationMap and EventDeactivationMap are supplied to a MetricProducer +// to provide the producer with state about its activators and deactivators. +// Returns false if there are errors. +bool handleMetricActivation( + const StatsdConfig& config, + const int64_t metricId, + const int metricIndex, + const unordered_map<int64_t, int>& metricToActivationMap, + const unordered_map<int64_t, int>& logTrackerMap, + unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap, + unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap, + vector<int>& metricsWithActivation, + unordered_map<int, shared_ptr<Activation>>& eventActivationMap, + unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap) { + // Check if metric has an associated activation + auto itr = metricToActivationMap.find(metricId); + if (itr == metricToActivationMap.end()) return true; + + int activationIndex = itr->second; + const MetricActivation& metricActivation = config.metric_activation(activationIndex); + + for (int i = 0; i < metricActivation.event_activation_size(); i++) { + const EventActivation& activation = metricActivation.event_activation(i); + + auto itr = logTrackerMap.find(activation.atom_matcher_id()); + if (itr == logTrackerMap.end()) { + ALOGE("Atom matcher not found for event activation."); + return false; + } + + ActivationType activationType = (activation.has_activation_type()) ? + activation.activation_type() : metricActivation.activation_type(); + std::shared_ptr<Activation> activationWrapper = std::make_shared<Activation>( + activationType, activation.ttl_seconds() * NS_PER_SEC); + + int atomMatcherIndex = itr->second; + activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(metricIndex); + eventActivationMap.emplace(atomMatcherIndex, activationWrapper); + + if (activation.has_deactivation_atom_matcher_id()) { + itr = logTrackerMap.find(activation.deactivation_atom_matcher_id()); + if (itr == logTrackerMap.end()) { + ALOGE("Atom matcher not found for event deactivation."); + return false; + } + int deactivationAtomMatcherIndex = itr->second; + deactivationAtomTrackerToMetricMap[deactivationAtomMatcherIndex].push_back(metricIndex); + eventDeactivationMap[deactivationAtomMatcherIndex].push_back(activationWrapper); + } + } + + metricsWithActivation.push_back(metricIndex); + return true; +} + bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap, unordered_map<int64_t, int>& logTrackerMap, vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) { @@ -182,74 +281,26 @@ bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap, return true; } -/** - * A StateTracker is built from a SimplePredicate which has only "start", and no "stop" - * or "stop_all". The start must be an atom matcher that matches a state atom. It must - * have dimension, the dimension must be the state atom's primary fields plus exclusive state - * field. For example, the StateTracker is used in tracking UidProcessState and ScreenState. - * - */ -bool isStateTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) { - // 1. must not have "stop". must have "dimension" - if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) { - auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find( - simplePredicate.dimensions().field()); - // 2. must be based on a state atom. - if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) { - // 3. dimension must be primary fields + state field IN ORDER - size_t expectedDimensionCount = it->second.primaryFields.size() + 1; - vector<Matcher> dimensions; - translateFieldMatcher(simplePredicate.dimensions(), &dimensions); - if (dimensions.size() != expectedDimensionCount) { - return false; - } - // 3.1 check the primary fields first. - size_t index = 0; - for (const auto& field : it->second.primaryFields) { - Matcher matcher = getSimpleMatcher(it->first, field); - if (!(matcher == dimensions[index])) { - return false; - } - primaryKeys->push_back(matcher); - index++; - } - Matcher stateFieldMatcher = - getSimpleMatcher(it->first, it->second.exclusiveField); - // 3.2 last dimension should be the exclusive field. - if (!(dimensions.back() == stateFieldMatcher)) { - return false; - } - return true; - } - } - return false; -} // namespace statsd - bool initConditions(const ConfigKey& key, const StatsdConfig& config, const unordered_map<int64_t, int>& logTrackerMap, unordered_map<int64_t, int>& conditionTrackerMap, vector<sp<ConditionTracker>>& allConditionTrackers, - unordered_map<int, std::vector<int>>& trackerToConditionMap) { + unordered_map<int, std::vector<int>>& trackerToConditionMap, + vector<ConditionState>& initialConditionCache) { vector<Predicate> conditionConfigs; const int conditionTrackerCount = config.predicate_size(); conditionConfigs.reserve(conditionTrackerCount); allConditionTrackers.reserve(conditionTrackerCount); + initialConditionCache.reserve(conditionTrackerCount); + std::fill(initialConditionCache.begin(), initialConditionCache.end(), ConditionState::kUnknown); for (int i = 0; i < conditionTrackerCount; i++) { const Predicate& condition = config.predicate(i); int index = allConditionTrackers.size(); switch (condition.contents_case()) { case Predicate::ContentsCase::kSimplePredicate: { - vector<Matcher> primaryKeys; - if (isStateTracker(condition.simple_predicate(), &primaryKeys)) { - allConditionTrackers.push_back(new StateTracker(key, condition.id(), index, - condition.simple_predicate(), - logTrackerMap, primaryKeys)); - } else { - allConditionTrackers.push_back(new SimpleConditionTracker( - key, condition.id(), index, condition.simple_predicate(), - logTrackerMap)); - } + allConditionTrackers.push_back(new SimpleConditionTracker( + key, condition.id(), index, condition.simple_predicate(), logTrackerMap)); break; } case Predicate::ContentsCase::kCombination: { @@ -273,7 +324,7 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, for (size_t i = 0; i < allConditionTrackers.size(); i++) { auto& conditionTracker = allConditionTrackers[i]; if (!conditionTracker->init(conditionConfigs, allConditionTrackers, conditionTrackerMap, - stackTracker)) { + stackTracker, initialConditionCache)) { return false; } for (const int trackerIndex : conditionTracker->getLogTrackerIndex()) { @@ -284,23 +335,59 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, return true; } +bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap, + unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps) { + for (int i = 0; i < config.state_size(); i++) { + const State& state = config.state(i); + const int64_t stateId = state.id(); + stateAtomIdMap[stateId] = state.atom_id(); + + const StateMap& stateMap = state.map(); + for (auto group : stateMap.group()) { + for (auto value : group.value()) { + allStateGroupMaps[stateId][value] = group.group_id(); + } + } + } + + return true; +} + bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs, - const int64_t currentTimeNs, - const sp<StatsPullerManager>& pullerManager, + const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager, const unordered_map<int64_t, int>& logTrackerMap, const unordered_map<int64_t, int>& conditionTrackerMap, const vector<sp<LogMatchingTracker>>& allAtomMatchers, + const unordered_map<int64_t, int>& stateAtomIdMap, + const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps, vector<sp<ConditionTracker>>& allConditionTrackers, + const vector<ConditionState>& initialConditionCache, vector<sp<MetricProducer>>& allMetricProducers, - unordered_map<int, std::vector<int>>& conditionToMetricMap, - unordered_map<int, std::vector<int>>& trackerToMetricMap, - unordered_map<int64_t, int>& metricMap, std::set<int64_t>& noReportMetricIds) { + unordered_map<int, vector<int>>& conditionToMetricMap, + unordered_map<int, vector<int>>& trackerToMetricMap, + unordered_map<int64_t, int>& metricMap, std::set<int64_t>& noReportMetricIds, + unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap, + unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap, + vector<int>& metricsWithActivation) { sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers); sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchers); const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + - config.event_metric_size() + config.value_metric_size(); + config.event_metric_size() + config.gauge_metric_size() + + config.value_metric_size(); allMetricProducers.reserve(allMetricsCount); - StatsPullerManager statsPullerManager; + + // Construct map from metric id to metric activation index. The map will be used to determine + // the metric activation corresponding to a metric. + unordered_map<int64_t, int> metricToActivationMap; + for (int i = 0; i < config.metric_activation_size(); i++) { + const MetricActivation& metricActivation = config.metric_activation(i); + int64_t metricId = metricActivation.metric_id(); + if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) { + ALOGE("Metric %lld has multiple MetricActivations", (long long) metricId); + return false; + } + metricToActivationMap.insert({metricId, i}); + } // Build MetricProducers for each metric defined in config. // build CountMetricProducer @@ -323,10 +410,9 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t int conditionIndex = -1; if (metric.has_condition()) { - bool good = handleMetricWithConditions( - metric.condition(), metricIndex, conditionTrackerMap, metric.links(), - allConditionTrackers, conditionIndex, conditionToMetricMap); - if (!good) { + if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap)) { return false; } } else { @@ -336,8 +422,32 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + std::vector<int> slicedStateAtoms; + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return false; + } + } else { + if (metric.state_link_size() > 0) { + ALOGW("CountMetric has a MetricStateLink but doesn't have a slice_by_state"); + return false; + } + } + + unordered_map<int, shared_ptr<Activation>> eventActivationMap; + unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; + bool success = handleMetricActivation(config, metric.id(), metricIndex, + metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap, + eventDeactivationMap); + if (!success) return false; + sp<MetricProducer> countProducer = - new CountMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs); + new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, + timeBaseTimeNs, currentTimeNs, eventActivationMap, + eventDeactivationMap, slicedStateAtoms, stateGroupMap); allMetricProducers.push_back(countProducer); } @@ -405,9 +515,46 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + std::vector<int> slicedStateAtoms; + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) { + ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state"); + return false; + } + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return false; + } + } else { + if (metric.state_link_size() > 0) { + ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state"); + return false; + } + } + + // Check that all metric state links are a subset of dimensions_in_what fields. + std::vector<Matcher> dimensionsInWhat; + translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); + for (const auto& stateLink : metric.state_link()) { + if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { + return false; + } + } + + unordered_map<int, shared_ptr<Activation>> eventActivationMap; + unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; + bool success = handleMetricActivation(config, metric.id(), metricIndex, + metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap, + eventDeactivationMap); + if (!success) return false; + sp<MetricProducer> durationMetric = new DurationMetricProducer( - key, metric, conditionIndex, trackerIndices[0], trackerIndices[1], - trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs, currentTimeNs); + key, metric, conditionIndex, initialConditionCache, trackerIndices[0], + trackerIndices[1], trackerIndices[2], nesting, wizard, internalDimensions, + timeBaseTimeNs, currentTimeNs, eventActivationMap, eventDeactivationMap, + slicedStateAtoms, stateGroupMap); allMetricProducers.push_back(durationMetric); } @@ -442,8 +589,17 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + unordered_map<int, shared_ptr<Activation>> eventActivationMap; + unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; + bool success = handleMetricActivation(config, metric.id(), metricIndex, + metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap, + eventDeactivationMap); + if (!success) return false; + sp<MetricProducer> eventMetric = - new EventMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs); + new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, + timeBaseTimeNs, eventActivationMap, eventDeactivationMap); allMetricProducers.push_back(eventMetric); } @@ -482,7 +638,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t return false; } int atomTagId = *(atomMatcher->getAtomIds().begin()); - int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1; + int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; int conditionIndex = -1; if (metric.has_condition()) { @@ -499,9 +655,41 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + std::vector<int> slicedStateAtoms; + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return false; + } + } else { + if (metric.state_link_size() > 0) { + ALOGW("ValueMetric has a MetricStateLink but doesn't have a sliced state"); + return false; + } + } + + // Check that all metric state links are a subset of dimensions_in_what fields. + std::vector<Matcher> dimensionsInWhat; + translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); + for (const auto& stateLink : metric.state_link()) { + if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { + return false; + } + } + + unordered_map<int, shared_ptr<Activation>> eventActivationMap; + unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; + bool success = handleMetricActivation( + config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, eventActivationMap, eventDeactivationMap); + if (!success) return false; + sp<MetricProducer> valueProducer = new ValueMetricProducer( - key, metric, conditionIndex, wizard, trackerIndex, matcherWizard, pullTagId, - timeBaseTimeNs, currentTimeNs, pullerManager); + key, metric, conditionIndex, initialConditionCache, wizard, trackerIndex, + matcherWizard, pullTagId, timeBaseTimeNs, currentTimeNs, pullerManager, + eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap); allMetricProducers.push_back(valueProducer); } @@ -542,7 +730,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t return false; } int atomTagId = *(atomMatcher->getAtomIds().begin()); - int pullTagId = statsPullerManager.PullerForMatcherExists(atomTagId) ? atomTagId : -1; + int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; int triggerTrackerIndex; int triggerAtomId = -1; @@ -585,10 +773,18 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + unordered_map<int, shared_ptr<Activation>> eventActivationMap; + unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; + bool success = handleMetricActivation(config, metric.id(), metricIndex, + metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap, + eventDeactivationMap); + if (!success) return false; + sp<MetricProducer> gaugeProducer = new GaugeMetricProducer( - key, metric, conditionIndex, wizard, - trackerIndex, matcherWizard, pullTagId, triggerAtomId, atomTagId, - timeBaseTimeNs, currentTimeNs, pullerManager); + key, metric, conditionIndex, initialConditionCache, wizard, trackerIndex, + matcherWizard, pullTagId, triggerAtomId, atomTagId, timeBaseTimeNs, currentTimeNs, + pullerManager, eventActivationMap, eventDeactivationMap); allMetricProducers.push_back(gaugeProducer); } for (int i = 0; i < config.no_report_metric_size(); ++i) { @@ -599,15 +795,30 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } noReportMetricIds.insert(no_report_metric); } + + const set<int> whitelistedAtomIds(config.whitelisted_atom_ids().begin(), + config.whitelisted_atom_ids().end()); + for (const auto& it : allMetricProducers) { + // Register metrics to StateTrackers + for (int atomId : it->getSlicedStateAtoms()) { + // Register listener for non-whitelisted atoms only. Using whitelisted atom as a sliced + // state atom is not allowed. + if (whitelistedAtomIds.find(atomId) == whitelistedAtomIds.end()) { + StateManager::getInstance().registerListener(atomId, it); + } else { + return false; + } + } + } return true; } bool initAlerts(const StatsdConfig& config, const unordered_map<int64_t, int>& metricProducerMap, + unordered_map<int64_t, int>& alertTrackerMap, const sp<AlarmMonitor>& anomalyAlarmMonitor, vector<sp<MetricProducer>>& allMetricProducers, vector<sp<AnomalyTracker>>& allAnomalyTrackers) { - unordered_map<int64_t, int> anomalyTrackerMap; for (int i = 0; i < config.alert_size(); i++) { const Alert& alert = config.alert(i); const auto& itr = metricProducerMap.find(alert.metric_id()); @@ -632,7 +843,7 @@ bool initAlerts(const StatsdConfig& config, // The ALOGW for this invalid alert was already displayed in addAnomalyTracker(). return false; } - anomalyTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size())); + alertTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size())); allAnomalyTrackers.push_back(anomalyTracker); } for (int i = 0; i < config.subscription_size(); ++i) { @@ -646,8 +857,8 @@ bool initAlerts(const StatsdConfig& config, (long long)subscription.id()); return false; } - const auto& itr = anomalyTrackerMap.find(subscription.rule_id()); - if (itr == anomalyTrackerMap.end()) { + const auto& itr = alertTrackerMap.find(subscription.rule_id()); + if (itr == alertTrackerMap.end()) { ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"", (long long)subscription.id(), (long long)subscription.rule_id()); return false; @@ -703,73 +914,6 @@ bool initAlarms(const StatsdConfig& config, const ConfigKey& key, return true; } -bool initMetricActivations(const ConfigKey& key, const StatsdConfig& config, - const int64_t currentTimeNs, - const unordered_map<int64_t, int> &logEventTrackerMap, - const unordered_map<int64_t, int> &metricProducerMap, - vector<sp<MetricProducer>>& allMetricProducers, - unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, - unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, - vector<int>& metricsWithActivation) { - for (int i = 0; i < config.metric_activation_size(); ++i) { - const MetricActivation& metric_activation = config.metric_activation(i); - auto itr = metricProducerMap.find(metric_activation.metric_id()); - if (itr == metricProducerMap.end()) { - ALOGE("Metric id not found in metric activation: %lld", - (long long)metric_activation.metric_id()); - return false; - } - const int metricTrackerIndex = itr->second; - if (metricTrackerIndex < 0 || metricTrackerIndex >= (int)allMetricProducers.size()) { - ALOGE("Invalid metric tracker index."); - return false; - } - const sp<MetricProducer>& metric = allMetricProducers[metricTrackerIndex]; - metricsWithActivation.push_back(metricTrackerIndex); - for (int j = 0; j < metric_activation.event_activation_size(); ++j) { - const EventActivation& activation = metric_activation.event_activation(j); - auto logTrackerIt = logEventTrackerMap.find(activation.atom_matcher_id()); - if (logTrackerIt == logEventTrackerMap.end()) { - ALOGE("Atom matcher not found for event activation."); - return false; - } - const int atomMatcherIndex = logTrackerIt->second; - activationAtomTrackerToMetricMap[atomMatcherIndex].push_back( - metricTrackerIndex); - - ActivationType activationType; - if (activation.has_activation_type()) { - activationType = activation.activation_type(); - } else { - activationType = metric_activation.activation_type(); - } - - if (activation.has_deactivation_atom_matcher_id()) { - auto deactivationAtomMatcherIt = - logEventTrackerMap.find(activation.deactivation_atom_matcher_id()); - if (deactivationAtomMatcherIt == logEventTrackerMap.end()) { - ALOGE("Atom matcher not found for event deactivation."); - return false; - } - const int deactivationMatcherIndex = deactivationAtomMatcherIt->second; - deactivationAtomTrackerToMetricMap[deactivationMatcherIndex] - .push_back(metricTrackerIndex); - metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds(), - deactivationMatcherIndex); - } else { - metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds()); - } - } - } - return true; -} - -void prepareFirstBucket(const vector<sp<MetricProducer>>& allMetricProducers) { - for (const auto& metric: allMetricProducers) { - metric->prepareFirstBucket(); - } -} - bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap, const sp<StatsPullerManager>& pullerManager, const sp<AlarmMonitor>& anomalyAlarmMonitor, @@ -785,11 +929,15 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& unordered_map<int, std::vector<int>>& trackerToConditionMap, unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, + unordered_map<int64_t, int>& alertTrackerMap, vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds) { unordered_map<int64_t, int> logTrackerMap; unordered_map<int64_t, int> conditionTrackerMap; + vector<ConditionState> initialConditionCache; unordered_map<int64_t, int> metricProducerMap; + unordered_map<int64_t, int> stateAtomIdMap; + unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps; if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) { ALOGE("initLogMatchingTrackers failed"); @@ -798,20 +946,26 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& VLOG("initLogMatchingTrackers succeed..."); if (!initConditions(key, config, logTrackerMap, conditionTrackerMap, allConditionTrackers, - trackerToConditionMap)) { + trackerToConditionMap, initialConditionCache)) { ALOGE("initConditionTrackers failed"); return false; } + if (!initStates(config, stateAtomIdMap, allStateGroupMaps)) { + ALOGE("initStates failed"); + return false; + } if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, logTrackerMap, - conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers, - conditionToMetricMap, trackerToMetricMap, metricProducerMap, - noReportMetricIds)) { + conditionTrackerMap, allAtomMatchers, stateAtomIdMap, allStateGroupMaps, + allConditionTrackers, initialConditionCache, allMetricProducers, + conditionToMetricMap, trackerToMetricMap, metricProducerMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation)) { ALOGE("initMetricProducers failed"); return false; } - if (!initAlerts(config, metricProducerMap, anomalyAlarmMonitor, allMetricProducers, - allAnomalyTrackers)) { + if (!initAlerts(config, metricProducerMap, alertTrackerMap, anomalyAlarmMonitor, + allMetricProducers, allAnomalyTrackers)) { ALOGE("initAlerts failed"); return false; } @@ -820,14 +974,6 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& ALOGE("initAlarms failed"); return false; } - if (!initMetricActivations(key, config, currentTimeNs, logTrackerMap, metricProducerMap, - allMetricProducers, activationAtomTrackerToMetricMap, - deactivationAtomTrackerToMetricMap, metricsWithActivation)) { - ALOGE("initMetricActivations failed"); - return false; - } - - prepareFirstBucket(allMetricProducers); return true; } diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index ece986b7fcc9..96b5c26ff789 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -60,12 +60,25 @@ bool initLogTrackers(const StatsdConfig& config, // [allConditionTrackers]: stores the sp to all the ConditionTrackers // [trackerToConditionMap]: contain the mapping from index of // log tracker to condition trackers that use the log tracker +// [initialConditionCache]: stores the initial conditions for each ConditionTracker bool initConditions(const ConfigKey& key, const StatsdConfig& config, const std::unordered_map<int64_t, int>& logTrackerMap, std::unordered_map<int64_t, int>& conditionTrackerMap, std::vector<sp<ConditionTracker>>& allConditionTrackers, std::unordered_map<int, std::vector<int>>& trackerToConditionMap, - std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks); + std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks, + std::vector<ConditionState>& initialConditionCache); + +// Initialize State maps using State protos in the config. These maps will +// eventually be passed to MetricProducers to initialize their state info. +// input: +// [config]: the input config +// output: +// [stateAtomIdMap]: this map should contain the mapping from state ids to atom ids +// [allStateGroupMaps]: this map should contain the mapping from states ids and state +// values to state group ids for all states +bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap, + unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps); // Initialize MetricProducers. // input: @@ -74,6 +87,9 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, // [timeBaseSec]: start time base for all metrics // [logTrackerMap]: LogMatchingTracker name to index mapping from previous step. // [conditionTrackerMap]: condition name to index mapping +// [stateAtomIdMap]: contains the mapping from state ids to atom ids +// [allStateGroupMaps]: contains the mapping from atom ids and state values to +// state group ids for all states // output: // [allMetricProducers]: contains the list of sp to the MetricProducers created. // [conditionToMetricMap]: contains the mapping from condition tracker index to @@ -86,11 +102,17 @@ bool initMetrics( const std::unordered_map<int64_t, int>& conditionTrackerMap, const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks, const vector<sp<LogMatchingTracker>>& allAtomMatchers, + const unordered_map<int64_t, int>& stateAtomIdMap, + const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps, vector<sp<ConditionTracker>>& allConditionTrackers, + const std::vector<ConditionState>& initialConditionCache, std::vector<sp<MetricProducer>>& allMetricProducers, std::unordered_map<int, std::vector<int>>& conditionToMetricMap, std::unordered_map<int, std::vector<int>>& trackerToMetricMap, - std::set<int64_t>& noReportMetricIds); + std::set<int64_t>& noReportMetricIds, + std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, + std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, + std::vector<int>& metricsWithActivation); // Initialize MetricsManager from StatsdConfig. // Parameters are the members of MetricsManager. See MetricsManager for declaration. @@ -109,11 +131,10 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& std::unordered_map<int, std::vector<int>>& trackerToConditionMap, unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, + std::unordered_map<int64_t, int>& alertTrackerMap, vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds); -bool isStateTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys); - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index ab0e86e24b02..acf40c88a00d 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -545,7 +545,15 @@ const std::map<string, uint32_t> UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_LMKD", 1069}, {"AID_LLKD", 1070}, {"AID_IORAPD", 1071}, + {"AID_GPU_SERVICE", 1072}, {"AID_NETWORK_STACK", 1073}, + {"AID_GSID", 1074}, + {"AID_FSVERITY_CERT", 1075}, + {"AID_CREDSTORE", 1076}, + {"AID_EXTERNAL_STORAGE", 1077}, + {"AID_EXT_DATA_RW", 1078}, + {"AID_EXT_OBB_RW", 1079}, + {"AID_CONTEXT_HUB", 1080}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, {"AID_DIAG", 2002}}; diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h index bfac6e3431b0..22250aee402e 100644 --- a/cmds/statsd/src/packages/UidMap.h +++ b/cmds/statsd/src/packages/UidMap.h @@ -21,10 +21,11 @@ #include "packages/PackageInfoListener.h" #include "stats_util.h" -#include <binder/IShellCallback.h> #include <gtest/gtest_prod.h> #include <stdio.h> #include <utils/RefBase.h> +#include <utils/String16.h> + #include <list> #include <mutex> #include <set> @@ -138,7 +139,7 @@ public: // record is deleted. void appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set<string>* str_set, bool includeVersionStrings, bool includeInstaller, - util::ProtoOutputStream* proto); + ProtoOutputStream* proto); // Forces the output to be cleared. We still generate a snapshot based on the current state. // This results in extra data uploaded but helps us reconstruct the uid mapping on the server @@ -148,7 +149,7 @@ public: // Get currently cached value of memory used by UID map. size_t getBytesUsed() const; - std::set<int32_t> getAppUid(const string& package) const; + virtual std::set<int32_t> getAppUid(const string& package) const; // Write current PackageInfoSnapshot to ProtoOutputStream. // interestingUids: If not empty, only write the package info for these uids. If empty, write diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp index d6a04336bc46..fd883c29dba0 100644 --- a/cmds/statsd/src/shell/ShellSubscriber.cpp +++ b/cmds/statsd/src/shell/ShellSubscriber.cpp @@ -18,6 +18,8 @@ #include "ShellSubscriber.h" +#include <android-base/file.h> + #include "matchers/matcher_util.h" #include "stats_log_util.h" @@ -29,204 +31,213 @@ namespace statsd { const static int FIELD_ID_ATOM = 1; -void ShellSubscriber::startNewSubscription(int in, int out, sp<IResultReceiver> resultReceiver, - int timeoutSec) { - VLOG("start new shell subscription"); +void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) { + int myToken = claimToken(); + VLOG("ShellSubscriber: new subscription %d has come in", myToken); + mSubscriptionShouldEnd.notify_one(); + + shared_ptr<SubscriptionInfo> mySubscriptionInfo = make_shared<SubscriptionInfo>(in, out); + if (!readConfig(mySubscriptionInfo)) return; + { - std::lock_guard<std::mutex> lock(mMutex); - if (mResultReceiver != nullptr) { - VLOG("Only one shell subscriber is allowed."); - return; - } - mInput = in; - mOutput = out; - mResultReceiver = resultReceiver; - IInterface::asBinder(mResultReceiver)->linkToDeath(this); - } + std::unique_lock<std::mutex> lock(mMutex); + mSubscriptionInfo = mySubscriptionInfo; + spawnHelperThread(myToken); + waitForSubscriptionToEndLocked(mySubscriptionInfo, myToken, lock, timeoutSec); - // Note that the following is blocking, and it's intended as we cannot return until the shell - // cmd exits, otherwise all resources & FDs will be automatically closed. + if (mSubscriptionInfo == mySubscriptionInfo) { + mSubscriptionInfo = nullptr; + } - // Read config forever until EOF is reached. Clients may send multiple configs -- each new - // config replace the previous one. - readConfig(in); - VLOG("timeout : %d", timeoutSec); + } +} - // Now we have read an EOF we now wait for the semaphore until the client exits. - VLOG("Now wait for client to exit"); - std::unique_lock<std::mutex> lk(mMutex); +void ShellSubscriber::spawnHelperThread(int myToken) { + std::thread t([this, myToken] { pullAndSendHeartbeats(myToken); }); + t.detach(); +} +void ShellSubscriber::waitForSubscriptionToEndLocked(shared_ptr<SubscriptionInfo> myInfo, + int myToken, + std::unique_lock<std::mutex>& lock, + int timeoutSec) { if (timeoutSec > 0) { - mShellDied.wait_for(lk, timeoutSec * 1s, - [this, resultReceiver] { return mResultReceiver != resultReceiver; }); + mSubscriptionShouldEnd.wait_for(lock, timeoutSec * 1s, [this, myToken, &myInfo] { + return mToken != myToken || !myInfo->mClientAlive; + }); } else { - mShellDied.wait(lk, [this, resultReceiver] { return mResultReceiver != resultReceiver; }); + mSubscriptionShouldEnd.wait(lock, [this, myToken, &myInfo] { + return mToken != myToken || !myInfo->mClientAlive; + }); } } -void ShellSubscriber::updateConfig(const ShellSubscription& config) { - std::lock_guard<std::mutex> lock(mMutex); - mPushedMatchers.clear(); - mPulledInfo.clear(); +// Atomically claim the next token. Token numbers denote subscriber ordering. +int ShellSubscriber::claimToken() { + std::unique_lock<std::mutex> lock(mMutex); + int myToken = ++mToken; + return myToken; +} - for (const auto& pushed : config.pushed()) { - mPushedMatchers.push_back(pushed); - VLOG("adding matcher for atom %d", pushed.atom_id()); +// Read and parse single config. There should only one config per input. +bool ShellSubscriber::readConfig(shared_ptr<SubscriptionInfo> subscriptionInfo) { + // Read the size of the config. + size_t bufferSize; + if (!android::base::ReadFully(subscriptionInfo->mInputFd, &bufferSize, sizeof(bufferSize))) { + return false; } - int64_t token = getElapsedRealtimeNs(); - mPullToken = token; - - int64_t minInterval = -1; - for (const auto& pulled : config.pulled()) { - // All intervals need to be multiples of the min interval. - if (minInterval < 0 || pulled.freq_millis() < minInterval) { - minInterval = pulled.freq_millis(); - } + // Read the config. + vector<uint8_t> buffer(bufferSize); + if (!android::base::ReadFully(subscriptionInfo->mInputFd, buffer.data(), bufferSize)) { + return false; + } - mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis()); - VLOG("adding matcher for pulled atom %d", pulled.matcher().atom_id()); + // Parse the config. + ShellSubscription config; + if (!config.ParseFromArray(buffer.data(), bufferSize)) { + return false; } - if (mPulledInfo.size() > 0 && minInterval > 0) { - // This thread is guaranteed to terminate after it detects the token is different or - // cleaned up. - std::thread puller([token, minInterval, this] { startPull(token, minInterval); }); - puller.detach(); + // Update SubscriptionInfo with state from config + for (const auto& pushed : config.pushed()) { + subscriptionInfo->mPushedMatchers.push_back(pushed); } -} -void ShellSubscriber::writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data, - const SimpleAtomMatcher& matcher) { - if (mOutput == 0) return; - int count = 0; - mProto.clear(); - for (const auto& event : data) { - VLOG("%s", event->ToString().c_str()); - if (matchesSimple(*mUidMap, matcher, *event)) { - VLOG("matched"); - count++; - uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE | - util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM); - event->ToProto(mProto); - mProto.end(atomToken); + for (const auto& pulled : config.pulled()) { + vector<string> packages; + vector<int32_t> uids; + for (const string& pkg : pulled.packages()) { + auto it = UidMap::sAidToUidMapping.find(pkg); + if (it != UidMap::sAidToUidMapping.end()) { + uids.push_back(it->second); + } else { + packages.push_back(pkg); + } } - } - if (count > 0) { - // First write the payload size. - size_t bufferSize = mProto.size(); - write(mOutput, &bufferSize, sizeof(bufferSize)); - VLOG("%d atoms, proto size: %zu", count, bufferSize); - // Then write the payload. - mProto.flush(mOutput); + subscriptionInfo->mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis(), packages, + uids); + VLOG("adding matcher for pulled atom %d", pulled.matcher().atom_id()); } - mProto.clear(); + + return true; } -void ShellSubscriber::startPull(int64_t token, int64_t intervalMillis) { - while (1) { - int64_t nowMillis = getElapsedRealtimeMillis(); +void ShellSubscriber::pullAndSendHeartbeats(int myToken) { + VLOG("ShellSubscriber: helper thread %d starting", myToken); + while (true) { + int64_t sleepTimeMs = INT_MAX; { std::lock_guard<std::mutex> lock(mMutex); - if (mPulledInfo.size() == 0 || mPullToken != token) { - VLOG("Pulling thread %lld done!", (long long)token); + if (!mSubscriptionInfo || mToken != myToken) { + VLOG("ShellSubscriber: helper thread %d done!", myToken); return; } - for (auto& pullInfo : mPulledInfo) { - if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval < nowMillis) { - VLOG("pull atom %d now", pullInfo.mPullerMatcher.atom_id()); - - vector<std::shared_ptr<LogEvent>> data; - mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), &data); - VLOG("pulled %zu atoms", data.size()); - if (data.size() > 0) { - writeToOutputLocked(data, pullInfo.mPullerMatcher); - } - pullInfo.mPrevPullElapsedRealtimeMs = nowMillis; + + int64_t nowMillis = getElapsedRealtimeMillis(); + int64_t nowNanos = getElapsedRealtimeNs(); + for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) { + if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval >= nowMillis) { + continue; } + + vector<int32_t> uids; + getUidsForPullAtom(&uids, pullInfo); + + vector<std::shared_ptr<LogEvent>> data; + mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data); + VLOG("Pulled %zu atoms with id %d", data.size(), pullInfo.mPullerMatcher.atom_id()); + writePulledAtomsLocked(data, pullInfo.mPullerMatcher); + + pullInfo.mPrevPullElapsedRealtimeMs = nowMillis; } + + // Send a heartbeat, consisting of a data size of 0, if perfd hasn't recently received + // data from statsd. When it receives the data size of 0, perfd will not expect any + // atoms and recheck whether the subscription should end. + if (nowMillis - mLastWriteMs > kMsBetweenHeartbeats) { + attemptWriteToPipeLocked(/*dataSize=*/0); + } + + // Determine how long to sleep before doing more work. + for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) { + int64_t nextPullTime = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval; + int64_t timeBeforePull = nextPullTime - nowMillis; // guaranteed to be non-negative + if (timeBeforePull < sleepTimeMs) sleepTimeMs = timeBeforePull; + } + int64_t timeBeforeHeartbeat = (mLastWriteMs + kMsBetweenHeartbeats) - nowMillis; + if (timeBeforeHeartbeat < sleepTimeMs) sleepTimeMs = timeBeforeHeartbeat; } - VLOG("Pulling thread %lld sleep....", (long long)token); - std::this_thread::sleep_for(std::chrono::milliseconds(intervalMillis)); + + VLOG("ShellSubscriber: helper thread %d sleeping for %lld ms", myToken, + (long long)sleepTimeMs); + std::this_thread::sleep_for(std::chrono::milliseconds(sleepTimeMs)); } } -void ShellSubscriber::readConfig(int in) { - if (in <= 0) { - return; +void ShellSubscriber::getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo) { + uids->insert(uids->end(), pullInfo.mPullUids.begin(), pullInfo.mPullUids.end()); + // This is slow. Consider storing the uids per app and listening to uidmap updates. + for (const string& pkg : pullInfo.mPullPackages) { + set<int32_t> uidsForPkg = mUidMap->getAppUid(pkg); + uids->insert(uids->end(), uidsForPkg.begin(), uidsForPkg.end()); } + uids->push_back(DEFAULT_PULL_UID); +} - while (1) { - size_t bufferSize = 0; - int result = 0; - if ((result = read(in, &bufferSize, sizeof(bufferSize))) == 0) { - VLOG("Done reading"); - break; - } else if (result < 0 || result != sizeof(bufferSize)) { - ALOGE("Error reading config size"); - break; - } - - vector<uint8_t> buffer(bufferSize); - if ((result = read(in, buffer.data(), bufferSize)) > 0 && ((size_t)result) == bufferSize) { - ShellSubscription config; - if (config.ParseFromArray(buffer.data(), bufferSize)) { - updateConfig(config); - } else { - ALOGE("error parsing the config"); - break; - } - } else { - VLOG("Error reading the config, returned: %d, expecting %zu", result, bufferSize); - break; +void ShellSubscriber::writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data, + const SimpleAtomMatcher& matcher) { + mProto.clear(); + int count = 0; + for (const auto& event : data) { + if (matchesSimple(*mUidMap, matcher, *event)) { + count++; + uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE | + util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM); + event->ToProto(mProto); + mProto.end(atomToken); } } -} -void ShellSubscriber::cleanUpLocked() { - // The file descriptors will be closed by binder. - mInput = 0; - mOutput = 0; - mResultReceiver = nullptr; - mPushedMatchers.clear(); - mPulledInfo.clear(); - mPullToken = 0; - VLOG("done clean up"); + if (count > 0) attemptWriteToPipeLocked(mProto.size()); } void ShellSubscriber::onLogEvent(const LogEvent& event) { std::lock_guard<std::mutex> lock(mMutex); + if (!mSubscriptionInfo) return; - if (mOutput <= 0) { - return; - } - for (const auto& matcher : mPushedMatchers) { + mProto.clear(); + for (const auto& matcher : mSubscriptionInfo->mPushedMatchers) { if (matchesSimple(*mUidMap, matcher, event)) { - VLOG("%s", event.ToString().c_str()); uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE | util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM); event.ToProto(mProto); mProto.end(atomToken); - // First write the payload size. - size_t bufferSize = mProto.size(); - write(mOutput, &bufferSize, sizeof(bufferSize)); - - // Then write the payload. - mProto.flush(mOutput); - mProto.clear(); - break; + attemptWriteToPipeLocked(mProto.size()); } } } -void ShellSubscriber::binderDied(const wp<IBinder>& who) { - { - VLOG("Shell exits"); - std::lock_guard<std::mutex> lock(mMutex); - cleanUpLocked(); +// Tries to write the atom encoded in mProto to the pipe. If the write fails +// because the read end of the pipe has closed, signals to other threads that +// the subscription should end. +void ShellSubscriber::attemptWriteToPipeLocked(size_t dataSize) { + // First, write the payload size. + if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &dataSize, sizeof(dataSize))) { + mSubscriptionInfo->mClientAlive = false; + mSubscriptionShouldEnd.notify_one(); + return; } - mShellDied.notify_all(); + + // Then, write the payload if this is not just a heartbeat. + if (dataSize > 0 && !mProto.flush(mSubscriptionInfo->mOutputFd)) { + mSubscriptionInfo->mClientAlive = false; + mSubscriptionShouldEnd.notify_one(); + return; + } + + mLastWriteMs = getElapsedRealtimeMillis(); } } // namespace statsd diff --git a/cmds/statsd/src/shell/ShellSubscriber.h b/cmds/statsd/src/shell/ShellSubscriber.h index 86d85901083a..4c05fa7f71c2 100644 --- a/cmds/statsd/src/shell/ShellSubscriber.h +++ b/cmds/statsd/src/shell/ShellSubscriber.h @@ -16,16 +16,17 @@ #pragma once -#include "logd/LogEvent.h" - #include <android/util/ProtoOutputStream.h> -#include <binder/IResultReceiver.h> +#include <private/android_filesystem_config.h> + #include <condition_variable> #include <mutex> #include <thread> + #include "external/StatsPullerManager.h" #include "frameworks/base/cmds/statsd/src/shell/shell_config.pb.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "logd/LogEvent.h" #include "packages/UidMap.h" namespace android { @@ -37,11 +38,11 @@ namespace statsd { * * A shell subscription lasts *until shell exits*. Unlike config based clients, a shell client * communicates with statsd via file descriptors. They can subscribe pushed and pulled atoms. - * The atoms are sent back to the client in real time, as opposed to - * keeping the data in memory. Shell clients do not subscribe aggregated metrics, as they are - * responsible for doing the aggregation after receiving the atom events. + * The atoms are sent back to the client in real time, as opposed to keeping the data in memory. + * Shell clients do not subscribe aggregated metrics, as they are responsible for doing the + * aggregation after receiving the atom events. * - * Shell client pass ShellSubscription in the proto binary format. Client can update the + * Shell clients pass ShellSubscription in the proto binary format. Clients can update the * subscription by sending a new subscription. The new subscription would replace the old one. * Input data stream format is: * @@ -53,43 +54,70 @@ namespace statsd { * The stream would be in the following format: * |size_t|shellData proto|size_t|shellData proto|.... * - * Only one shell subscriber allowed at a time, because each shell subscriber blocks one thread + * Only one shell subscriber is allowed at a time because each shell subscriber blocks one thread * until it exits. */ -class ShellSubscriber : public virtual IBinder::DeathRecipient { +class ShellSubscriber : public virtual RefBase { public: ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr) : mUidMap(uidMap), mPullerMgr(pullerMgr){}; - /** - * Start a new subscription. - */ - void startNewSubscription(int inFd, int outFd, sp<IResultReceiver> resultReceiver, - int timeoutSec); - - void binderDied(const wp<IBinder>& who); + void startNewSubscription(int inFd, int outFd, int timeoutSec); void onLogEvent(const LogEvent& event); private: struct PullInfo { - PullInfo(const SimpleAtomMatcher& matcher, int64_t interval) - : mPullerMatcher(matcher), mInterval(interval), mPrevPullElapsedRealtimeMs(0) { + PullInfo(const SimpleAtomMatcher& matcher, int64_t interval, + const std::vector<std::string>& packages, const std::vector<int32_t>& uids) + : mPullerMatcher(matcher), + mInterval(interval), + mPrevPullElapsedRealtimeMs(0), + mPullPackages(packages), + mPullUids(uids) { } SimpleAtomMatcher mPullerMatcher; int64_t mInterval; int64_t mPrevPullElapsedRealtimeMs; + std::vector<std::string> mPullPackages; + std::vector<int32_t> mPullUids; + }; + + struct SubscriptionInfo { + SubscriptionInfo(const int& inputFd, const int& outputFd) + : mInputFd(inputFd), mOutputFd(outputFd), mClientAlive(true) { + } + + int mInputFd; + int mOutputFd; + std::vector<SimpleAtomMatcher> mPushedMatchers; + std::vector<PullInfo> mPulledInfo; + bool mClientAlive; }; - void readConfig(int in); - void updateConfig(const ShellSubscription& config); + int claimToken(); + + bool readConfig(std::shared_ptr<SubscriptionInfo> subscriptionInfo); + + void spawnHelperThread(int myToken); - void startPull(int64_t token, int64_t intervalMillis); + void waitForSubscriptionToEndLocked(std::shared_ptr<SubscriptionInfo> myInfo, + int myToken, + std::unique_lock<std::mutex>& lock, + int timeoutSec); - void cleanUpLocked(); + // Helper thread that pulls atoms at a regular frequency and sends + // heartbeats to perfd if statsd hasn't recently sent any data. Statsd must + // send heartbeats for perfd to escape a blocking read call and recheck if + // the user has terminated the subscription. + void pullAndSendHeartbeats(int myToken); - void writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data, - const SimpleAtomMatcher& matcher); + void writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data, + const SimpleAtomMatcher& matcher); + + void getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo); + + void attemptWriteToPipeLocked(size_t dataSize); sp<UidMap> mUidMap; @@ -99,19 +127,18 @@ private: mutable std::mutex mMutex; - std::condition_variable mShellDied; // semaphore for waiting until shell exits. - - int mInput; // The input file descriptor - - int mOutput; // The output file descriptor + std::condition_variable mSubscriptionShouldEnd; - sp<IResultReceiver> mResultReceiver; + std::shared_ptr<SubscriptionInfo> mSubscriptionInfo = nullptr; - std::vector<SimpleAtomMatcher> mPushedMatchers; + int mToken = 0; - std::vector<PullInfo> mPulledInfo; + const int32_t DEFAULT_PULL_UID = AID_SYSTEM; - int64_t mPullToken = 0; // A unique token to identify a puller thread. + // Tracks when we last send data to perfd. We need that time to determine + // when next to send a heartbeat. + int64_t mLastWriteMs = 0; + const int64_t kMsBetweenHeartbeats = 1000; }; } // namespace statsd diff --git a/cmds/statsd/src/shell/shell_config.proto b/cmds/statsd/src/shell/shell_config.proto index 73cb49a61821..07d0310ef2dd 100644 --- a/cmds/statsd/src/shell/shell_config.proto +++ b/cmds/statsd/src/shell/shell_config.proto @@ -28,6 +28,9 @@ message PulledAtomSubscription { /* gap between two pulls in milliseconds */ optional int32 freq_millis = 2; + + /* Packages that the pull is requested from */ + repeated string packages = 3; } message ShellSubscription { diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp index bedfa7dec7a4..b877cc9c352f 100755 --- a/cmds/statsd/src/socket/StatsSocketListener.cpp +++ b/cmds/statsd/src/socket/StatsSocketListener.cpp @@ -36,8 +36,6 @@ namespace android { namespace os { namespace statsd { -static const int kLogMsgHeaderSize = 28; - StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue) : SocketListener(getLogSocket(), false /*start listen*/), mQueue(queue) { } @@ -92,7 +90,7 @@ bool StatsSocketListener::onDataAvailable(SocketClient* cli) { cred->uid = DEFAULT_OVERFLOWUID; } - char* ptr = ((char*)buffer) + sizeof(android_log_header_t); + uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t); n -= sizeof(android_log_header_t); // When a log failed to write to statsd socket (e.g., due ot EBUSY), a special message would @@ -121,18 +119,17 @@ bool StatsSocketListener::onDataAvailable(SocketClient* cli) { } } - log_msg msg; - - msg.entry.len = n; - msg.entry.hdr_size = kLogMsgHeaderSize; - msg.entry.sec = time(nullptr); - msg.entry.pid = cred->pid; - msg.entry.uid = cred->uid; - - memcpy(msg.buf + kLogMsgHeaderSize, ptr, n + 1); + // move past the 4-byte StatsEventTag + uint8_t* msg = ptr + sizeof(uint32_t); + uint32_t len = n - sizeof(uint32_t); + uint32_t uid = cred->uid; + uint32_t pid = cred->pid; int64_t oldestTimestamp; - if (!mQueue->push(std::make_unique<LogEvent>(msg), &oldestTimestamp)) { + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid); + logEvent->parseBuffer(msg, len); + + if (!mQueue->push(std::move(logEvent), &oldestTimestamp)) { StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp); } diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h new file mode 100644 index 000000000000..63880017ca18 --- /dev/null +++ b/cmds/statsd/src/state/StateListener.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <utils/RefBase.h> + +#include "HashableDimensionKey.h" + +namespace android { +namespace os { +namespace statsd { + +class StateListener : public virtual RefBase { +public: + StateListener(){}; + + virtual ~StateListener(){}; + + /** + * Interface for handling a state change. + * + * The old and new state values map to the original state values. + * StateTrackers only track the original state values and are unaware + * of higher-level state groups. MetricProducers hold information on + * state groups and are responsible for mapping original state values to + * the correct state group. + * + * [eventTimeNs]: Time of the state change log event. + * [atomId]: The id of the state atom + * [primaryKey]: The primary field values of the state atom + * [oldState]: Previous state value before state change + * [newState]: Current state value after state change + */ + virtual void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) = 0; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp new file mode 100644 index 000000000000..c29afeb794fa --- /dev/null +++ b/cmds/statsd/src/state/StateManager.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2019, 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. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "StateManager.h" + +#include <private/android_filesystem_config.h> + +namespace android { +namespace os { +namespace statsd { + +StateManager::StateManager() + : mAllowedPkg({ + "com.android.systemui", + }) { +} + +StateManager& StateManager::getInstance() { + static StateManager sStateManager; + return sStateManager; +} + +void StateManager::clear() { + mStateTrackers.clear(); +} + +void StateManager::onLogEvent(const LogEvent& event) { + // Only process state events from uids in AID_* and packages that are whitelisted in + // mAllowedPkg. + // Whitelisted AIDs are AID_ROOT and all AIDs in [1000, 2000) + if (event.GetUid() == AID_ROOT || (event.GetUid() >= 1000 && event.GetUid() < 2000) || + mAllowedLogSources.find(event.GetUid()) != mAllowedLogSources.end()) { + if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) { + mStateTrackers[event.GetTagId()]->onLogEvent(event); + } + } +} + +void StateManager::registerListener(const int32_t atomId, wp<StateListener> listener) { + // Check if state tracker already exists. + if (mStateTrackers.find(atomId) == mStateTrackers.end()) { + mStateTrackers[atomId] = new StateTracker(atomId); + } + mStateTrackers[atomId]->registerListener(listener); +} + +void StateManager::unregisterListener(const int32_t atomId, wp<StateListener> listener) { + std::unique_lock<std::mutex> lock(mMutex); + + // Hold the sp<> until the lock is released so that ~StateTracker() is + // not called while the lock is held. + sp<StateTracker> toRemove; + + // Unregister listener from correct StateTracker + auto it = mStateTrackers.find(atomId); + if (it != mStateTrackers.end()) { + it->second->unregisterListener(listener); + + // Remove the StateTracker if it has no listeners + if (it->second->getListenersCount() == 0) { + toRemove = it->second; + mStateTrackers.erase(it); + } + } else { + ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist", + atomId); + } + lock.unlock(); +} + +bool StateManager::getStateValue(const int32_t atomId, const HashableDimensionKey& key, + FieldValue* output) const { + auto it = mStateTrackers.find(atomId); + if (it != mStateTrackers.end()) { + return it->second->getStateValue(key, output); + } + return false; +} + +void StateManager::updateLogSources(const sp<UidMap>& uidMap) { + mAllowedLogSources.clear(); + for (const auto& pkg : mAllowedPkg) { + auto uids = uidMap->getAppUid(pkg); + mAllowedLogSources.insert(uids.begin(), uids.end()); + } +} + +void StateManager::notifyAppChanged(const string& apk, const sp<UidMap>& uidMap) { + if (mAllowedPkg.find(apk) != mAllowedPkg.end()) { + updateLogSources(uidMap); + } +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h new file mode 100644 index 000000000000..18c404c29c4e --- /dev/null +++ b/cmds/statsd/src/state/StateManager.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <inttypes.h> +#include <utils/RefBase.h> + +#include <set> +#include <string> +#include <unordered_map> + +#include "HashableDimensionKey.h" +#include "packages/UidMap.h" +#include "state/StateListener.h" +#include "state/StateTracker.h" + +namespace android { +namespace os { +namespace statsd { + +/** + * This class is NOT thread safe. + * It should only be used while StatsLogProcessor's lock is held. + */ +class StateManager : public virtual RefBase { +public: + StateManager(); + + ~StateManager(){}; + + // Returns a pointer to the single, shared StateManager object. + static StateManager& getInstance(); + + // Unregisters all listeners and removes all trackers from StateManager. + void clear(); + + // Notifies the correct StateTracker of an event. + void onLogEvent(const LogEvent& event); + + // Notifies the StateTracker for the given atomId to register listener. + // If the correct StateTracker does not exist, a new StateTracker is created. + // Note: StateTrackers can be created for non-state atoms. They are essentially empty and + // do not perform any actions. + void registerListener(const int32_t atomId, wp<StateListener> listener); + + // Notifies the correct StateTracker to unregister a listener + // and removes the tracker if it no longer has any listeners. + void unregisterListener(const int32_t atomId, wp<StateListener> listener); + + // Returns true if the StateTracker exists and queries for the + // original state value mapped to the given query key. The state value is + // stored and output in a FieldValue class. + // Returns false if the StateTracker doesn't exist. + bool getStateValue(const int32_t atomId, const HashableDimensionKey& queryKey, + FieldValue* output) const; + + // Updates mAllowedLogSources with the latest uids for the packages that are allowed to log. + void updateLogSources(const sp<UidMap>& uidMap); + + void notifyAppChanged(const string& apk, const sp<UidMap>& uidMap); + + inline int getStateTrackersCount() const { + return mStateTrackers.size(); + } + + inline int getListenersCount(const int32_t atomId) const { + auto it = mStateTrackers.find(atomId); + if (it != mStateTrackers.end()) { + return it->second->getListenersCount(); + } + return -1; + } + +private: + mutable std::mutex mMutex; + + // Maps state atom ids to StateTrackers + std::unordered_map<int32_t, sp<StateTracker>> mStateTrackers; + + // The package names that can log state events. + const std::set<std::string> mAllowedPkg; + + // The combined uid sources (after translating pkg name to uid). + // State events from uids that are not in the list will be ignored to avoid state pollution. + std::set<int32_t> mAllowedLogSources; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp new file mode 100644 index 000000000000..41e525c343ba --- /dev/null +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019, 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. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include "stats_util.h" + +#include "StateTracker.h" + +namespace android { +namespace os { +namespace statsd { + +StateTracker::StateTracker(const int32_t atomId) : mField(atomId, 0) { +} + +void StateTracker::onLogEvent(const LogEvent& event) { + const int64_t eventTimeNs = event.GetElapsedTimestampNs(); + + // Parse event for primary field values i.e. primary key. + HashableDimensionKey primaryKey; + filterPrimaryKey(event.getValues(), &primaryKey); + + FieldValue newState; + if (!getStateFieldValueFromLogEvent(event, &newState)) { + ALOGE("StateTracker error extracting state from log event. Missing exclusive state field."); + clearStateForPrimaryKey(eventTimeNs, primaryKey); + return; + } + + mField.setField(newState.mField.getField()); + + if (newState.mValue.getType() != INT) { + ALOGE("StateTracker error extracting state from log event. Type: %d", + newState.mValue.getType()); + clearStateForPrimaryKey(eventTimeNs, primaryKey); + return; + } + + if (int resetState = event.getResetState(); resetState != -1) { + VLOG("StateTracker new reset state: %d", resetState); + const FieldValue resetStateFieldValue(mField, Value(resetState)); + handleReset(eventTimeNs, resetStateFieldValue); + return; + } + + const bool nested = newState.mAnnotations.isNested(); + StateValueInfo* stateValueInfo = &mStateMap[primaryKey]; + updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo); +} + +void StateTracker::registerListener(wp<StateListener> listener) { + mListeners.insert(listener); +} + +void StateTracker::unregisterListener(wp<StateListener> listener) { + mListeners.erase(listener); +} + +bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const { + output->mField = mField; + + if (const auto it = mStateMap.find(queryKey); it != mStateMap.end()) { + output->mValue = it->second.state; + return true; + } + + // Set the state value to kStateUnknown if query key is not found in state map. + output->mValue = kStateUnknown; + return false; +} + +void StateTracker::handleReset(const int64_t eventTimeNs, const FieldValue& newState) { + VLOG("StateTracker handle reset"); + for (auto& [primaryKey, stateValueInfo] : mStateMap) { + updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, + false /* nested; treat this state change as not nested */, + &stateValueInfo); + } +} + +void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey) { + VLOG("StateTracker clear state for primary key"); + const std::unordered_map<HashableDimensionKey, StateValueInfo>::iterator it = + mStateMap.find(primaryKey); + + // If there is no entry for the primaryKey in mStateMap, then the state is already + // kStateUnknown. + const FieldValue state(mField, Value(kStateUnknown)); + if (it != mStateMap.end()) { + updateStateForPrimaryKey(eventTimeNs, primaryKey, state, + false /* nested; treat this state change as not nested */, + &it->second); + } +} + +void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey, + const FieldValue& newState, const bool nested, + StateValueInfo* stateValueInfo) { + FieldValue oldState; + oldState.mField = mField; + oldState.mValue.setInt(stateValueInfo->state); + const int32_t oldStateValue = stateValueInfo->state; + const int32_t newStateValue = newState.mValue.int_value; + + if (kStateUnknown == newStateValue) { + mStateMap.erase(primaryKey); + } + + // Update state map for non-nested counting case. + // Every state event triggers a state overwrite. + if (!nested) { + stateValueInfo->state = newStateValue; + stateValueInfo->count = 1; + + // Notify listeners if state has changed. + if (oldStateValue != newStateValue) { + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } + return; + } + + // 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. There is no enforcemnt here of this requirement. + // The atom must be logged correctly. + if (kStateUnknown == newStateValue) { + if (kStateUnknown != oldStateValue) { + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } + } else if (oldStateValue == kStateUnknown) { + stateValueInfo->state = newStateValue; + stateValueInfo->count = 1; + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } else if (oldStateValue == newStateValue) { + stateValueInfo->count++; + } else if (--stateValueInfo->count == 0) { + stateValueInfo->state = newStateValue; + stateValueInfo->count = 1; + notifyListeners(eventTimeNs, primaryKey, oldState, newState); + } +} + +void StateTracker::notifyListeners(const int64_t eventTimeNs, + const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState) { + for (auto l : mListeners) { + auto sl = l.promote(); + if (sl != nullptr) { + sl->onStateChanged(eventTimeNs, mField.getTag(), primaryKey, oldState, newState); + } + } +} + +bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output) { + const int exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex(); + if (-1 == exclusiveStateFieldIndex) { + ALOGE("error extracting state from log event. Missing exclusive state field."); + return false; + } + + *output = event.getValues()[exclusiveStateFieldIndex]; + return true; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h new file mode 100644 index 000000000000..abd579e7e302 --- /dev/null +++ b/cmds/statsd/src/state/StateTracker.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <utils/RefBase.h> +#include "HashableDimensionKey.h" +#include "logd/LogEvent.h" + +#include "state/StateListener.h" + +#include <unordered_map> + +namespace android { +namespace os { +namespace statsd { + +class StateTracker : public virtual RefBase { +public: + StateTracker(const int32_t atomId); + + virtual ~StateTracker(){}; + + // Updates state map and notifies all listeners if a state change occurs. + // Checks if a state change has occurred by getting the state value from + // the log event and comparing the old and new states. + void onLogEvent(const LogEvent& event); + + // Adds new listeners to set of StateListeners. If a listener is already + // registered, it is ignored. + void registerListener(wp<StateListener> listener); + + void unregisterListener(wp<StateListener> listener); + + // The output is a FieldValue object that has mStateField as the field and + // the original state value (found using the given query key) as the value. + // + // If the key isn't mapped to a state or the key size doesn't match the + // number of primary fields, the output value is set to kStateUnknown. + bool getStateValue(const HashableDimensionKey& queryKey, FieldValue* output) const; + + inline int getListenersCount() const { + return mListeners.size(); + } + + const static int kStateUnknown = -1; + +private: + struct StateValueInfo { + int32_t state = kStateUnknown; // state value + int count = 0; // nested count (only used for binary states) + }; + + Field mField; + + // Maps primary key to state value info + std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap; + + // Set of all StateListeners (objects listening for state changes) + std::set<wp<StateListener>> mListeners; + + // Reset all state values in map to the given state. + void handleReset(const int64_t eventTimeNs, const FieldValue& newState); + + // Clears the state value mapped to the given primary key by setting it to kStateUnknown. + void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey); + + // Update the StateMap based on the received state value. + void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, + const FieldValue& newState, const bool nested, + StateValueInfo* stateValueInfo); + + // Notify registered state listeners of state change. + void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey, + const FieldValue& oldState, const FieldValue& newState); +}; + +bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index b87547056ad3..ddd2725c9cb9 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -41,6 +41,15 @@ message DimensionsValueTuple { repeated DimensionsValue dimensions_value = 1; } +message StateValue { + optional int32 atom_id = 1; + + oneof contents { + int64 group_id = 2; + int32 value = 3; + } +} + message EventMetricData { optional int64 elapsed_timestamp_nanos = 1; @@ -66,13 +75,15 @@ message CountBucketInfo { message CountMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + repeated StateValue slice_by_state = 6; repeated CountBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message DurationBucketInfo { @@ -92,13 +103,15 @@ message DurationBucketInfo { message DurationMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + repeated StateValue slice_by_state = 6; repeated DurationBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message ValueBucketInfo { @@ -136,13 +149,15 @@ message ValueBucketInfo { message ValueMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + repeated StateValue slice_by_state = 6; repeated ValueBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message GaugeBucketInfo { @@ -166,13 +181,16 @@ message GaugeBucketInfo { message GaugeMetricData { optional DimensionsValue dimensions_in_what = 1; - optional DimensionsValue dimensions_in_condition = 2; + // Currently unsupported + repeated StateValue slice_by_state = 6; repeated GaugeBucketInfo bucket_info = 3; repeated DimensionsValue dimension_leaf_values_in_what = 4; - repeated DimensionsValue dimension_leaf_values_in_condition = 5; + optional DimensionsValue dimensions_in_condition = 2 [deprecated = true]; + + repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true]; } message StatsLogReport { @@ -180,11 +198,42 @@ message StatsLogReport { // Fields 2 and 3 are reserved. + // Keep this in sync with BucketDropReason enum in MetricProducer.h. + enum BucketDropReason { + // For ValueMetric, a bucket is dropped during a dump report request iff + // current bucket should be included, a pull is needed (pulled metric and + // condition is true), and we are under fast time constraints. + DUMP_REPORT_REQUESTED = 1; + EVENT_IN_WRONG_BUCKET = 2; + CONDITION_UNKNOWN = 3; + PULL_FAILED = 4; + PULL_DELAYED = 5; + DIMENSION_GUARDRAIL_REACHED = 6; + MULTIPLE_BUCKETS_SKIPPED = 7; + // Not an invalid bucket case, but the bucket is dropped. + BUCKET_TOO_SMALL = 8; + // Not an invalid bucket case, but the bucket is skipped. + NO_DATA = 9; + }; + + message DropEvent { + optional BucketDropReason drop_reason = 1; + + optional int64 drop_time_millis = 2; + } + message SkippedBuckets { optional int64 start_bucket_elapsed_nanos = 1; + optional int64 end_bucket_elapsed_nanos = 2; + optional int64 start_bucket_elapsed_millis = 3; + optional int64 end_bucket_elapsed_millis = 4; + + // The number of drop events is capped by StatsdStats::kMaxLoggedBucketDropEvents. + // The current maximum is 10 drop events. + repeated DropEvent drop_event = 5; } message EventMetricDataWrapper { @@ -220,7 +269,7 @@ message StatsLogReport { optional DimensionsValue dimensions_path_in_what = 11; - optional DimensionsValue dimensions_path_in_condition = 12; + optional DimensionsValue dimensions_path_in_condition = 12 [deprecated = true]; // DO NOT USE field 13. @@ -363,7 +412,7 @@ message StatsdStatsReport { repeated ConditionStats condition_stats = 14; repeated MetricStats metric_stats = 15; repeated AlertStats alert_stats = 16; - repeated MetricStats metric_dimension_in_condition_stats = 17; + repeated MetricStats metric_dimension_in_condition_stats = 17 [deprecated = true]; message Annotation { optional int64 field_int64 = 1; optional int32 field_int32 = 2; @@ -378,6 +427,7 @@ message StatsdStatsReport { message AtomStats { optional int32 tag = 1; optional int32 count = 2; + optional int32 error_count = 3; } repeated AtomStats atom_stats = 7; @@ -408,11 +458,20 @@ message StatsdStatsReport { optional int64 pull_timeout = 10; optional int64 pull_exceed_max_delay = 11; optional int64 pull_failed = 12; - optional int64 stats_companion_pull_failed = 13; - optional int64 stats_companion_pull_binder_transaction_failed = 14; + optional int64 stats_companion_pull_failed = 13 [deprecated = true]; + optional int64 stats_companion_pull_binder_transaction_failed = 14 [deprecated = true]; optional int64 empty_data = 15; optional int64 registered_count = 16; optional int64 unregistered_count = 17; + optional int32 atom_error_count = 18; + optional int64 binder_call_failed = 19; + optional int64 failed_uid_provider_not_found = 20; + optional int64 puller_not_found = 21; + message PullTimeoutMetadata { + optional int64 pull_timeout_uptime_millis = 1; + optional int64 pull_timeout_elapsed_millis = 2; + } + repeated PullTimeoutMetadata pull_atom_metadata = 22; } repeated PulledAtomStats pulled_atom_stats = 10; @@ -483,7 +542,7 @@ message AlertTriggerDetails { message MetricValue { optional int64 metric_id = 1; optional DimensionsValue dimension_in_what = 2; - optional DimensionsValue dimension_in_condition = 3; + optional DimensionsValue dimension_in_condition = 3 [deprecated = true]; optional int64 value = 4; } oneof value { diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index a7b681014292..423bae8bc0a4 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -17,11 +17,13 @@ #include "hash.h" #include "stats_log_util.h" +#include <aidl/android/os/IStatsCompanionService.h> #include <private/android_filesystem_config.h> #include <set> #include <utils/SystemClock.h> -using android::util::AtomsInfo; +#include "statscompanion_util.h" + using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_BOOL; using android::util::FIELD_TYPE_FIXED64; @@ -33,6 +35,10 @@ using android::util::FIELD_TYPE_STRING; using android::util::FIELD_TYPE_UINT64; using android::util::ProtoOutputStream; +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; +using std::string; + namespace android { namespace os { namespace statsd { @@ -49,6 +55,11 @@ const int DIMENSIONS_VALUE_VALUE_STR_HASH = 8; const int DIMENSIONS_VALUE_TUPLE_VALUE = 1; +// for StateValue Proto +const int STATE_VALUE_ATOM_ID = 1; +const int STATE_VALUE_CONTENTS_GROUP_ID = 2; +const int STATE_VALUE_CONTENTS_VALUE = 3; + // for PulledAtomStats proto const int FIELD_ID_PULLED_ATOM_STATS = 10; const int FIELD_ID_PULL_ATOM_ID = 1; @@ -63,11 +74,17 @@ const int FIELD_ID_DATA_ERROR = 9; const int FIELD_ID_PULL_TIMEOUT = 10; const int FIELD_ID_PULL_EXCEED_MAX_DELAY = 11; const int FIELD_ID_PULL_FAILED = 12; -const int FIELD_ID_STATS_COMPANION_FAILED = 13; -const int FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED = 14; const int FIELD_ID_EMPTY_DATA = 15; const int FIELD_ID_PULL_REGISTERED_COUNT = 16; const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17; +const int FIELD_ID_ATOM_ERROR_COUNT = 18; +const int FIELD_ID_BINDER_CALL_FAIL_COUNT = 19; +const int FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND = 20; +const int FIELD_ID_PULLER_NOT_FOUND = 21; +const int FIELD_ID_PULL_TIMEOUT_METADATA = 22; +const int FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS = 1; +const int FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS = 2; + // for AtomMetricStats proto const int FIELD_ID_ATOM_METRIC_STATS = 17; const int FIELD_ID_METRIC_ID = 1; @@ -345,30 +362,7 @@ void writeFieldValueTreeToStreamHelper(int tagId, const std::vector<FieldValue>& protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value); break; case STRING: { - bool isBytesField = false; - // Bytes field is logged via string format in log_msg format. So here we check - // if this string field is a byte field. - std::map<int, std::vector<int>>::const_iterator itr; - if (depth == 0 && (itr = AtomsInfo::kBytesFieldAtoms.find(tagId)) != - AtomsInfo::kBytesFieldAtoms.end()) { - const std::vector<int>& bytesFields = itr->second; - for (int bytesField : bytesFields) { - if (bytesField == fieldNum) { - // This is a bytes field - isBytesField = true; - break; - } - } - } - if (isBytesField) { - if (dim.mValue.str_value.length() > 0) { - protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum, - (const char*)dim.mValue.str_value.c_str(), - dim.mValue.str_value.length()); - } - } else { - protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value); - } + protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value); break; } case STORAGE: @@ -412,6 +406,23 @@ void writeFieldValueTreeToStream(int tagId, const std::vector<FieldValue>& value protoOutput->end(atomToken); } +void writeStateToProto(const FieldValue& state, util::ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_ATOM_ID, state.mField.getTag()); + + switch (state.mValue.getType()) { + case INT: + protoOutput->write(FIELD_TYPE_INT32 | STATE_VALUE_CONTENTS_VALUE, + state.mValue.int_value); + break; + case LONG: + protoOutput->write(FIELD_TYPE_INT64 | STATE_VALUE_CONTENTS_GROUP_ID, + state.mValue.long_value); + break; + default: + break; + } +} + int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit) { int64_t bucketSizeMillis = TimeUnitToBucketSizeInMillis(unit); if (bucketSizeMillis > 1000 && bucketSizeMillis < 5 * 60 * 1000LL && uid != AID_SHELL && @@ -441,6 +452,8 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) { return 12 * 60 * 60 * 1000LL; case ONE_DAY: return 24 * 60 * 60 * 1000LL; + case ONE_WEEK: + return 7 * 24 * 60 * 60 * 1000LL; case CTS: return 1000; case TIME_UNIT_UNSPECIFIED: @@ -474,16 +487,29 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats> (long long)pair.second.pullExceedMaxDelay); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_FAILED, (long long)pair.second.pullFailed); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_STATS_COMPANION_FAILED, - (long long)pair.second.statsCompanionPullFailed); - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED, - (long long)pair.second.statsCompanionPullBinderTransactionFailed); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_EMPTY_DATA, (long long)pair.second.emptyData); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_REGISTERED_COUNT, (long long) pair.second.registeredCount); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT, (long long) pair.second.unregisteredCount); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BINDER_CALL_FAIL_COUNT, + (long long)pair.second.binderCallFailCount); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND, + (long long)pair.second.pullUidProviderNotFound); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULLER_NOT_FOUND, + (long long)pair.second.pullerNotFound); + for (const auto& pullTimeoutMetadata : pair.second.pullTimeoutMetadata) { + uint64_t timeoutMetadataToken = protoOutput->start(FIELD_TYPE_MESSAGE | + FIELD_ID_PULL_TIMEOUT_METADATA | + FIELD_COUNT_REPEATED); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS, + pullTimeoutMetadata.pullTimeoutUptimeMillis); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS, + pullTimeoutMetadata.pullTimeoutElapsedMillis); + protoOutput->end(timeoutMetadataToken); + } protoOutput->end(token); } @@ -529,6 +555,10 @@ int64_t getElapsedRealtimeMillis() { return ::android::elapsedRealtime(); } +int64_t getSystemUptimeMillis() { + return ::android::uptimeMillis(); +} + int64_t getWallClockNs() { return time(nullptr) * NS_PER_SEC; } @@ -541,14 +571,13 @@ int64_t getWallClockMillis() { return time(nullptr) * MS_PER_SEC; } -int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs) { - if (AtomsInfo::kTruncatingTimestampAtomBlackList.find(atomId) != - AtomsInfo::kTruncatingTimestampAtomBlackList.end() || - (atomId >= StatsdStats::kTimestampTruncationStartTag && - atomId <= StatsdStats::kTimestampTruncationEndTag)) { - return timestampNs / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60); +int64_t truncateTimestampIfNecessary(const LogEvent& event) { + if (event.shouldTruncateTimestamp() || + (event.GetTagId() >= StatsdStats::kTimestampTruncationStartTag && + event.GetTagId() <= StatsdStats::kTimestampTruncationEndTag)) { + return event.GetElapsedTimestampNs() / NS_PER_SEC / (5 * 60) * NS_PER_SEC * (5 * 60); } else { - return timestampNs; + return event.GetElapsedTimestampNs(); } } @@ -560,6 +589,21 @@ int64_t MillisToNano(const int64_t millis) { return millis * 1000000; } +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) { + shared_ptr<IStatsCompanionService> scs = getStatsCompanionService(); + if (scs == nullptr) { + return false; + } + + bool success; + ::ndk::ScopedAStatus status = scs->checkPermission(string(permission), pid, uid, &success); + if (!status.isOk()) { + return false; + } + + return success; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index a28165d09cdf..eb65dc6979c5 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -17,28 +17,33 @@ #pragma once #include <android/util/ProtoOutputStream.h> + #include "FieldValue.h" #include "HashableDimensionKey.h" -#include "atoms_info.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" + +using android::util::ProtoOutputStream; namespace android { namespace os { namespace statsd { void writeFieldValueTreeToStream(int tagId, const std::vector<FieldValue>& values, - util::ProtoOutputStream* protoOutput); + ProtoOutputStream* protoOutput); void writeDimensionToProto(const HashableDimensionKey& dimension, std::set<string> *str_set, - util::ProtoOutputStream* protoOutput); + ProtoOutputStream* protoOutput); void writeDimensionLeafNodesToProto(const HashableDimensionKey& dimension, const int dimensionLeafFieldId, std::set<string> *str_set, - util::ProtoOutputStream* protoOutput); + ProtoOutputStream* protoOutput); void writeDimensionPathToProto(const std::vector<Matcher>& fieldMatchers, - util::ProtoOutputStream* protoOutput); + ProtoOutputStream* protoOutput); + +void writeStateToProto(const FieldValue& state, ProtoOutputStream* protoOutput); // Convert the TimeUnit enum to the bucket size in millis with a guardrail on // bucket size. @@ -56,6 +61,9 @@ int64_t getElapsedRealtimeMillis(); // Gets the elapsed timestamp in seconds. int64_t getElapsedRealtimeSec(); +// Gets the system uptime in millis. +int64_t getSystemUptimeMillis(); + // Gets the wall clock timestamp in ns. int64_t getWallClockNs(); @@ -71,14 +79,14 @@ int64_t MillisToNano(const int64_t millis); // Helper function to write PulledAtomStats to ProtoOutputStream void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair, - util::ProtoOutputStream* protoOutput); + ProtoOutputStream* protoOutput); // Helper function to write AtomMetricStats to ProtoOutputStream void writeAtomMetricStatsToStream(const std::pair<int64_t, StatsdStats::AtomMetricStats> &pair, - util::ProtoOutputStream *protoOutput); + ProtoOutputStream *protoOutput); template<class T> -bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { +bool parseProtoOutputStream(ProtoOutputStream& protoOutput, T* message) { std::string pbBytes; sp<android::util::ProtoReader> reader = protoOutput.data(); while (reader->readBuffer() != NULL) { @@ -89,14 +97,21 @@ bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); } -// Checks the blacklist of atoms as well as the blacklisted range of 300,000 - 304,999. +// Checks the truncate timestamp annotation as well as the blacklisted range of 300,000 - 304,999. // Returns the truncated timestamp to the nearest 5 minutes if needed. -int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs); +int64_t truncateTimestampIfNecessary(const LogEvent& event); + +// Checks permission for given pid and uid. +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid); inline bool isVendorPulledAtom(int atomId) { return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag; } +inline bool isPulledAtom(int atomId) { + return atomId >= StatsdStats::kPullAtomStartTag && atomId < StatsdStats::kVendorAtomStartTag; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/statscompanion_util.cpp b/cmds/statsd/src/statscompanion_util.cpp index d338827774f9..ce07ec0ea884 100644 --- a/cmds/statsd/src/statscompanion_util.cpp +++ b/cmds/statsd/src/statscompanion_util.cpp @@ -18,26 +18,16 @@ #include "Log.h" #include "statscompanion_util.h" +#include <android/binder_auto_utils.h> +#include <android/binder_manager.h> namespace android { namespace os { namespace statsd { -sp <IStatsCompanionService> getStatsCompanionService() { - sp<IStatsCompanionService> statsCompanion = nullptr; - // Get statscompanion service from service manager - static const sp <IServiceManager> sm(defaultServiceManager()); - if (statsCompanion == nullptr) { - if (sm != nullptr) { - const String16 name("statscompanion"); - statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name)); - if (statsCompanion == nullptr) { - ALOGW("statscompanion service unavailable!"); - return nullptr; - } - } - } - return statsCompanion; +shared_ptr<IStatsCompanionService> getStatsCompanionService() { + ::ndk::SpAIBinder binder(AServiceManager_getService("statscompanion")); + return IStatsCompanionService::fromBinder(binder); } } // namespace statsd diff --git a/cmds/statsd/src/statscompanion_util.h b/cmds/statsd/src/statscompanion_util.h index dc4f28361214..e20c40bba104 100644 --- a/cmds/statsd/src/statscompanion_util.h +++ b/cmds/statsd/src/statscompanion_util.h @@ -16,14 +16,17 @@ #pragma once -#include "StatsLogProcessor.h" +#include <aidl/android/os/IStatsCompanionService.h> + +using aidl::android::os::IStatsCompanionService; +using std::shared_ptr; namespace android { namespace os { namespace statsd { /** Fetches and returns the StatsCompanionService. */ -sp<IStatsCompanionService> getStatsCompanionService(); +shared_ptr<IStatsCompanionService> getStatsCompanionService(); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 79c06b98a82d..acdffd3d4712 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -35,7 +35,7 @@ enum Position { enum TimeUnit { TIME_UNIT_UNSPECIFIED = 0; - ONE_MINUTE = 1; + ONE_MINUTE = 1; // WILL BE GUARDRAILED TO 5 MINS UNLESS UID = SHELL OR ROOT FIVE_MINUTES = 2; TEN_MINUTES = 3; THIRTY_MINUTES = 4; @@ -44,6 +44,7 @@ enum TimeUnit { SIX_HOURS = 7; TWELVE_HOURS = 8; ONE_DAY = 9; + ONE_WEEK = 10; CTS = 1000; } @@ -130,7 +131,7 @@ message SimplePredicate { UNKNOWN = 0; FALSE = 1; } - optional InitialValue initial_value = 5 [default = FALSE]; + optional InitialValue initial_value = 5 [default = UNKNOWN]; optional FieldMatcher dimensions = 6; } @@ -150,6 +151,24 @@ message Predicate { } } +message StateMap { + message StateGroup { + optional int64 group_id = 1; + + repeated int32 value = 2; + } + + repeated StateGroup group = 1; +} + +message State { + optional int64 id = 1; + + optional int32 atom_id = 2; + + optional StateMap map = 3; +} + message MetricConditionLink { optional int64 condition = 1; @@ -158,6 +177,14 @@ message MetricConditionLink { optional FieldMatcher fields_in_condition = 3; } +message MetricStateLink { + optional int32 state_atom_id = 1; + + optional FieldMatcher fields_in_what = 2; + + optional FieldMatcher fields_in_state = 3; +} + message FieldFilter { optional bool include_all = 1 [default = false]; optional FieldMatcher fields = 2; @@ -171,6 +198,9 @@ message EventMetric { optional int64 condition = 3; repeated MetricConditionLink links = 4; + + reserved 100; + reserved 101; } message CountMetric { @@ -182,11 +212,18 @@ message CountMetric { optional FieldMatcher dimensions_in_what = 4; - optional FieldMatcher dimensions_in_condition = 7; + repeated int64 slice_by_state = 8; optional TimeUnit bucket = 5; repeated MetricConditionLink links = 6; + + repeated MetricStateLink state_link = 9; + + optional FieldMatcher dimensions_in_condition = 7 [deprecated = true]; + + reserved 100; + reserved 101; } message DurationMetric { @@ -196,8 +233,12 @@ message DurationMetric { optional int64 condition = 3; + repeated int64 slice_by_state = 9; + repeated MetricConditionLink links = 4; + repeated MetricStateLink state_link = 10; + enum AggregationType { SUM = 1; @@ -207,9 +248,12 @@ message DurationMetric { optional FieldMatcher dimensions_in_what = 6; - optional FieldMatcher dimensions_in_condition = 8; - optional TimeUnit bucket = 7; + + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; + + reserved 100; + reserved 101; } message GaugeMetric { @@ -225,7 +269,7 @@ message GaugeMetric { optional FieldMatcher dimensions_in_what = 5; - optional FieldMatcher dimensions_in_condition = 8; + optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; optional TimeUnit bucket = 6; @@ -243,9 +287,12 @@ message GaugeMetric { optional int64 max_num_gauge_atoms_per_bucket = 11 [default = 10]; - optional int32 max_pull_delay_sec = 13 [default = 10]; + optional int32 max_pull_delay_sec = 13 [default = 30]; optional bool split_bucket_for_app_upgrade = 14 [default = true]; + + reserved 100; + reserved 101; } message ValueMetric { @@ -259,12 +306,14 @@ message ValueMetric { optional FieldMatcher dimensions_in_what = 5; - optional FieldMatcher dimensions_in_condition = 9; + repeated int64 slice_by_state = 18; optional TimeUnit bucket = 6; repeated MetricConditionLink links = 7; + repeated MetricStateLink state_link = 19; + enum AggregationType { SUM = 1; MIN = 2; @@ -291,9 +340,14 @@ message ValueMetric { optional bool skip_zero_diff_output = 14 [default = true]; - optional int32 max_pull_delay_sec = 16 [default = 10]; + optional int32 max_pull_delay_sec = 16 [default = 30]; optional bool split_bucket_for_app_upgrade = 17 [default = true]; + + optional FieldMatcher dimensions_in_condition = 9 [deprecated = true]; + + reserved 100; + reserved 101; } message Alert { @@ -393,6 +447,12 @@ message MetricActivation { repeated EventActivation event_activation = 2; } +message PullAtomPackages { + optional int32 atom_id = 1; + + repeated string packages = 2; +} + message StatsdConfig { optional int64 id = 1; @@ -438,6 +498,14 @@ message StatsdConfig { optional bool persist_locally = 20 [default = false]; + repeated State state = 21; + + repeated string default_pull_packages = 22; + + repeated PullAtomPackages pull_atom_packages = 23; + + repeated int32 whitelisted_atom_ids = 24; + // Field number 1000 is reserved for later use. reserved 1000; } diff --git a/cmds/statsd/src/statsd_metadata.proto b/cmds/statsd/src/statsd_metadata.proto new file mode 100644 index 000000000000..200b392f7542 --- /dev/null +++ b/cmds/statsd/src/statsd_metadata.proto @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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"; + +package android.os.statsd.metadata; + +message ConfigKey { + optional int64 config_id = 1; + optional int32 uid = 2; +} + +message Field { + optional int32 tag = 1; + optional int32 field = 2; +} + +message FieldValue { + optional Field field = 1; + oneof value { + int32 value_int = 2; + int64 value_long = 3; + float value_float = 4; + double value_double = 5; + string value_str = 6; + bytes value_storage = 7; + } +} + +message MetricDimensionKey { + repeated FieldValue dimension_key_in_what = 1; + repeated FieldValue state_values_key = 2; +} + +message AlertDimensionKeyedData { + // The earliest time the alert can be fired again in wall clock time. + optional int32 last_refractory_ends_sec = 1; + optional MetricDimensionKey dimension_key = 2; +} + +message AlertMetadata { + optional int64 alert_id = 1; + repeated AlertDimensionKeyedData alert_dim_keyed_data = 2; +} + +// All metadata for a config in statsd +message StatsMetadata { + optional ConfigKey config_key = 1; + repeated AlertMetadata alert_metadata = 2; +} + +message StatsMetadataList { + repeated StatsMetadata stats_metadata = 1; +}
\ No newline at end of file diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 507297c6c401..dcfdfe3aae53 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -44,7 +44,7 @@ using std::map; #define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" // Magic word at the start of the train info file, change this if changing the file format -const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff; +const uint32_t TRAIN_INFO_FILE_MAGIC = 0xfb7447bf; // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; @@ -75,6 +75,29 @@ string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_ (long long)id); } +static string findTrainInfoFileNameLocked(const string& trainName) { + unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", TRAIN_INFO_DIR); + return ""; + } + dirent* de; + while ((de = readdir(dir.get()))) { + char* fileName = de->d_name; + if (fileName[0] == '.') continue; + + size_t fileNameLength = strlen(fileName); + if (fileNameLength >= trainName.length()) { + if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(), + trainName.length())) { + return string(fileName); + } + } + } + + return ""; +} + // Returns array of int64_t which contains timestamp in seconds, uid, // configID and whether the file is a local history file. static void parseFileName(char* name, FileName* output) { @@ -123,20 +146,25 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte close(fd); } -bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<int64_t>& experimentIds) { +bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); - deleteAllFiles(TRAIN_INFO_DIR); + if (trainInfo.trainName.empty()) { + return false; + } + deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str()); + + std::string fileName = + StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(), + trainInfo.trainName.c_str()); - int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { - VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH); + VLOG("Attempt to access %s but failed", fileName.c_str()); return false; } size_t result; - // Write the magic word result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { @@ -146,8 +174,8 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write the train version - const size_t trainVersionCodeByteCount = sizeof(trainVersionCode); - result = write(fd, &trainVersionCode, trainVersionCodeByteCount); + const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode); + result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); if (result != trainVersionCodeByteCount) { VLOG("Failed to wrtie train version code"); close(fd); @@ -155,7 +183,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write # of bytes in trainName to file - const size_t trainNameSize = trainName.size(); + const size_t trainNameSize = trainInfo.trainName.size(); const size_t trainNameSizeByteCount = sizeof(trainNameSize); result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); if (result != trainNameSizeByteCount) { @@ -165,7 +193,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write trainName to file - result = write(fd, trainName.c_str(), trainNameSize); + result = write(fd, trainInfo.trainName.c_str(), trainNameSize); if (result != trainNameSize) { VLOG("Failed to write train name"); close(fd); @@ -173,8 +201,8 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write status to file - const size_t statusByteCount = sizeof(status); - result = write(fd, (uint8_t*)&status, statusByteCount); + const size_t statusByteCount = sizeof(trainInfo.status); + result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount); if (result != statusByteCount) { VLOG("Failed to write status"); close(fd); @@ -182,7 +210,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write experiment id count to file. - const size_t experimentIdsCount = experimentIds.size(); + const size_t experimentIdsCount = trainInfo.experimentIds.size(); const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); if (result != experimentIdsCountByteCount) { @@ -193,7 +221,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& // Write experimentIds to file for (size_t i = 0; i < experimentIdsCount; i++) { - const int64_t experimentId = experimentIds[i]; + const int64_t experimentId = trainInfo.experimentIds[i]; const size_t experimentIdByteCount = sizeof(experimentId); result = write(fd, &experimentId, experimentIdByteCount); if (result == experimentIdByteCount) { @@ -205,23 +233,47 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } } - result = fchown(fd, AID_STATSD, AID_STATSD); - if (result) { - VLOG("Failed to chown train info file to statsd"); - close(fd); - return false; + // Write bools to file + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires staging"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write rollback enabled"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires log latency monitor"); + close(fd); + return false; } close(fd); return true; } -bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { +bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); + return readTrainInfoLocked(trainName, trainInfo); +} - int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC); +bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) { + trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true); + string fileName = findTrainInfoFileNameLocked(trainName); + if (fileName.empty()) { + return false; + } + int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName.c_str()).c_str(), O_RDONLY | O_CLOEXEC); if (fd == -1) { - VLOG("Failed to open train-info.bin"); + VLOG("Failed to open %s", fileName.c_str()); return false; } @@ -297,6 +349,29 @@ bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { trainInfo.experimentIds.push_back(experimentId); } + // Read bools + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = read(fd, &trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires staging from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires rollback enabled from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires low latency monitor from train info file"); + close(fd); + return false; + } + // Expect to be at EOF. char c; result = read(fd, &c, 1); @@ -311,6 +386,32 @@ bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { return true; } +vector<InstallTrainInfo> StorageManager::readAllTrainInfo() { + std::lock_guard<std::mutex> lock(sTrainInfoMutex); + vector<InstallTrainInfo> trainInfoList; + unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + return trainInfoList; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + + InstallTrainInfo trainInfo; + bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo); + if (!readSuccess) { + continue; + } + trainInfoList.push_back(trainInfo); + } + return trainInfoList; +} + void StorageManager::deleteFile(const char* file) { if (remove(file) != 0) { VLOG("Attempt to delete %s but is not found", file); @@ -574,7 +675,7 @@ void StorageManager::sortFiles(vector<FileInfo>* fileNames) { }); } -void StorageManager::trimToFit(const char* path) { +void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) { unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); if (dir == NULL) { VLOG("Path %s does not exist", path); @@ -589,9 +690,16 @@ void StorageManager::trimToFit(const char* path) { if (name[0] == '.') continue; FileName output; - parseFileName(name, &output); + string file_name; + if (parseTimestampOnly) { + file_name = StringPrintf("%s/%s", path, name); + output.mTimestampSec = StrToInt64(strtok(name, "_")); + output.mIsHistory = false; + } else { + parseFileName(name, &output); + file_name = output.getFullFileName(path); + } if (output.mTimestampSec == -1) continue; - string file_name = output.getFullFileName(path); // Check for timestamp and delete if it's too old. long fileAge = nowSec - output.mTimestampSec; diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index 69b41c2cb974..d59046dfbb99 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -52,13 +52,22 @@ public: /** * Writes train info. */ - static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<int64_t>& experimentIds); + static bool writeTrainInfo(const InstallTrainInfo& trainInfo); /** * Reads train info. */ - static bool readTrainInfo(InstallTrainInfo& trainInfo); + static bool readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads train info assuming lock is obtained. + */ + static bool readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads all train info and returns a vector of train info. + */ + static vector<InstallTrainInfo> readAllTrainInfo(); /** * Reads the file content to the buffer. @@ -124,7 +133,7 @@ public: * Trims files in the provided directory to limit the total size, number of * files, accumulation of outdated files. */ - static void trimToFit(const char* dir); + static void trimToFit(const char* dir, bool parseTimestampOnly = false); /** * Returns true if there already exists identical configuration on device. diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp index ba5e66761528..1d77513d9d33 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -21,10 +21,8 @@ #include "packages/UidMap.h" #include "stats_log_util.h" -#include <android/os/IIncidentManager.h> -#include <android/os/IncidentReportArgs.h> #include <android/util/ProtoOutputStream.h> -#include <binder/IServiceManager.h> +#include <incident/incident_report.h> #include <vector> @@ -51,7 +49,6 @@ const int FIELD_ID_TRIGGER_DETAILS = 4; const int FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC = 1; const int FIELD_ID_METRIC_VALUE_METRIC_ID = 1; const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT = 2; -const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_CONDITION = 3; const int FIELD_ID_METRIC_VALUE_VALUE = 4; const int FIELD_ID_PACKAGE_INFO = 3; @@ -83,10 +80,8 @@ void getProtoData(const int64_t& rule_id, int64_t metricId, const MetricDimensio writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), nullptr, &headerProto); headerProto.end(dimToken); + // deprecated field // optional DimensionsValue dimension_in_condition = 3; - dimToken = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_METRIC_VALUE_DIMENSION_IN_CONDITION); - writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), nullptr, &headerProto); - headerProto.end(dimToken); // optional int64 value = 4; headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_VALUE, (long long)metricValue); @@ -105,13 +100,6 @@ void getProtoData(const int64_t& rule_id, int64_t metricId, const MetricDimensio } } - for (const auto& dim : dimensionKey.getDimensionKeyInCondition().getValues()) { - int uid = getUidIfExists(dim); - if (uid > 2000) { - uids.insert(uid); - } - } - if (!uids.empty()) { uint64_t token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_PACKAGE_INFO); UidMap::getInstance()->writeUidMapSnapshot(getElapsedRealtimeNs(), true, true, uids, @@ -142,44 +130,38 @@ bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int return false; } - IncidentReportArgs incidentReport; + AIncidentReportArgs* args = AIncidentReportArgs_init(); vector<uint8_t> protoData; getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey, config.alert_description(), &protoData); - incidentReport.addHeader(protoData); + AIncidentReportArgs_addHeader(args, protoData.data(), protoData.size()); for (int i = 0; i < config.section_size(); i++) { - incidentReport.addSection(config.section(i)); + AIncidentReportArgs_addSection(args, config.section(i)); } uint8_t dest; switch (config.dest()) { case IncidentdDetails_Destination_AUTOMATIC: - dest = android::os::PRIVACY_POLICY_AUTOMATIC; + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; break; case IncidentdDetails_Destination_EXPLICIT: - dest = android::os::PRIVACY_POLICY_EXPLICIT; + dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT; break; default: - dest = android::os::PRIVACY_POLICY_AUTOMATIC; + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; } - incidentReport.setPrivacyPolicy(dest); + AIncidentReportArgs_setPrivacyPolicy(args, dest); - incidentReport.setReceiverPkg(config.receiver_pkg()); + AIncidentReportArgs_setReceiverPackage(args, config.receiver_pkg().c_str()); - incidentReport.setReceiverCls(config.receiver_cls()); + AIncidentReportArgs_setReceiverClass(args, config.receiver_cls().c_str()); - sp<IIncidentManager> service = interface_cast<IIncidentManager>( - defaultServiceManager()->getService(android::String16("incident"))); - if (service == nullptr) { - ALOGW("Failed to fetch incident service."); - return false; - } - VLOG("Calling incidentd %p", service.get()); - binder::Status s = service->reportIncident(incidentReport); - VLOG("Report incident status: %s", s.toString8().string()); - return s.isOk(); + int err = AIncidentReportArgs_takeReport(args); + AIncidentReportArgs_delete(args); + + return err == NO_ERROR; } } // namespace statsd diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index d4f44780b0bc..c915ef3bf069 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -19,7 +19,6 @@ #include "SubscriberReporter.h" -using android::IBinder; using std::lock_guard; namespace android { @@ -28,18 +27,66 @@ namespace statsd { using std::vector; +struct BroadcastSubscriberDeathCookie { + BroadcastSubscriberDeathCookie(const ConfigKey& configKey, int64_t subscriberId, + const shared_ptr<IPendingIntentRef>& pir): + mConfigKey(configKey), + mSubscriberId(subscriberId), + mPir(pir) {} + + ConfigKey mConfigKey; + int64_t mSubscriberId; + shared_ptr<IPendingIntentRef> mPir; +}; + +void SubscriberReporter::broadcastSubscriberDied(void* cookie) { + auto cookie_ = static_cast<BroadcastSubscriberDeathCookie*>(cookie); + ConfigKey& configKey = cookie_->mConfigKey; + int64_t subscriberId = cookie_->mSubscriberId; + shared_ptr<IPendingIntentRef>& pir = cookie_->mPir; + + SubscriberReporter& thiz = getInstance(); + + // Erase the mapping from a (config_key, subscriberId) to a pir if the + // mapping exists. + lock_guard<mutex> lock(thiz.mLock); + auto subscriberMapIt = thiz.mIntentMap.find(configKey); + if (subscriberMapIt != thiz.mIntentMap.end()) { + auto subscriberMap = subscriberMapIt->second; + auto pirIt = subscriberMap.find(subscriberId); + if (pirIt != subscriberMap.end() && pirIt->second == pir) { + subscriberMap.erase(subscriberId); + if (subscriberMap.empty()) { + thiz.mIntentMap.erase(configKey); + } + } + } + + // The death recipient corresponding to this specific pir can never be + // triggered again, so free up resources. + delete cookie_; +} + +SubscriberReporter::SubscriberReporter() : + mBroadcastSubscriberDeathRecipient(AIBinder_DeathRecipient_new(broadcastSubscriberDied)) { +} + void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId, - const sp<IBinder>& intentSender) { + const shared_ptr<IPendingIntentRef>& pir) { VLOG("SubscriberReporter::setBroadcastSubscriber called."); - lock_guard<std::mutex> lock(mLock); - mIntentMap[configKey][subscriberId] = intentSender; + { + lock_guard<mutex> lock(mLock); + mIntentMap[configKey][subscriberId] = pir; + } + AIBinder_linkToDeath(pir->asBinder().get(), mBroadcastSubscriberDeathRecipient.get(), + new BroadcastSubscriberDeathCookie(configKey, subscriberId, pir)); } void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId) { VLOG("SubscriberReporter::unsetBroadcastSubscriber called."); - lock_guard<std::mutex> lock(mLock); + lock_guard<mutex> lock(mLock); auto subscriberMapIt = mIntentMap.find(configKey); if (subscriberMapIt != mIntentMap.end()) { subscriberMapIt->second.erase(subscriberId); @@ -49,12 +96,6 @@ void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, } } -void SubscriberReporter::removeConfig(const ConfigKey& configKey) { - VLOG("SubscriberReporter::removeConfig called."); - lock_guard<std::mutex> lock(mLock); - mIntentMap.erase(configKey); -} - void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, const Subscription& subscription, const MetricDimensionKey& dimKey) const { @@ -67,7 +108,7 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, // config id - the name of this config (for this particular uid) VLOG("SubscriberReporter::alertBroadcastSubscriber called."); - lock_guard<std::mutex> lock(mLock); + lock_guard<mutex> lock(mLock); if (!subscription.has_broadcast_subscriber_details() || !subscription.broadcast_subscriber_details().has_subscriber_id()) { @@ -76,10 +117,10 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, } int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id(); - vector<String16> cookies; + vector<string> cookies; cookies.reserve(subscription.broadcast_subscriber_details().cookie_size()); for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) { - cookies.push_back(String16(cookie.c_str())); + cookies.push_back(cookie); } auto it1 = mIntentMap.find(configKey); @@ -96,79 +137,33 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey); } -void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender, +void SubscriberReporter::sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir, const ConfigKey& configKey, const Subscription& subscription, - const vector<String16>& cookies, + const vector<string>& cookies, const MetricDimensionKey& dimKey) const { VLOG("SubscriberReporter::sendBroadcastLocked called."); - if (mStatsCompanionService == nullptr) { - ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService."); - return; - } - mStatsCompanionService->sendSubscriberBroadcast( - intentSender, + pir->sendSubscriberBroadcast( configKey.GetUid(), configKey.GetId(), subscription.id(), subscription.rule_id(), cookies, - getStatsDimensionsValue(dimKey.getDimensionKeyInWhat())); + dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel()); } -void getStatsDimensionsValueHelper(const vector<FieldValue>& dims, size_t* index, int depth, - int prefix, vector<StatsDimensionsValue>* output) { - size_t count = dims.size(); - while (*index < count) { - const auto& dim = dims[*index]; - const int valueDepth = dim.mField.getDepth(); - const int valuePrefix = dim.mField.getPrefix(depth); - if (valueDepth > 2) { - ALOGE("Depth > 2 not supported"); - return; - } - if (depth == valueDepth && valuePrefix == prefix) { - switch (dim.mValue.getType()) { - case INT: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.int_value)); - break; - case LONG: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.long_value)); - break; - case FLOAT: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.float_value)); - break; - case STRING: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - String16(dim.mValue.str_value.c_str()))); - break; - default: - break; - } - (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { - vector<StatsDimensionsValue> childOutput; - getStatsDimensionsValueHelper(dims, index, depth + 1, dim.mField.getPrefix(depth + 1), - &childOutput); - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), childOutput)); - } else { - return; - } +shared_ptr<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId) { + lock_guard<mutex> lock(mLock); + auto subscriberMapIt = mIntentMap.find(configKey); + if (subscriberMapIt == mIntentMap.end()) { + return nullptr; } -} - -StatsDimensionsValue SubscriberReporter::getStatsDimensionsValue(const HashableDimensionKey& dim) { - if (dim.getValues().size() == 0) { - return StatsDimensionsValue(); + auto pirMapIt = subscriberMapIt->second.find(subscriberId); + if (pirMapIt == subscriberMapIt->second.end()) { + return nullptr; } - - vector<StatsDimensionsValue> fields; - size_t index = 0; - getStatsDimensionsValueHelper(dim.getValues(), &index, 0, 0, &fields); - return StatsDimensionsValue(dim.getValues()[0].mField.getTag(), fields); + return pirMapIt->second; } } // namespace statsd diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 2a7f771a0ba4..4fe428198e71 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -16,18 +16,25 @@ #pragma once -#include <android/os/IStatsCompanionService.h> +#include <aidl/android/os/IPendingIntentRef.h> #include <utils/RefBase.h> +#include <utils/String16.h> #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription -#include "android/os/StatsDimensionsValue.h" #include "HashableDimensionKey.h" #include <mutex> #include <unordered_map> #include <vector> +using aidl::android::os::IPendingIntentRef; +using std::mutex; +using std::shared_ptr; +using std::string; +using std::unordered_map; +using std::vector; + namespace android { namespace os { namespace statsd { @@ -47,32 +54,17 @@ public: void operator=(SubscriberReporter const&) = delete; /** - * Tells SubscriberReporter what IStatsCompanionService to use. - * May be nullptr, but SubscriberReporter will not send broadcasts for any calls - * to alertBroadcastSubscriber that occur while nullptr. - */ - void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) { - std::lock_guard<std::mutex> lock(mLock); - sp<IStatsCompanionService> tmpForLock = mStatsCompanionService; - mStatsCompanionService = statsCompanionService; - } - - /** * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair. - * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder). */ void setBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId, - const sp<android::IBinder>& intentSender); + const shared_ptr<IPendingIntentRef>& pir); /** * Erases any intentSender information from the given (configKey, subscriberId) pair. */ void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); - /** Remove all information stored by SubscriberReporter about the given config. */ - void removeConfig(const ConfigKey& configKey); - /** * Sends a broadcast via the intentSender previously stored for the * given (configKey, subscriberId) pair by setBroadcastSubscriber. @@ -82,29 +74,34 @@ public: const Subscription& subscription, const MetricDimensionKey& dimKey) const; - static StatsDimensionsValue getStatsDimensionsValue(const HashableDimensionKey& dim); + shared_ptr<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey, + int64_t subscriberId); private: - SubscriberReporter() {}; + SubscriberReporter(); - mutable std::mutex mLock; + mutable mutex mLock; - /** Binder interface for communicating with StatsCompanionService. */ - sp<IStatsCompanionService> mStatsCompanionService = nullptr; - - /** Maps <ConfigKey, SubscriberId> -> IBinder (which represents an IIntentSender). */ - std::unordered_map<ConfigKey, - std::unordered_map<int64_t, sp<android::IBinder>>> mIntentMap; + /** Maps <ConfigKey, SubscriberId> -> IPendingIntentRef (which represents a PendingIntent). */ + unordered_map<ConfigKey, unordered_map<int64_t, shared_ptr<IPendingIntentRef>>> mIntentMap; /** * Sends a broadcast via the given intentSender (using mStatsCompanionService), along * with the information in the other parameters. */ - void sendBroadcastLocked(const sp<android::IBinder>& intentSender, + void sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir, const ConfigKey& configKey, const Subscription& subscription, - const std::vector<String16>& cookies, + const vector<string>& cookies, const MetricDimensionKey& dimKey) const; + + ::ndk::ScopedAIBinder_DeathRecipient mBroadcastSubscriberDeathRecipient; + + /** + * Death recipient callback that is called when a broadcast subscriber dies. + * The cookie is a pointer to a BroadcastSubscriberDeathCookie. + */ + static void broadcastSubscriberDied(void* cookie); }; } // namespace statsd diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.cpp b/cmds/statsd/src/utils/MultiConditionTrigger.cpp new file mode 100644 index 000000000000..43a69337f368 --- /dev/null +++ b/cmds/statsd/src/utils/MultiConditionTrigger.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 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. + */ +#define DEBUG false // STOPSHIP if true + +#include "MultiConditionTrigger.h" + +#include <thread> + +using namespace std; + +namespace android { +namespace os { +namespace statsd { + +MultiConditionTrigger::MultiConditionTrigger(const set<string>& conditionNames, + function<void()> trigger) + : mRemainingConditionNames(conditionNames), + mTrigger(trigger), + mCompleted(mRemainingConditionNames.empty()) { + if (mCompleted) { + thread executorThread([this] { mTrigger(); }); + executorThread.detach(); + } +} + +void MultiConditionTrigger::markComplete(const string& conditionName) { + bool doTrigger = false; + { + lock_guard<mutex> lg(mMutex); + if (mCompleted) { + return; + } + mRemainingConditionNames.erase(conditionName); + mCompleted = mRemainingConditionNames.empty(); + doTrigger = mCompleted; + } + if (doTrigger) { + std::thread executorThread([this] { mTrigger(); }); + executorThread.detach(); + } +} +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.h b/cmds/statsd/src/utils/MultiConditionTrigger.h new file mode 100644 index 000000000000..51f6029915be --- /dev/null +++ b/cmds/statsd/src/utils/MultiConditionTrigger.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <gtest/gtest_prod.h> + +#include <mutex> +#include <set> + +namespace android { +namespace os { +namespace statsd { + +/** + * This class provides a utility to wait for a set of named conditions to occur. + * + * It will execute the trigger runnable in a detached thread once all conditions have been marked + * true. + */ +class MultiConditionTrigger { +public: + explicit MultiConditionTrigger(const std::set<std::string>& conditionNames, + std::function<void()> trigger); + + MultiConditionTrigger(const MultiConditionTrigger&) = delete; + MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete; + + // Mark a specific condition as true. If this condition has called markComplete already or if + // the event was not specified in the constructor, the function is a no-op. + void markComplete(const std::string& eventName); + +private: + mutable std::mutex mMutex; + std::set<std::string> mRemainingConditionNames; + std::function<void()> mTrigger; + bool mCompleted; + + FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName); +}; +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc deleted file mode 100644 index b87a48309e18..000000000000 --- a/cmds/statsd/statsd.rc +++ /dev/null @@ -1,24 +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. - -service statsd /system/bin/statsd - class main - socket statsdw dgram+passcred 0222 statsd statsd - user statsd - group statsd log - writepid /dev/cpuset/system-background/tasks - -on property:ro.statsd.enable=false - stop statsd - diff --git a/cmds/statsd/statsd_test.xml b/cmds/statsd/statsd_test.xml new file mode 100644 index 000000000000..8f9bb1cb6b2a --- /dev/null +++ b/cmds/statsd/statsd_test.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> +<configuration description="Runs statsd_test."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + <option name="test-suite-tag" value="mts" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="push" value="statsd_test->/data/local/tmp/statsd_test" /> + <option name="append-bitness" value="true" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="statsd_test" /> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.os.statsd" /> + </object> +</configuration> diff --git a/cmds/statsd/tests/AlarmMonitor_test.cpp b/cmds/statsd/tests/AlarmMonitor_test.cpp index 1fccb35eccb5..1dc9795dcf16 100644 --- a/cmds/statsd/tests/AlarmMonitor_test.cpp +++ b/cmds/statsd/tests/AlarmMonitor_test.cpp @@ -17,14 +17,16 @@ #include <gtest/gtest.h> using namespace android::os::statsd; +using std::shared_ptr; #ifdef __ANDROID__ TEST(AlarmMonitor, popSoonerThan) { std::string emptyMetricId; std::string emptyDimensionId; unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> set; - AlarmMonitor am(2, [](const sp<IStatsCompanionService>&, int64_t){}, - [](const sp<IStatsCompanionService>&){}); + AlarmMonitor am(2, + [](const shared_ptr<IStatsCompanionService>&, int64_t){}, + [](const shared_ptr<IStatsCompanionService>&){}); set = am.popSoonerThan(5); EXPECT_TRUE(set.empty()); @@ -47,19 +49,19 @@ TEST(AlarmMonitor, popSoonerThan) { EXPECT_TRUE(set.empty()); set = am.popSoonerThan(30); - EXPECT_EQ(4u, set.size()); + ASSERT_EQ(4u, set.size()); EXPECT_EQ(1u, set.count(a)); EXPECT_EQ(1u, set.count(b)); EXPECT_EQ(1u, set.count(c)); EXPECT_EQ(1u, set.count(d)); set = am.popSoonerThan(60); - EXPECT_EQ(2u, set.size()); + ASSERT_EQ(2u, set.size()); EXPECT_EQ(1u, set.count(e)); EXPECT_EQ(1u, set.count(f)); set = am.popSoonerThan(80); - EXPECT_EQ(0u, set.size()); + ASSERT_EQ(0u, set.size()); } #else diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index f1cad92c336b..a21eb9b9147f 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -14,13 +14,16 @@ * limitations under the License. */ #include <gtest/gtest.h> + #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "matchers/matcher_util.h" #include "src/logd/LogEvent.h" +#include "stats_event.h" #include "stats_log_util.h" #include "stats_util.h" #include "subscriber/SubscriberReporter.h" +#include "tests/statsd_test_util.h" #ifdef __ANDROID__ @@ -30,6 +33,40 @@ namespace android { namespace os { namespace statsd { +// These constants must be kept in sync with those in StatsDimensionsValue.java. +const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + +namespace { +void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const vector<int>& attributionUids, const vector<string>& attributionTags, + const string& name) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const vector<int>& attributionUids, const vector<string>& attributionTags, + const int32_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeInt32(statsEvent, value); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} +} // anonymous namespace + TEST(AtomMatcherTest, TestFieldTranslation) { FieldMatcher matcher1; matcher1.set_field(10); @@ -43,7 +80,7 @@ TEST(AtomMatcherTest, TestFieldTranslation) { vector<Matcher> output; translateFieldMatcher(matcher1, &output); - EXPECT_EQ((size_t)1, output.size()); + ASSERT_EQ((size_t)1, output.size()); const auto& matcher12 = output[0]; EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag()); @@ -64,7 +101,7 @@ TEST(AtomMatcherTest, TestFieldTranslation_ALL) { vector<Matcher> output; translateFieldMatcher(matcher1, &output); - EXPECT_EQ((size_t)1, output.size()); + ASSERT_EQ((size_t)1, output.size()); const auto& matcher12 = output[0]; EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag()); @@ -88,31 +125,16 @@ TEST(AtomMatcherTest, TestFilter_ALL) { vector<Matcher> matchers; translateFieldMatcher(matcher1, &matchers); - AttributionNodeInternal attribution_node1; - attribution_node1.set_uid(1111); - attribution_node1.set_tag("location1"); - - AttributionNodeInternal attribution_node2; - attribution_node2.set_uid(2222); - attribution_node2.set_tag("location2"); - - AttributionNodeInternal attribution_node3; - attribution_node3.set_uid(3333); - attribution_node3.set_tag("location3"); - std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2, - attribution_node3}; - - // Set up the event - LogEvent event(10, 12345); - event.write(attribution_nodes); - event.write("some value"); - // Convert to a LogEvent - event.init(); + std::vector<int> attributionUids = {1111, 2222, 3333}; + std::vector<string> attributionTags = {"location1", "location2", "location3"}; + + LogEvent event(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event, 10 /*atomId*/, 1012345, attributionUids, attributionTags, "some value"); HashableDimensionKey output; filterValues(matchers, event.getValues(), &output); - EXPECT_EQ((size_t)7, output.getValues().size()); + ASSERT_EQ((size_t)7, output.getValues().size()); EXPECT_EQ((int32_t)0x02010101, output.getValues()[0].mField.getField()); EXPECT_EQ((int32_t)1111, output.getValues()[0].mValue.int_value); EXPECT_EQ((int32_t)0x02010102, output.getValues()[1].mField.getField()); @@ -174,26 +196,11 @@ TEST(AtomMatcherTest, TestSubDimension) { } TEST(AtomMatcherTest, TestMetric2ConditionLink) { - AttributionNodeInternal attribution_node1; - attribution_node1.set_uid(1111); - attribution_node1.set_tag("location1"); - - AttributionNodeInternal attribution_node2; - attribution_node2.set_uid(2222); - attribution_node2.set_tag("location2"); - - AttributionNodeInternal attribution_node3; - attribution_node3.set_uid(3333); - attribution_node3.set_tag("location3"); - std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2, - attribution_node3}; - - // Set up the event - LogEvent event(10, 12345); - event.write(attribution_nodes); - event.write("some value"); - // Convert to a LogEvent - event.init(); + std::vector<int> attributionUids = {1111, 2222, 3333}; + std::vector<string> attributionTags = {"location1", "location2", "location3"}; + + LogEvent event(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event, 10 /*atomId*/, 12345, attributionUids, attributionTags, "some value"); FieldMatcher whatMatcher; whatMatcher.set_field(10); @@ -217,12 +224,12 @@ TEST(AtomMatcherTest, TestMetric2ConditionLink) { translateFieldMatcher(whatMatcher, &link.metricFields); translateFieldMatcher(conditionMatcher, &link.conditionFields); - EXPECT_EQ((size_t)1, link.metricFields.size()); + ASSERT_EQ((size_t)1, link.metricFields.size()); EXPECT_EQ((int32_t)0x02010001, link.metricFields[0].mMatcher.getField()); EXPECT_EQ((int32_t)0xff7f007f, link.metricFields[0].mMask); EXPECT_EQ((int32_t)10, link.metricFields[0].mMatcher.getTag()); - EXPECT_EQ((size_t)1, link.conditionFields.size()); + ASSERT_EQ((size_t)1, link.conditionFields.size()); EXPECT_EQ((int32_t)0x02028002, link.conditionFields[0].mMatcher.getField()); EXPECT_EQ((int32_t)0xff7f807f, link.conditionFields[0].mMask); EXPECT_EQ((int32_t)27, link.conditionFields[0].mMatcher.getTag()); @@ -263,15 +270,15 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { } DimensionsValue result; - EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); EXPECT_EQ(10, result.field()); EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case()); - EXPECT_EQ(3, result.value_tuple().dimensions_value_size()); + ASSERT_EQ(3, result.value_tuple().dimensions_value_size()); const auto& dim1 = result.value_tuple().dimensions_value(0); EXPECT_EQ(2, dim1.field()); - EXPECT_EQ(2, dim1.value_tuple().dimensions_value_size()); + ASSERT_EQ(2, dim1.value_tuple().dimensions_value_size()); const auto& dim11 = dim1.value_tuple().dimensions_value(0); EXPECT_EQ(1, dim11.field()); @@ -284,38 +291,81 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { const auto& dim3 = result.value_tuple().dimensions_value(2); EXPECT_EQ(6, dim3.field()); - EXPECT_EQ(1, dim3.value_tuple().dimensions_value_size()); + ASSERT_EQ(1, dim3.value_tuple().dimensions_value_size()); const auto& dim31 = dim3.value_tuple().dimensions_value(0); EXPECT_EQ(2, dim31.field()); } } -TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { - HashableDimensionKey dim; +void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel, + int32_t nodeDepthInAttributionChain, + int32_t uid, string tag) { + EXPECT_EQ(attributionNodeParcel.field, nodeDepthInAttributionChain /*position at depth 1*/); + ASSERT_EQ(attributionNodeParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionNodeParcel.tupleValue.size(), 2); + + StatsDimensionsValueParcel uidParcel = attributionNodeParcel.tupleValue[0]; + EXPECT_EQ(uidParcel.field, 1 /*position at depth 2*/); + EXPECT_EQ(uidParcel.valueType, STATS_DIMENSIONS_VALUE_INT_TYPE); + EXPECT_EQ(uidParcel.intValue, uid); + + StatsDimensionsValueParcel tagParcel = attributionNodeParcel.tupleValue[1]; + EXPECT_EQ(tagParcel.field, 2 /*position at depth 2*/); + EXPECT_EQ(tagParcel.valueType, STATS_DIMENSIONS_VALUE_STRING_TYPE); + EXPECT_EQ(tagParcel.stringValue, tag); +} +// Test conversion of a HashableDimensionKey into a StatsDimensionValueParcel +TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { + int atomId = 10; + // First four fields form an attribution chain int pos1[] = {1, 1, 1}; int pos2[] = {1, 1, 2}; - int pos3[] = {1, 1, 3}; - int pos4[] = {2, 0, 0}; - - Field field1(10, pos1, 2); - Field field2(10, pos2, 2); - Field field3(10, pos3, 2); - Field field4(10, pos4, 0); - - Value value1((int32_t)10025); - Value value2("tag"); - Value value3((int32_t)987654); - Value value4((int32_t)99999); - - dim.addValue(FieldValue(field1, value1)); - dim.addValue(FieldValue(field2, value2)); - dim.addValue(FieldValue(field3, value3)); - dim.addValue(FieldValue(field4, value4)); - - SubscriberReporter::getStatsDimensionsValue(dim); - // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't - // have any read api. + int pos3[] = {1, 2, 1}; + int pos4[] = {1, 2, 2}; + int pos5[] = {2, 1, 1}; + + Field field1(atomId, pos1, /*depth=*/2); + Field field2(atomId, pos2, /*depth=*/2); + Field field3(atomId, pos3, /*depth=*/2); + Field field4(atomId, pos4, /*depth=*/2); + Field field5(atomId, pos5, /*depth=*/0); + + Value value1((int32_t)1); + Value value2("string2"); + Value value3((int32_t)3); + Value value4("string4"); + Value value5((float)5.0); + + HashableDimensionKey dimensionKey; + dimensionKey.addValue(FieldValue(field1, value1)); + dimensionKey.addValue(FieldValue(field2, value2)); + dimensionKey.addValue(FieldValue(field3, value3)); + dimensionKey.addValue(FieldValue(field4, value4)); + dimensionKey.addValue(FieldValue(field5, value5)); + + StatsDimensionsValueParcel rootParcel = dimensionKey.toStatsDimensionsValueParcel(); + EXPECT_EQ(rootParcel.field, atomId); + ASSERT_EQ(rootParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(rootParcel.tupleValue.size(), 2); + + // Check that attribution chain is populated correctly + StatsDimensionsValueParcel attributionChainParcel = rootParcel.tupleValue[0]; + EXPECT_EQ(attributionChainParcel.field, 1 /*position at depth 0*/); + ASSERT_EQ(attributionChainParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionChainParcel.tupleValue.size(), 2); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[0], + /*nodeDepthInAttributionChain=*/1, + value1.int_value, value2.str_value); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[1], + /*nodeDepthInAttributionChain=*/2, + value3.int_value, value4.str_value); + + // Check that the float is populated correctly + StatsDimensionsValueParcel floatParcel = rootParcel.tupleValue[1]; + EXPECT_EQ(floatParcel.field, 2 /*position at depth 0*/); + EXPECT_EQ(floatParcel.valueType, STATS_DIMENSIONS_VALUE_FLOAT_TYPE); + EXPECT_EQ(floatParcel.floatValue, value5.float_value); } TEST(AtomMatcherTest, TestWriteDimensionToProto) { @@ -354,14 +404,14 @@ TEST(AtomMatcherTest, TestWriteDimensionToProto) { } DimensionsValue result; - EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); EXPECT_EQ(10, result.field()); EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, result.value_case()); - EXPECT_EQ(2, result.value_tuple().dimensions_value_size()); + ASSERT_EQ(2, result.value_tuple().dimensions_value_size()); const auto& dim1 = result.value_tuple().dimensions_value(0); EXPECT_EQ(DimensionsValue::ValueCase::kValueTuple, dim1.value_case()); - EXPECT_EQ(3, dim1.value_tuple().dimensions_value_size()); + ASSERT_EQ(3, dim1.value_tuple().dimensions_value_size()); const auto& dim11 = dim1.value_tuple().dimensions_value(0); EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim11.value_case()); @@ -416,8 +466,8 @@ TEST(AtomMatcherTest, TestWriteDimensionLeafNodesToProto) { } DimensionsValueTuple result; - EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); - EXPECT_EQ(4, result.dimensions_value_size()); + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + ASSERT_EQ(4, result.dimensions_value_size()); const auto& dim1 = result.dimensions_value(0); EXPECT_EQ(DimensionsValue::ValueCase::kValueInt, dim1.value_case()); @@ -437,22 +487,11 @@ TEST(AtomMatcherTest, TestWriteDimensionLeafNodesToProto) { } TEST(AtomMatcherTest, TestWriteAtomToProto) { - AttributionNodeInternal attribution_node1; - attribution_node1.set_uid(1111); - attribution_node1.set_tag("location1"); - - AttributionNodeInternal attribution_node2; - attribution_node2.set_uid(2222); - attribution_node2.set_tag("location2"); + std::vector<int> attributionUids = {1111, 2222}; + std::vector<string> attributionTags = {"location1", "location2"}; - std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2}; - - // Set up the event - LogEvent event(4, 12345); - event.write(attribution_nodes); - event.write((int32_t)999); - // Convert to a LogEvent - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event, 4 /*atomId*/, 12345, attributionUids, attributionTags, 999); android::util::ProtoOutputStream protoOutput; writeFieldValueTreeToStream(event.GetTagId(), event.getValues(), &protoOutput); @@ -469,10 +508,10 @@ TEST(AtomMatcherTest, TestWriteAtomToProto) { } Atom result; - EXPECT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); EXPECT_EQ(Atom::PushedCase::kBleScanResultReceived, result.pushed_case()); const auto& atom = result.ble_scan_result_received(); - EXPECT_EQ(2, atom.attribution_node_size()); + ASSERT_EQ(2, atom.attribution_node_size()); EXPECT_EQ(1111, atom.attribution_node(0).uid()); EXPECT_EQ("location1", atom.attribution_node(0).tag()); EXPECT_EQ(2222, atom.attribution_node(1).uid()); @@ -480,6 +519,137 @@ TEST(AtomMatcherTest, TestWriteAtomToProto) { EXPECT_EQ(999, atom.num_results()); } +/* + * Test two Matchers is not a subset of one Matcher. + * Test one Matcher is subset of two Matchers. + */ +TEST(AtomMatcherTest, TestSubsetDimensions1) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + child->set_position(Position::ALL); + child->add_child()->set_field(1); + child->add_child()->set_field(2); + + vector<Matcher> matchers1; + translateFieldMatcher(matcher1, &matchers1); + ASSERT_EQ(2, matchers1.size()); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(10); + + child = matcher2.add_child(); + child->set_field(1); + child->set_position(Position::ALL); + child->add_child()->set_field(1); + + vector<Matcher> matchers2; + translateFieldMatcher(matcher2, &matchers2); + ASSERT_EQ(1, matchers2.size()); + + EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); + EXPECT_TRUE(subsetDimensions(matchers2, matchers1)); +} +/* + * Test not a subset with one matching Matcher, one non-matching Matcher. + */ +TEST(AtomMatcherTest, TestSubsetDimensions2) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + + child = matcher1.add_child(); + child->set_field(2); + + vector<Matcher> matchers1; + translateFieldMatcher(matcher1, &matchers1); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(10); + + child = matcher2.add_child(); + child->set_field(1); + + child = matcher2.add_child(); + child->set_field(3); + + vector<Matcher> matchers2; + translateFieldMatcher(matcher2, &matchers2); + + EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); +} + +/* + * Test not a subset if parent field is not equal. + */ +TEST(AtomMatcherTest, TestSubsetDimensions3) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + + vector<Matcher> matchers1; + translateFieldMatcher(matcher1, &matchers1); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(5); + + child = matcher2.add_child(); + child->set_field(1); + + vector<Matcher> matchers2; + translateFieldMatcher(matcher2, &matchers2); + + EXPECT_FALSE(subsetDimensions(matchers1, matchers2)); +} + +/* + * Test is subset with two matching Matchers. + */ +TEST(AtomMatcherTest, TestSubsetDimensions4) { + // Initialize first set of matchers + FieldMatcher matcher1; + matcher1.set_field(10); + + FieldMatcher* child = matcher1.add_child(); + child->set_field(1); + + child = matcher1.add_child(); + child->set_field(2); + + vector<Matcher> matchers1; + translateFieldMatcher(matcher1, &matchers1); + + // Initialize second set of matchers + FieldMatcher matcher2; + matcher2.set_field(10); + + child = matcher2.add_child(); + child->set_field(1); + + child = matcher2.add_child(); + child->set_field(2); + + child = matcher2.add_child(); + child->set_field(3); + + vector<Matcher> matchers2; + translateFieldMatcher(matcher2, &matchers2); + + EXPECT_TRUE(subsetDimensions(matchers1, matchers2)); + EXPECT_FALSE(subsetDimensions(matchers2, matchers1)); +} } // namespace statsd } // namespace os diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp new file mode 100644 index 000000000000..29adcd08a7b8 --- /dev/null +++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 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 "src/HashableDimensionKey.h" + +#include <gtest/gtest.h> + +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "statsd_test_util.h" + +#ifdef __ANDROID__ + +using android::util::ProtoReader; + +namespace android { +namespace os { +namespace statsd { + +/** + * Test that #containsLinkedStateValues returns false when the whatKey is + * smaller than the primaryKey. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) { + std::vector<Metric2State> mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY; + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, + UID_PROCESS_STATE_ATOM_ID)); +} + +/** + * Test that #containsLinkedStateValues returns false when the linked values + * are not equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector<Metric2State> mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + int32_t uid2 = 1001; + HashableDimensionKey whatKey; + getOverlayKey(uid2, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns false when there is no link + * between the key values. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + std::vector<Metric2State> mMetric2StateLinks; + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +/** + * Test that #containsLinkedStateValues returns true when the key values are + * linked and equal. + */ +TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) { + int stateAtomId = UID_PROCESS_STATE_ATOM_ID; + + FieldMatcher whatMatcher; + whatMatcher.set_field(util::OVERLAY_STATE_CHANGED); + FieldMatcher* child11 = whatMatcher.add_child(); + child11->set_field(1); + + FieldMatcher stateMatcher; + stateMatcher.set_field(stateAtomId); + FieldMatcher* child21 = stateMatcher.add_child(); + child21->set_field(1); + + std::vector<Metric2State> mMetric2StateLinks; + Metric2State ms; + ms.stateAtomId = stateAtomId; + translateFieldMatcher(whatMatcher, &ms.metricFields); + translateFieldMatcher(stateMatcher, &ms.stateFields); + mMetric2StateLinks.push_back(ms); + + int32_t uid1 = 1000; + HashableDimensionKey whatKey; + getOverlayKey(uid1, "package", &whatKey); + HashableDimensionKey primaryKey; + getUidProcessKey(uid1, &primaryKey); + + EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp index 2cbcf28ef7d6..6264c075426a 100644 --- a/cmds/statsd/tests/LogEntryMatcher_test.cpp +++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp @@ -12,20 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <gtest/gtest.h> +#include <stdio.h> + +#include "annotations.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "matchers/matcher_util.h" +#include "stats_event.h" #include "stats_log_util.h" #include "stats_util.h" - -#include <gtest/gtest.h> - -#include <stdio.h> +#include "statsd_test_util.h" using namespace android::os::statsd; using std::unordered_map; using std::vector; const int32_t TAG_ID = 123; +const int32_t TAG_ID_2 = 28; // hardcoded tag of atom with uid field const int FIELD_ID_1 = 1; const int FIELD_ID_2 = 2; const int FIELD_ID_3 = 2; @@ -35,6 +38,77 @@ const int ATTRIBUTION_TAG_FIELD_ID = 2; #ifdef __ANDROID__ + +namespace { + +void makeIntLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const int32_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + AStatsEvent_writeInt32(statsEvent, value); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const float floatValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + AStatsEvent_writeFloat(statsEvent, floatValue); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const string& name) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + AStatsEvent_writeString(statsEvent, name.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeIntWithBoolAnnotationLogEvent(LogEvent* logEvent, const int32_t atomId, + const int32_t field, const uint8_t annotationId, + const bool annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_writeInt32(statsEvent, field); + AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const vector<int>& attributionUids, + const vector<string>& attributionTags, const string& name) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, + const bool bool1, const bool bool2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); + + AStatsEvent_writeBool(statsEvent, bool1); + AStatsEvent_writeBool(statsEvent, bool2); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +} // anonymous namespace + TEST(AtomMatcherTest, TestSimpleMatcher) { UidMap uidMap; @@ -43,9 +117,8 @@ TEST(AtomMatcherTest, TestSimpleMatcher) { auto simpleMatcher = matcher.mutable_simple_atom_matcher(); simpleMatcher->set_atom_id(TAG_ID); - LogEvent event(TAG_ID, 0); - EXPECT_TRUE(event.write(11)); - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + makeIntLogEvent(&event, TAG_ID, 0, 11); // Test EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); @@ -57,26 +130,12 @@ TEST(AtomMatcherTest, TestSimpleMatcher) { TEST(AtomMatcherTest, TestAttributionMatcher) { UidMap uidMap; - AttributionNodeInternal attribution_node1; - attribution_node1.set_uid(1111); - attribution_node1.set_tag("location1"); - - AttributionNodeInternal attribution_node2; - attribution_node2.set_uid(2222); - attribution_node2.set_tag("location2"); + std::vector<int> attributionUids = {1111, 2222, 3333}; + std::vector<string> attributionTags = {"location1", "location2", "location3"}; - AttributionNodeInternal attribution_node3; - attribution_node3.set_uid(3333); - attribution_node3.set_tag("location3"); - std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2, - attribution_node3}; - - // Set up the event - LogEvent event(TAG_ID, 0); - event.write(attribution_nodes); - event.write("some value"); - // Convert to a LogEvent - event.init(); + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); // Set up the matcher AtomMatcher matcher; @@ -88,8 +147,9 @@ TEST(AtomMatcherTest, TestAttributionMatcher) { attributionMatcher->set_field(FIELD_ID_1); attributionMatcher->set_position(Position::FIRST); attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( - ATTRIBUTION_TAG_FIELD_ID); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("tag"); + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "tag"); auto fieldMatcher = simpleMatcher->add_field_value_matcher(); fieldMatcher->set_field(FIELD_ID_2); @@ -97,40 +157,40 @@ TEST(AtomMatcherTest, TestAttributionMatcher) { // Tag not matched. EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location3"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location1"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); // Match last node. attributionMatcher->set_position(Position::LAST); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location3"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); // Match any node. attributionMatcher->set_position(Position::ANY); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location1"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location2"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location3"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location4"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location4"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); // Attribution match but primitive field not match. attributionMatcher->set_position(Position::ANY); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("location2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "location2"); fieldMatcher->set_eq_string("wrong value"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); @@ -139,8 +199,9 @@ TEST(AtomMatcherTest, TestAttributionMatcher) { // Uid match. attributionMatcher->set_position(Position::ANY); attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_field( - ATTRIBUTION_UID_FIELD_ID); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("pkg0"); + ATTRIBUTION_UID_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); uidMap.updateMap( @@ -153,148 +214,148 @@ TEST(AtomMatcherTest, TestAttributionMatcher) { android::String16(""), android::String16("")}); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); attributionMatcher->set_position(Position::FIRST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); attributionMatcher->set_position(Position::LAST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); // Uid + tag. attributionMatcher->set_position(Position::ANY); attributionMatcher->mutable_matches_tuple()->add_field_value_matcher()->set_field( - ATTRIBUTION_TAG_FIELD_ID); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg0"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location2"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg2"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); + ATTRIBUTION_TAG_FIELD_ID); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); attributionMatcher->set_position(Position::FIRST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg0"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location2"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg2"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); attributionMatcher->set_position(Position::LAST); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg0"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg1"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location2"); - EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg2"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location3"); - EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0) - ->set_eq_string("pkg3"); - attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1) - ->set_eq_string("location1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg0"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg1"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string( + "pkg3"); + attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string( + "location1"); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); } -TEST(AtomMatcherTest, TestNeqAnyStringMatcher) { +TEST(AtomMatcherTest, TestUidFieldMatcher) { UidMap uidMap; uidMap.updateMap( 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, @@ -305,30 +366,48 @@ TEST(AtomMatcherTest, TestNeqAnyStringMatcher) { {android::String16(""), android::String16(""), android::String16(""), android::String16(""), android::String16("")}); - AttributionNodeInternal attribution_node1; - attribution_node1.set_uid(1111); - attribution_node1.set_tag("location1"); + // Set up matcher + AtomMatcher matcher; + auto simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + simpleMatcher->add_field_value_matcher()->set_field(1); + simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("pkg0"); - AttributionNodeInternal attribution_node2; - attribution_node2.set_uid(2222); - attribution_node2.set_tag("location2"); + // Make event without is_uid annotation. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeIntLogEvent(&event1, TAG_ID, 0, 1111); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); - AttributionNodeInternal attribution_node3; - attribution_node3.set_uid(3333); - attribution_node3.set_tag("location3"); + // Make event with is_uid annotation. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true); - AttributionNodeInternal attribution_node4; - attribution_node4.set_uid(1066); - attribution_node4.set_tag("location3"); - std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2, - attribution_node3, attribution_node4}; + // Event has is_uid annotation, so mapping from uid to package name occurs. + simpleMatcher->set_atom_id(TAG_ID_2); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); + + // Event has is_uid annotation, but uid maps to different package name. + simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); +} + +TEST(AtomMatcherTest, TestNeqAnyStringMatcher) { + UidMap uidMap; + uidMap.updateMap( + 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, + {android::String16("v1"), android::String16("v1"), android::String16("v2"), + android::String16("v1"), android::String16("v2")}, + {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), + android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, + {android::String16(""), android::String16(""), android::String16(""), + android::String16(""), android::String16("")}); + + std::vector<int> attributionUids = {1111, 2222, 3333, 1066}; + std::vector<string> attributionTags = {"location1", "location2", "location3", "location3"}; // Set up the event - LogEvent event(TAG_ID, 0); - event.write(attribution_nodes); - event.write("some value"); - // Convert to a LogEvent - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); // Set up the matcher AtomMatcher matcher; @@ -384,30 +463,12 @@ TEST(AtomMatcherTest, TestEqAnyStringMatcher) { {android::String16(""), android::String16(""), android::String16(""), android::String16(""), android::String16("")}); - AttributionNodeInternal attribution_node1; - attribution_node1.set_uid(1067); - attribution_node1.set_tag("location1"); - - AttributionNodeInternal attribution_node2; - attribution_node2.set_uid(2222); - attribution_node2.set_tag("location2"); - - AttributionNodeInternal attribution_node3; - attribution_node3.set_uid(3333); - attribution_node3.set_tag("location3"); - - AttributionNodeInternal attribution_node4; - attribution_node4.set_uid(1066); - attribution_node4.set_tag("location3"); - std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2, - attribution_node3, attribution_node4}; + std::vector<int> attributionUids = {1067, 2222, 3333, 1066}; + std::vector<string> attributionTags = {"location1", "location2", "location3", "location3"}; // Set up the event - LogEvent event(TAG_ID, 0); - event.write(attribution_nodes); - event.write("some value"); - // Convert to a LogEvent - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value"); // Set up the matcher AtomMatcher matcher; @@ -467,11 +528,8 @@ TEST(AtomMatcherTest, TestBoolMatcher) { keyValue2->set_field(FIELD_ID_2); // Set up the event - LogEvent event(TAG_ID, 0); - EXPECT_TRUE(event.write(true)); - EXPECT_TRUE(event.write(false)); - // Convert to a LogEvent - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + makeBoolLogEvent(&event, TAG_ID, 0, true, false); // Test keyValue1->set_eq_bool(true); @@ -502,10 +560,8 @@ TEST(AtomMatcherTest, TestStringMatcher) { keyValue->set_eq_string("some value"); // Set up the event - LogEvent event(TAG_ID, 0); - event.write("some value"); - // Convert to a LogEvent - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + makeStringLogEvent(&event, TAG_ID, 0, "some value"); // Test EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); @@ -523,12 +579,8 @@ TEST(AtomMatcherTest, TestMultiFieldsMatcher) { keyValue2->set_field(FIELD_ID_2); // Set up the event - LogEvent event(TAG_ID, 0); - event.write(2); - event.write(3); - - // Convert to a LogEvent - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event, TAG_ID, 0, 2, 3); // Test keyValue1->set_eq_int(2); @@ -555,9 +607,8 @@ TEST(AtomMatcherTest, TestIntComparisonMatcher) { keyValue->set_field(FIELD_ID_1); // Set up the event - LogEvent event(TAG_ID, 0); - event.write(11); - event.init(); + LogEvent event(/*uid=*/0, /*pid=*/0); + makeIntLogEvent(&event, TAG_ID, 0, 11); // Test @@ -612,26 +663,22 @@ TEST(AtomMatcherTest, TestFloatComparisonMatcher) { auto keyValue = simpleMatcher->add_field_value_matcher(); keyValue->set_field(FIELD_ID_1); - LogEvent event1(TAG_ID, 0); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event1, TAG_ID, 0, 10.1f); keyValue->set_lt_float(10.0); - event1.write(10.1f); - event1.init(); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); - LogEvent event2(TAG_ID, 0); - event2.write(9.9f); - event2.init(); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event2, TAG_ID, 0, 9.9f); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); - LogEvent event3(TAG_ID, 0); - event3.write(10.1f); - event3.init(); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event3, TAG_ID, 0, 10.1f); keyValue->set_gt_float(10.0); EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3)); - LogEvent event4(TAG_ID, 0); - event4.write(9.9f); - event4.init(); + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeFloatLogEvent(&event4, TAG_ID, 0, 9.9f); EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4)); } diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index 504ee22f72e4..5c170c07eb7d 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -13,10 +13,13 @@ // limitations under the License. #include "src/logd/LogEvent.h" + #include <gtest/gtest.h> -#include <log/log_event_list.h> + #include "frameworks/base/cmds/statsd/src/atoms.pb.h" #include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h" +#include "log/log_event_list.h" +#include "stats_event.h" #ifdef __ANDROID__ @@ -25,641 +28,340 @@ namespace os { namespace statsd { using std::string; +using std::vector; using util::ProtoOutputStream; using util::ProtoReader; -TEST(LogEventTest, TestLogParsing) { - LogEvent event1(1, 2000); - - std::vector<AttributionNodeInternal> nodes; - - AttributionNodeInternal node1; - node1.set_uid(1000); - node1.set_tag("tag1"); - nodes.push_back(node1); - - AttributionNodeInternal node2; - node2.set_uid(2000); - node2.set_tag("tag2"); - nodes.push_back(node2); - - event1.write(nodes); - event1.write("hello"); - event1.write((int32_t)10); - event1.write((int64_t)20); - event1.write((float)1.1); - event1.init(); - - const auto& items = event1.getValues(); - EXPECT_EQ((size_t)8, items.size()); - EXPECT_EQ(1, event1.GetTagId()); - - const FieldValue& item0 = event1.getValues()[0]; - EXPECT_EQ(0x2010101, item0.mField.getField()); - EXPECT_EQ(Type::INT, item0.mValue.getType()); - EXPECT_EQ(1000, item0.mValue.int_value); - - const FieldValue& item1 = event1.getValues()[1]; - EXPECT_EQ(0x2010182, item1.mField.getField()); - EXPECT_EQ(Type::STRING, item1.mValue.getType()); - EXPECT_EQ("tag1", item1.mValue.str_value); - - const FieldValue& item2 = event1.getValues()[2]; - EXPECT_EQ(0x2018201, item2.mField.getField()); - EXPECT_EQ(Type::INT, item2.mValue.getType()); - EXPECT_EQ(2000, item2.mValue.int_value); - - const FieldValue& item3 = event1.getValues()[3]; - EXPECT_EQ(0x2018282, item3.mField.getField()); - EXPECT_EQ(Type::STRING, item3.mValue.getType()); - EXPECT_EQ("tag2", item3.mValue.str_value); - - const FieldValue& item4 = event1.getValues()[4]; - EXPECT_EQ(0x20000, item4.mField.getField()); - EXPECT_EQ(Type::STRING, item4.mValue.getType()); - EXPECT_EQ("hello", item4.mValue.str_value); - - const FieldValue& item5 = event1.getValues()[5]; - EXPECT_EQ(0x30000, item5.mField.getField()); - EXPECT_EQ(Type::INT, item5.mValue.getType()); - EXPECT_EQ(10, item5.mValue.int_value); - - const FieldValue& item6 = event1.getValues()[6]; - EXPECT_EQ(0x40000, item6.mField.getField()); - EXPECT_EQ(Type::LONG, item6.mValue.getType()); - EXPECT_EQ((int64_t)20, item6.mValue.long_value); - - const FieldValue& item7 = event1.getValues()[7]; - EXPECT_EQ(0x50000, item7.mField.getField()); - EXPECT_EQ(Type::FLOAT, item7.mValue.getType()); - EXPECT_EQ((float)1.1, item7.mValue.float_value); +namespace { + +Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vector<bool>& last) { + Field f(tag, (int32_t*)pos.data(), depth); + + // For loop starts at 1 because the last field at depth 0 is not decorated. + for (int i = 1; i < depth; i++) { + if (last[i]) f.decorateLastPos(i); + } + + return f; } -TEST(LogEventTest, TestKeyValuePairsAtomParsing) { - LogEvent event1(83, 2000, 1000); - std::map<int32_t, int32_t> int_map; - std::map<int32_t, int64_t> long_map; - std::map<int32_t, std::string> string_map; - std::map<int32_t, float> float_map; - - int_map[11] = 123; - int_map[22] = 345; - - long_map[33] = 678L; - long_map[44] = 890L; - - string_map[1] = "test2"; - string_map[2] = "test1"; - - float_map[111] = 2.2f; - float_map[222] = 1.1f; - - EXPECT_TRUE(event1.writeKeyValuePairs(0, // Logging side logs 0 uid. - int_map, - long_map, - string_map, - float_map)); - event1.init(); - - EXPECT_EQ(83, event1.GetTagId()); - const auto& items = event1.getValues(); - EXPECT_EQ((size_t)17, items.size()); - - const FieldValue& item0 = event1.getValues()[0]; - EXPECT_EQ(0x10000, item0.mField.getField()); - EXPECT_EQ(Type::INT, item0.mValue.getType()); - EXPECT_EQ(1000, item0.mValue.int_value); - - const FieldValue& item1 = event1.getValues()[1]; - EXPECT_EQ(0x2010201, item1.mField.getField()); - EXPECT_EQ(Type::INT, item1.mValue.getType()); - EXPECT_EQ(11, item1.mValue.int_value); - - const FieldValue& item2 = event1.getValues()[2]; - EXPECT_EQ(0x2010282, item2.mField.getField()); - EXPECT_EQ(Type::INT, item2.mValue.getType()); - EXPECT_EQ(123, item2.mValue.int_value); - - const FieldValue& item3 = event1.getValues()[3]; - EXPECT_EQ(0x2010301, item3.mField.getField()); - EXPECT_EQ(Type::INT, item3.mValue.getType()); - EXPECT_EQ(22, item3.mValue.int_value); - - const FieldValue& item4 = event1.getValues()[4]; - EXPECT_EQ(0x2010382, item4.mField.getField()); - EXPECT_EQ(Type::INT, item4.mValue.getType()); - EXPECT_EQ(345, item4.mValue.int_value); - - const FieldValue& item5 = event1.getValues()[5]; - EXPECT_EQ(0x2010401, item5.mField.getField()); - EXPECT_EQ(Type::INT, item5.mValue.getType()); - EXPECT_EQ(33, item5.mValue.int_value); - - const FieldValue& item6 = event1.getValues()[6]; - EXPECT_EQ(0x2010483, item6.mField.getField()); - EXPECT_EQ(Type::LONG, item6.mValue.getType()); - EXPECT_EQ(678L, item6.mValue.int_value); - - const FieldValue& item7 = event1.getValues()[7]; - EXPECT_EQ(0x2010501, item7.mField.getField()); - EXPECT_EQ(Type::INT, item7.mValue.getType()); - EXPECT_EQ(44, item7.mValue.int_value); - - const FieldValue& item8 = event1.getValues()[8]; - EXPECT_EQ(0x2010583, item8.mField.getField()); - EXPECT_EQ(Type::LONG, item8.mValue.getType()); - EXPECT_EQ(890L, item8.mValue.int_value); - - const FieldValue& item9 = event1.getValues()[9]; - EXPECT_EQ(0x2010601, item9.mField.getField()); - EXPECT_EQ(Type::INT, item9.mValue.getType()); - EXPECT_EQ(1, item9.mValue.int_value); - - const FieldValue& item10 = event1.getValues()[10]; - EXPECT_EQ(0x2010684, item10.mField.getField()); - EXPECT_EQ(Type::STRING, item10.mValue.getType()); - EXPECT_EQ("test2", item10.mValue.str_value); - - const FieldValue& item11 = event1.getValues()[11]; - EXPECT_EQ(0x2010701, item11.mField.getField()); - EXPECT_EQ(Type::INT, item11.mValue.getType()); - EXPECT_EQ(2, item11.mValue.int_value); - - const FieldValue& item12 = event1.getValues()[12]; - EXPECT_EQ(0x2010784, item12.mField.getField()); - EXPECT_EQ(Type::STRING, item12.mValue.getType()); - EXPECT_EQ("test1", item12.mValue.str_value); - - const FieldValue& item13 = event1.getValues()[13]; - EXPECT_EQ(0x2010801, item13.mField.getField()); - EXPECT_EQ(Type::INT, item13.mValue.getType()); - EXPECT_EQ(111, item13.mValue.int_value); - - const FieldValue& item14 = event1.getValues()[14]; - EXPECT_EQ(0x2010885, item14.mField.getField()); - EXPECT_EQ(Type::FLOAT, item14.mValue.getType()); - EXPECT_EQ(2.2f, item14.mValue.float_value); - - const FieldValue& item15 = event1.getValues()[15]; - EXPECT_EQ(0x2018901, item15.mField.getField()); - EXPECT_EQ(Type::INT, item15.mValue.getType()); - EXPECT_EQ(222, item15.mValue.int_value); - - const FieldValue& item16 = event1.getValues()[16]; - EXPECT_EQ(0x2018985, item16.mField.getField()); - EXPECT_EQ(Type::FLOAT, item16.mValue.getType()); - EXPECT_EQ(1.1f, item16.mValue.float_value); +void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, + bool annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeInt32(statsEvent, 10); + AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + + AStatsEvent_release(statsEvent); +} + +void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, + int annotationValue) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeInt32(statsEvent, 10); + AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + + AStatsEvent_release(statsEvent); } -TEST(LogEventTest, TestLogParsing2) { - LogEvent event1(1, 2000); +} // anonymous namespace + +TEST(LogEventTest, TestPrimitiveParsing) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 10); + AStatsEvent_writeInt64(event, 0x123456789); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_writeBool(event, true); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + EXPECT_FALSE(logEvent.hasAttributionChain()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(4, values.size()); + + const FieldValue& int32Item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, int32Item.mField); + EXPECT_EQ(Type::INT, int32Item.mValue.getType()); + EXPECT_EQ(10, int32Item.mValue.int_value); + + const FieldValue& int64Item = values[1]; + expectedField = getField(100, {2, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, int64Item.mField); + EXPECT_EQ(Type::LONG, int64Item.mValue.getType()); + EXPECT_EQ(0x123456789, int64Item.mValue.long_value); + + const FieldValue& floatItem = values[2]; + expectedField = getField(100, {3, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, floatItem.mField); + EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType()); + EXPECT_EQ(2.0, floatItem.mValue.float_value); + + const FieldValue& boolItem = values[3]; + expectedField = getField(100, {4, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, boolItem.mField); + EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type + EXPECT_EQ(1, boolItem.mValue.int_value); + + AStatsEvent_release(event); +} - std::vector<AttributionNodeInternal> nodes; +TEST(LogEventTest, TestStringAndByteArrayParsing) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + string str = "test"; + AStatsEvent_writeString(event, str.c_str()); + AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length()); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + EXPECT_FALSE(logEvent.hasAttributionChain()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(2, values.size()); + + const FieldValue& stringItem = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false}); + EXPECT_EQ(expectedField, stringItem.mField); + EXPECT_EQ(Type::STRING, stringItem.mValue.getType()); + EXPECT_EQ(str, stringItem.mValue.str_value); + + const FieldValue& storageItem = values[1]; + expectedField = getField(100, {2, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, storageItem.mField); + EXPECT_EQ(Type::STORAGE, storageItem.mValue.getType()); + vector<uint8_t> expectedValue = {'t', 'e', 's', 't'}; + EXPECT_EQ(expectedValue, storageItem.mValue.storage_value); + + AStatsEvent_release(event); +} - event1.write("hello"); +TEST(LogEventTest, TestEmptyString) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + string empty = ""; + AStatsEvent_writeString(event, empty.c_str()); + AStatsEvent_build(event); - // repeated msg can be in the middle - AttributionNodeInternal node1; - node1.set_uid(1000); - node1.set_tag("tag1"); - nodes.push_back(node1); + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); - AttributionNodeInternal node2; - node2.set_uid(2000); - node2.set_tag("tag2"); - nodes.push_back(node2); - event1.write(nodes); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); - event1.write((int32_t)10); - event1.write((int64_t)20); - event1.write((float)1.1); - event1.init(); + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + EXPECT_FALSE(logEvent.hasAttributionChain()); - const auto& items = event1.getValues(); - EXPECT_EQ((size_t)8, items.size()); - EXPECT_EQ(1, event1.GetTagId()); + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(1, values.size()); - const FieldValue& item = event1.getValues()[0]; - EXPECT_EQ(0x00010000, item.mField.getField()); + const FieldValue& item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, item.mField); EXPECT_EQ(Type::STRING, item.mValue.getType()); - EXPECT_EQ("hello", item.mValue.str_value); - - const FieldValue& item0 = event1.getValues()[1]; - EXPECT_EQ(0x2020101, item0.mField.getField()); - EXPECT_EQ(Type::INT, item0.mValue.getType()); - EXPECT_EQ(1000, item0.mValue.int_value); - - const FieldValue& item1 = event1.getValues()[2]; - EXPECT_EQ(0x2020182, item1.mField.getField()); - EXPECT_EQ(Type::STRING, item1.mValue.getType()); - EXPECT_EQ("tag1", item1.mValue.str_value); - - const FieldValue& item2 = event1.getValues()[3]; - EXPECT_EQ(0x2028201, item2.mField.getField()); - EXPECT_EQ(Type::INT, item2.mValue.getType()); - EXPECT_EQ(2000, item2.mValue.int_value); - - const FieldValue& item3 = event1.getValues()[4]; - EXPECT_EQ(0x2028282, item3.mField.getField()); - EXPECT_EQ(Type::STRING, item3.mValue.getType()); - EXPECT_EQ("tag2", item3.mValue.str_value); - - const FieldValue& item5 = event1.getValues()[5]; - EXPECT_EQ(0x30000, item5.mField.getField()); - EXPECT_EQ(Type::INT, item5.mValue.getType()); - EXPECT_EQ(10, item5.mValue.int_value); - - const FieldValue& item6 = event1.getValues()[6]; - EXPECT_EQ(0x40000, item6.mField.getField()); - EXPECT_EQ(Type::LONG, item6.mValue.getType()); - EXPECT_EQ((int64_t)20, item6.mValue.long_value); - - const FieldValue& item7 = event1.getValues()[7]; - EXPECT_EQ(0x50000, item7.mField.getField()); - EXPECT_EQ(Type::FLOAT, item7.mValue.getType()); - EXPECT_EQ((float)1.1, item7.mValue.float_value); -} + EXPECT_EQ(empty, item.mValue.str_value); -TEST(LogEventTest, TestKeyValuePairsEvent) { - std::map<int32_t, int32_t> int_map; - std::map<int32_t, int64_t> long_map; - std::map<int32_t, std::string> string_map; - std::map<int32_t, float> float_map; - - int_map[11] = 123; - int_map[22] = 345; - - long_map[33] = 678L; - long_map[44] = 890L; - - string_map[1] = "test2"; - string_map[2] = "test1"; - - float_map[111] = 2.2f; - float_map[222] = 1.1f; - - LogEvent event1(83, 2000, 2001, 10001, int_map, long_map, string_map, float_map); - event1.init(); - - EXPECT_EQ(83, event1.GetTagId()); - EXPECT_EQ((int64_t)2000, event1.GetLogdTimestampNs()); - EXPECT_EQ((int64_t)2001, event1.GetElapsedTimestampNs()); - EXPECT_EQ((int64_t)10001, event1.GetUid()); - - const auto& items = event1.getValues(); - EXPECT_EQ((size_t)17, items.size()); - - const FieldValue& item0 = event1.getValues()[0]; - EXPECT_EQ(0x00010000, item0.mField.getField()); - EXPECT_EQ(Type::INT, item0.mValue.getType()); - EXPECT_EQ(10001, item0.mValue.int_value); - - const FieldValue& item1 = event1.getValues()[1]; - EXPECT_EQ(0x2020101, item1.mField.getField()); - EXPECT_EQ(Type::INT, item1.mValue.getType()); - EXPECT_EQ(11, item1.mValue.int_value); - - const FieldValue& item2 = event1.getValues()[2]; - EXPECT_EQ(0x2020182, item2.mField.getField()); - EXPECT_EQ(Type::INT, item2.mValue.getType()); - EXPECT_EQ(123, item2.mValue.int_value); - - const FieldValue& item3 = event1.getValues()[3]; - EXPECT_EQ(0x2020201, item3.mField.getField()); - EXPECT_EQ(Type::INT, item3.mValue.getType()); - EXPECT_EQ(22, item3.mValue.int_value); - - const FieldValue& item4 = event1.getValues()[4]; - EXPECT_EQ(0x2020282, item4.mField.getField()); - EXPECT_EQ(Type::INT, item4.mValue.getType()); - EXPECT_EQ(345, item4.mValue.int_value); - - const FieldValue& item5 = event1.getValues()[5]; - EXPECT_EQ(0x2020301, item5.mField.getField()); - EXPECT_EQ(Type::INT, item5.mValue.getType()); - EXPECT_EQ(33, item5.mValue.int_value); - - const FieldValue& item6 = event1.getValues()[6]; - EXPECT_EQ(0x2020383, item6.mField.getField()); - EXPECT_EQ(Type::LONG, item6.mValue.getType()); - EXPECT_EQ(678L, item6.mValue.long_value); - - const FieldValue& item7 = event1.getValues()[7]; - EXPECT_EQ(0x2020401, item7.mField.getField()); - EXPECT_EQ(Type::INT, item7.mValue.getType()); - EXPECT_EQ(44, item7.mValue.int_value); - - const FieldValue& item8 = event1.getValues()[8]; - EXPECT_EQ(0x2020483, item8.mField.getField()); - EXPECT_EQ(Type::LONG, item8.mValue.getType()); - EXPECT_EQ(890L, item8.mValue.long_value); - - const FieldValue& item9 = event1.getValues()[9]; - EXPECT_EQ(0x2020501, item9.mField.getField()); - EXPECT_EQ(Type::INT, item9.mValue.getType()); - EXPECT_EQ(1, item9.mValue.int_value); - - const FieldValue& item10 = event1.getValues()[10]; - EXPECT_EQ(0x2020584, item10.mField.getField()); - EXPECT_EQ(Type::STRING, item10.mValue.getType()); - EXPECT_EQ("test2", item10.mValue.str_value); - - const FieldValue& item11 = event1.getValues()[11]; - EXPECT_EQ(0x2020601, item11.mField.getField()); - EXPECT_EQ(Type::INT, item11.mValue.getType()); - EXPECT_EQ(2, item11.mValue.int_value); - - const FieldValue& item12 = event1.getValues()[12]; - EXPECT_EQ(0x2020684, item12.mField.getField()); - EXPECT_EQ(Type::STRING, item12.mValue.getType()); - EXPECT_EQ("test1", item12.mValue.str_value); - - const FieldValue& item13 = event1.getValues()[13]; - EXPECT_EQ(0x2020701, item13.mField.getField()); - EXPECT_EQ(Type::INT, item13.mValue.getType()); - EXPECT_EQ(111, item13.mValue.int_value); - - const FieldValue& item14 = event1.getValues()[14]; - EXPECT_EQ(0x2020785, item14.mField.getField()); - EXPECT_EQ(Type::FLOAT, item14.mValue.getType()); - EXPECT_EQ(2.2f, item14.mValue.float_value); - - const FieldValue& item15 = event1.getValues()[15]; - EXPECT_EQ(0x2028801, item15.mField.getField()); - EXPECT_EQ(Type::INT, item15.mValue.getType()); - EXPECT_EQ(222, item15.mValue.int_value); - - const FieldValue& item16 = event1.getValues()[16]; - EXPECT_EQ(0x2028885, item16.mField.getField()); - EXPECT_EQ(Type::FLOAT, item16.mValue.getType()); - EXPECT_EQ(1.1f, item16.mValue.float_value); + AStatsEvent_release(event); } -TEST(LogEventTest, TestStatsLogEventWrapperNoChain) { - Parcel parcel; - // tag id - parcel.writeInt32(1); - // elapsed realtime - parcel.writeInt64(1111L); - // wallclock time - parcel.writeInt64(2222L); - // no chain - parcel.writeInt32(0); - // 2 data - parcel.writeInt32(2); - // int 6 - parcel.writeInt32(1); - parcel.writeInt32(6); - // long 10 - parcel.writeInt32(2); - parcel.writeInt64(10); - parcel.setDataPosition(0); - - StatsLogEventWrapper statsLogEventWrapper; - EXPECT_EQ(NO_ERROR, statsLogEventWrapper.readFromParcel(&parcel)); - EXPECT_EQ(1, statsLogEventWrapper.getTagId()); - EXPECT_EQ(1111L, statsLogEventWrapper.getElapsedRealTimeNs()); - EXPECT_EQ(2222L, statsLogEventWrapper.getWallClockTimeNs()); - EXPECT_EQ(0, statsLogEventWrapper.getWorkChains().size()); - EXPECT_EQ(2, statsLogEventWrapper.getElements().size()); - EXPECT_EQ(6, statsLogEventWrapper.getElements()[0].int_value); - EXPECT_EQ(10L, statsLogEventWrapper.getElements()[1].long_value); - LogEvent event(statsLogEventWrapper, -1); - EXPECT_EQ(1, event.GetTagId()); - EXPECT_EQ(1111L, event.GetElapsedTimestampNs()); - EXPECT_EQ(2222L, event.GetLogdTimestampNs()); - EXPECT_EQ(2, event.size()); - EXPECT_EQ(6, event.getValues()[0].mValue.int_value); - EXPECT_EQ(10, event.getValues()[1].mValue.long_value); -} +TEST(LogEventTest, TestByteArrayWithNullCharacter) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + uint8_t message[] = {'\t', 'e', '\0', 's', 't'}; + AStatsEvent_writeByteArray(event, message, 5); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); -TEST(LogEventTest, TestStatsLogEventWrapperWithChain) { - Parcel parcel; - // tag id - parcel.writeInt32(1); - // elapsed realtime - parcel.writeInt64(1111L); - // wallclock time - parcel.writeInt64(2222L); - // 3 chains - parcel.writeInt32(3); - // chain1, 2 nodes (1, "tag1") (2, "tag2") - parcel.writeInt32(2); - parcel.writeInt32(1); - parcel.writeString16(String16("tag1")); - parcel.writeInt32(2); - parcel.writeString16(String16("tag2")); - // chain2, 1 node (3, "tag3") - parcel.writeInt32(1); - parcel.writeInt32(3); - parcel.writeString16(String16("tag3")); - // chain3, 2 nodes (4, "") (5, "") - parcel.writeInt32(2); - parcel.writeInt32(4); - parcel.writeString16(String16("")); - parcel.writeInt32(5); - parcel.writeString16(String16("")); - // 2 data - parcel.writeInt32(2); - // int 6 - parcel.writeInt32(1); - parcel.writeInt32(6); - // long 10 - parcel.writeInt32(2); - parcel.writeInt64(10); - parcel.setDataPosition(0); - - StatsLogEventWrapper statsLogEventWrapper; - EXPECT_EQ(NO_ERROR, statsLogEventWrapper.readFromParcel(&parcel)); - EXPECT_EQ(1, statsLogEventWrapper.getTagId()); - EXPECT_EQ(1111L, statsLogEventWrapper.getElapsedRealTimeNs()); - EXPECT_EQ(2222L, statsLogEventWrapper.getWallClockTimeNs()); - EXPECT_EQ(3, statsLogEventWrapper.getWorkChains().size()); - EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[0].uids.size()); - EXPECT_EQ(1, statsLogEventWrapper.getWorkChains()[0].uids[0]); - EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[0].uids[1]); - EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[0].tags.size()); - EXPECT_EQ("tag1", statsLogEventWrapper.getWorkChains()[0].tags[0]); - EXPECT_EQ("tag2", statsLogEventWrapper.getWorkChains()[0].tags[1]); - EXPECT_EQ(1, statsLogEventWrapper.getWorkChains()[1].uids.size()); - EXPECT_EQ(3, statsLogEventWrapper.getWorkChains()[1].uids[0]); - EXPECT_EQ(1, statsLogEventWrapper.getWorkChains()[1].tags.size()); - EXPECT_EQ("tag3", statsLogEventWrapper.getWorkChains()[1].tags[0]); - EXPECT_EQ(2, statsLogEventWrapper.getElements().size()); - EXPECT_EQ(6, statsLogEventWrapper.getElements()[0].int_value); - EXPECT_EQ(10L, statsLogEventWrapper.getElements()[1].long_value); - EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[2].uids.size()); - EXPECT_EQ(4, statsLogEventWrapper.getWorkChains()[2].uids[0]); - EXPECT_EQ(5, statsLogEventWrapper.getWorkChains()[2].uids[1]); - EXPECT_EQ(2, statsLogEventWrapper.getWorkChains()[2].tags.size()); - EXPECT_EQ("", statsLogEventWrapper.getWorkChains()[2].tags[0]); - EXPECT_EQ("", statsLogEventWrapper.getWorkChains()[2].tags[1]); - - LogEvent event(statsLogEventWrapper, -1); - EXPECT_EQ(1, event.GetTagId()); - EXPECT_EQ(1111L, event.GetElapsedTimestampNs()); - EXPECT_EQ(2222L, event.GetLogdTimestampNs()); - EXPECT_EQ(2, event.size()); - EXPECT_EQ(6, event.getValues()[0].mValue.int_value); - EXPECT_EQ(10, event.getValues()[1].mValue.long_value); - - LogEvent event1(statsLogEventWrapper, 0); - - EXPECT_EQ(1, event1.GetTagId()); - EXPECT_EQ(1111L, event1.GetElapsedTimestampNs()); - EXPECT_EQ(2222L, event1.GetLogdTimestampNs()); - EXPECT_EQ(6, event1.size()); - EXPECT_EQ(1, event1.getValues()[0].mValue.int_value); - EXPECT_EQ(0x2010101, event1.getValues()[0].mField.getField()); - EXPECT_EQ("tag1", event1.getValues()[1].mValue.str_value); - EXPECT_EQ(0x2010182, event1.getValues()[1].mField.getField()); - EXPECT_EQ(2, event1.getValues()[2].mValue.int_value); - EXPECT_EQ(0x2010201, event1.getValues()[2].mField.getField()); - EXPECT_EQ("tag2", event1.getValues()[3].mValue.str_value); - EXPECT_EQ(0x2018282, event1.getValues()[3].mField.getField()); - EXPECT_EQ(6, event1.getValues()[4].mValue.int_value); - EXPECT_EQ(0x20000, event1.getValues()[4].mField.getField()); - EXPECT_EQ(10, event1.getValues()[5].mValue.long_value); - EXPECT_EQ(0x30000, event1.getValues()[5].mField.getField()); - - LogEvent event2(statsLogEventWrapper, 1); - - EXPECT_EQ(1, event2.GetTagId()); - EXPECT_EQ(1111L, event2.GetElapsedTimestampNs()); - EXPECT_EQ(2222L, event2.GetLogdTimestampNs()); - EXPECT_EQ(4, event2.size()); - EXPECT_EQ(3, event2.getValues()[0].mValue.int_value); - EXPECT_EQ(0x2010101, event2.getValues()[0].mField.getField()); - EXPECT_EQ("tag3", event2.getValues()[1].mValue.str_value); - EXPECT_EQ(0x2018182, event2.getValues()[1].mField.getField()); - EXPECT_EQ(6, event2.getValues()[2].mValue.int_value); - EXPECT_EQ(0x20000, event2.getValues()[2].mField.getField()); - EXPECT_EQ(10, event2.getValues()[3].mValue.long_value); - EXPECT_EQ(0x30000, event2.getValues()[3].mField.getField()); - - LogEvent event3(statsLogEventWrapper, 2); - - EXPECT_EQ(1, event3.GetTagId()); - EXPECT_EQ(1111L, event3.GetElapsedTimestampNs()); - EXPECT_EQ(2222L, event3.GetLogdTimestampNs()); - EXPECT_EQ(6, event3.size()); - EXPECT_EQ(4, event3.getValues()[0].mValue.int_value); - EXPECT_EQ(0x2010101, event3.getValues()[0].mField.getField()); - EXPECT_EQ("", event3.getValues()[1].mValue.str_value); - EXPECT_EQ(0x2010182, event3.getValues()[1].mField.getField()); - EXPECT_EQ(5, event3.getValues()[2].mValue.int_value); - EXPECT_EQ(0x2010201, event3.getValues()[2].mField.getField()); - EXPECT_EQ("", event3.getValues()[3].mValue.str_value); - EXPECT_EQ(0x2018282, event3.getValues()[3].mField.getField()); - EXPECT_EQ(6, event3.getValues()[4].mValue.int_value); - EXPECT_EQ(0x20000, event3.getValues()[4].mField.getField()); - EXPECT_EQ(10, event3.getValues()[5].mValue.long_value); - EXPECT_EQ(0x30000, event3.getValues()[5].mField.getField()); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(1, values.size()); + + const FieldValue& item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false}); + EXPECT_EQ(expectedField, item.mField); + EXPECT_EQ(Type::STORAGE, item.mValue.getType()); + vector<uint8_t> expectedValue(message, message + 5); + EXPECT_EQ(expectedValue, item.mValue.storage_value); + + AStatsEvent_release(event); } -TEST(LogEventTest, TestBinaryFieldAtom) { - Atom launcherAtom; - auto launcher_event = launcherAtom.mutable_launcher_event(); - launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS); - launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW); - launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS); - - auto extension = launcher_event->mutable_extension(); - - auto src_target = extension->add_src_target(); - src_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE); - src_target->set_item(stats::launcher::LauncherTarget_Item_FOLDER_ICON); - - auto dst_target = extension->add_dst_target(); - dst_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE); - dst_target->set_item(stats::launcher::LauncherTarget_Item_WIDGET); - - string extension_str; - extension->SerializeToString(&extension_str); - - LogEvent event1(Atom::kLauncherEventFieldNumber, 1000); - - event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS); - event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW); - event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS); - event1.write(extension_str); - event1.init(); - - ProtoOutputStream proto; - event1.ToProto(proto); - - std::vector<uint8_t> outData; - outData.resize(proto.size()); - size_t pos = 0; - sp<ProtoReader> reader = proto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } +TEST(LogEventTest, TestAttributionChain) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); - std::string result_str(outData.begin(), outData.end()); - std::string orig_str; - launcherAtom.SerializeToString(&orig_str); + string tag1 = "tag1"; + string tag2 = "tag2"; - EXPECT_EQ(orig_str, result_str); + uint32_t uids[] = {1001, 1002}; + const char* tags[] = {tag1.c_str(), tag2.c_str()}; + + AStatsEvent_writeAttributionChain(event, uids, tags, 2); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(4, values.size()); // 2 per attribution node + + std::pair<int, int> attrIndexRange; + EXPECT_TRUE(logEvent.hasAttributionChain(&attrIndexRange)); + EXPECT_EQ(0, attrIndexRange.first); + EXPECT_EQ(3, attrIndexRange.second); + + // Check first attribution node + const FieldValue& uid1Item = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 2, {true, false, false}); + EXPECT_EQ(expectedField, uid1Item.mField); + EXPECT_EQ(Type::INT, uid1Item.mValue.getType()); + EXPECT_EQ(1001, uid1Item.mValue.int_value); + + const FieldValue& tag1Item = values[1]; + expectedField = getField(100, {1, 1, 2}, 2, {true, false, true}); + EXPECT_EQ(expectedField, tag1Item.mField); + EXPECT_EQ(Type::STRING, tag1Item.mValue.getType()); + EXPECT_EQ(tag1, tag1Item.mValue.str_value); + + // Check second attribution nodes + const FieldValue& uid2Item = values[2]; + expectedField = getField(100, {1, 2, 1}, 2, {true, true, false}); + EXPECT_EQ(expectedField, uid2Item.mField); + EXPECT_EQ(Type::INT, uid2Item.mValue.getType()); + EXPECT_EQ(1002, uid2Item.mValue.int_value); + + const FieldValue& tag2Item = values[3]; + expectedField = getField(100, {1, 2, 2}, 2, {true, true, true}); + EXPECT_EQ(expectedField, tag2Item.mField); + EXPECT_EQ(Type::STRING, tag2Item.mValue.getType()); + EXPECT_EQ(tag2, tag2Item.mValue.str_value); + + AStatsEvent_release(event); } -TEST(LogEventTest, TestBinaryFieldAtom_empty) { - Atom launcherAtom; - auto launcher_event = launcherAtom.mutable_launcher_event(); - launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS); - launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW); - launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS); - - // empty string. - string extension_str; - - LogEvent event1(Atom::kLauncherEventFieldNumber, 1000); - - event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS); - event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW); - event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS); - event1.write(extension_str); - event1.init(); - - ProtoOutputStream proto; - event1.ToProto(proto); - - std::vector<uint8_t> outData; - outData.resize(proto.size()); - size_t pos = 0; - sp<ProtoReader> reader = proto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } +TEST(LogEventTest, TestAnnotationIdIsUid) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true); + + const vector<FieldValue>& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_EQ(event.getUidFieldIndex(), 0); +} + +TEST(LogEventTest, TestAnnotationIdStateNested) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true); + + const vector<FieldValue>& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isNested()); +} - std::string result_str(outData.begin(), outData.end()); - std::string orig_str; - launcherAtom.SerializeToString(&orig_str); +TEST(LogEventTest, TestPrimaryFieldAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_PRIMARY_FIELD, true); - EXPECT_EQ(orig_str, result_str); + const vector<FieldValue>& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isPrimaryField()); } -TEST(LogEventTest, TestWriteExperimentIdsToProto) { - std::vector<int64_t> expIds; - expIds.push_back(5038); - std::vector<uint8_t> proto; +TEST(LogEventTest, TestExclusiveStateAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_EXCLUSIVE_STATE, true); - writeExperimentIdsToProto(expIds, &proto); + const vector<FieldValue>& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_TRUE(values[0].mAnnotations.isExclusiveState()); +} - EXPECT_EQ(proto.size(), 3); - // Proto wire format for field ID 1, varint - EXPECT_EQ(proto[0], 0x08); - // varint of 5038, 2 bytes long - EXPECT_EQ(proto[1], 0xae); - EXPECT_EQ(proto[2], 0x27); +TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) { + // Event has 10 ints and then an attribution chain + int numInts = 10; + int firstUidInChainIndex = numInts; + string tag1 = "tag1"; + string tag2 = "tag2"; + uint32_t uids[] = {1001, 1002}; + const char* tags[] = {tag1.c_str(), tag2.c_str()}; + + // Construct AStatsEvent + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 100); + for (int i = 0; i < numInts; i++) { + AStatsEvent_writeInt32(statsEvent, 10); + } + AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); + AStatsEvent_build(statsEvent); + + // Construct LogEvent + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/0, /*pid=*/0); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + AStatsEvent_release(statsEvent); + + // Check annotation + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(values.size(), numInts + 4); + EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField()); } +TEST(LogEventTest, TestResetStateAnnotation) { + int32_t resetState = 10; + LogEvent event(/*uid=*/0, /*pid=*/0); + createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_TRIGGER_STATE_RESET, resetState); + + const vector<FieldValue>& values = event.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_EQ(event.getResetState(), resetState); +} } // namespace statsd } // namespace os diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp index 71adc5789d92..6259757fe092 100644 --- a/cmds/statsd/tests/MetricsManager_test.cpp +++ b/cmds/statsd/tests/MetricsManager_test.cpp @@ -13,7 +13,15 @@ // limitations under the License. #include <gtest/gtest.h> +#include <private/android_filesystem_config.h> +#include <stdio.h> + +#include <set> +#include <unordered_map> +#include <vector> +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "metrics/metrics_test_helper.h" #include "src/condition/ConditionTracker.h" #include "src/matchers/LogMatchingTracker.h" #include "src/metrics/CountMetricProducer.h" @@ -21,25 +29,26 @@ #include "src/metrics/MetricProducer.h" #include "src/metrics/ValueMetricProducer.h" #include "src/metrics/metrics_manager_util.h" +#include "src/state/StateManager.h" #include "statsd_test_util.h" -#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" - -#include <stdio.h> -#include <set> -#include <unordered_map> -#include <vector> - -using namespace android::os::statsd; +using namespace testing; using android::sp; +using android::os::statsd::Predicate; +using std::map; using std::set; using std::unordered_map; using std::vector; -using android::os::statsd::Predicate; #ifdef __ANDROID__ +namespace android { +namespace os { +namespace statsd { + +namespace { const ConfigKey kConfigKey(0, 12345); +const long kAlertId = 3; const long timeBaseSec = 1000; @@ -85,7 +94,7 @@ StatsdConfig buildGoodConfig() { config.add_no_report_metric(3); auto alert = config.add_alert(); - alert->set_id(3); + alert->set_id(kAlertId); alert->set_metric_id(3); alert->set_num_buckets(10); alert->set_refractory_period_secs(100); @@ -218,7 +227,7 @@ StatsdConfig buildDimensionMetricsWithMultiTags() { metric->mutable_dimensions_in_what()->add_child()->set_field(1); auto alert = config.add_alert(); - alert->set_id(103); + alert->set_id(kAlertId); alert->set_metric_id(3); alert->set_num_buckets(10); alert->set_refractory_period_secs(100); @@ -267,6 +276,157 @@ StatsdConfig buildCirclePredicates() { return config; } +StatsdConfig buildConfigWithDifferentPredicates() { + StatsdConfig config; + config.set_id(12345); + + auto pulledAtomMatcher = + CreateSimpleAtomMatcher("SUBSYSTEM_SLEEP", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + auto screenOnAtomMatcher = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = screenOnAtomMatcher; + auto screenOffAtomMatcher = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = screenOffAtomMatcher; + auto batteryNoneAtomMatcher = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = batteryNoneAtomMatcher; + auto batteryUsbAtomMatcher = CreateBatteryStateUsbMatcher(); + *config.add_atom_matcher() = batteryUsbAtomMatcher; + + // Simple condition with InitialValue set to default (unknown). + auto screenOnUnknownPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnUnknownPredicate; + + // Simple condition with InitialValue set to false. + auto screenOnFalsePredicate = config.add_predicate(); + screenOnFalsePredicate->set_id(StringToId("ScreenIsOnInitialFalse")); + SimplePredicate* simpleScreenOnFalsePredicate = + screenOnFalsePredicate->mutable_simple_predicate(); + simpleScreenOnFalsePredicate->set_start(screenOnAtomMatcher.id()); + simpleScreenOnFalsePredicate->set_stop(screenOffAtomMatcher.id()); + simpleScreenOnFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); + + // Simple condition with InitialValue set to false. + auto onBatteryFalsePredicate = config.add_predicate(); + onBatteryFalsePredicate->set_id(StringToId("OnBatteryInitialFalse")); + SimplePredicate* simpleOnBatteryFalsePredicate = + onBatteryFalsePredicate->mutable_simple_predicate(); + simpleOnBatteryFalsePredicate->set_start(batteryNoneAtomMatcher.id()); + simpleOnBatteryFalsePredicate->set_stop(batteryUsbAtomMatcher.id()); + simpleOnBatteryFalsePredicate->set_initial_value(SimplePredicate_InitialValue_FALSE); + + // Combination condition with both simple condition InitialValues set to false. + auto screenOnFalseOnBatteryFalsePredicate = config.add_predicate(); + screenOnFalseOnBatteryFalsePredicate->set_id(StringToId("ScreenOnFalseOnBatteryFalse")); + screenOnFalseOnBatteryFalsePredicate->mutable_combination()->set_operation( + LogicalOperation::AND); + addPredicateToPredicateCombination(*screenOnFalsePredicate, + screenOnFalseOnBatteryFalsePredicate); + addPredicateToPredicateCombination(*onBatteryFalsePredicate, + screenOnFalseOnBatteryFalsePredicate); + + // Combination condition with one simple condition InitialValue set to unknown and one set to + // false. + auto screenOnUnknownOnBatteryFalsePredicate = config.add_predicate(); + screenOnUnknownOnBatteryFalsePredicate->set_id(StringToId("ScreenOnUnknowneOnBatteryFalse")); + screenOnUnknownOnBatteryFalsePredicate->mutable_combination()->set_operation( + LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnUnknownPredicate, + screenOnUnknownOnBatteryFalsePredicate); + addPredicateToPredicateCombination(*onBatteryFalsePredicate, + screenOnUnknownOnBatteryFalsePredicate); + + // Simple condition metric with initial value false. + ValueMetric* metric1 = config.add_value_metric(); + metric1->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialFalse")); + metric1->set_what(pulledAtomMatcher.id()); + *metric1->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric1->set_bucket(FIVE_MINUTES); + metric1->set_condition(screenOnFalsePredicate->id()); + + // Simple condition metric with initial value unknown. + ValueMetric* metric2 = config.add_value_metric(); + metric2->set_id(StringToId("ValueSubsystemSleepWhileScreenOnInitialUnknown")); + metric2->set_what(pulledAtomMatcher.id()); + *metric2->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric2->set_bucket(FIVE_MINUTES); + metric2->set_condition(screenOnUnknownPredicate.id()); + + // Combination condition metric with initial values false and false. + ValueMetric* metric3 = config.add_value_metric(); + metric3->set_id(StringToId("ValueSubsystemSleepWhileScreenOnFalseDeviceUnpluggedFalse")); + metric3->set_what(pulledAtomMatcher.id()); + *metric3->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric3->set_bucket(FIVE_MINUTES); + metric3->set_condition(screenOnFalseOnBatteryFalsePredicate->id()); + + // Combination condition metric with initial values unknown and false. + ValueMetric* metric4 = config.add_value_metric(); + metric4->set_id(StringToId("ValueSubsystemSleepWhileScreenOnUnknownDeviceUnpluggedFalse")); + metric4->set_what(pulledAtomMatcher.id()); + *metric4->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + metric4->set_bucket(FIVE_MINUTES); + metric4->set_condition(screenOnUnknownOnBatteryFalsePredicate->id()); + + return config; +} + +bool isSubset(const set<int32_t>& set1, const set<int32_t>& set2) { + return std::includes(set2.begin(), set2.end(), set1.begin(), set1.end()); +} +} // anonymous namespace + +TEST(MetricsManagerTest, TestInitialConditions) { + UidMap uidMap; + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> periodicAlarmMonitor; + StatsdConfig config = buildConfigWithDifferentPredicates(); + set<int> allTagIds; + vector<sp<LogMatchingTracker>> allAtomMatchers; + vector<sp<ConditionTracker>> allConditionTrackers; + vector<sp<MetricProducer>> allMetricProducers; + std::vector<sp<AnomalyTracker>> allAnomalyTrackers; + std::vector<sp<AlarmTracker>> allAlarmTrackers; + unordered_map<int, std::vector<int>> conditionToMetricMap; + unordered_map<int, std::vector<int>> trackerToMetricMap; + unordered_map<int, std::vector<int>> trackerToConditionMap; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; + vector<int> metricsWithActivation; + std::set<int64_t> noReportMetricIds; + + EXPECT_TRUE(initStatsdConfig( + kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, + timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, + allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, + trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation, + noReportMetricIds)); + ASSERT_EQ(4u, allMetricProducers.size()); + ASSERT_EQ(5u, allConditionTrackers.size()); + + ConditionKey queryKey; + vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated); + + allConditionTrackers[3]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache); + allConditionTrackers[4]->isConditionMet(queryKey, allConditionTrackers, false, conditionCache); + EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); + EXPECT_EQ(ConditionState::kFalse, conditionCache[1]); + EXPECT_EQ(ConditionState::kFalse, conditionCache[2]); + EXPECT_EQ(ConditionState::kFalse, conditionCache[3]); + EXPECT_EQ(ConditionState::kUnknown, conditionCache[4]); + + EXPECT_EQ(ConditionState::kFalse, allMetricProducers[0]->mCondition); + EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[1]->mCondition); + EXPECT_EQ(ConditionState::kFalse, allMetricProducers[2]->mCondition); + EXPECT_EQ(ConditionState::kUnknown, allMetricProducers[3]->mCondition); +} + TEST(MetricsManagerTest, TestGoodConfig) { UidMap uidMap; sp<StatsPullerManager> pullerManager = new StatsPullerManager(); @@ -284,6 +444,7 @@ TEST(MetricsManagerTest, TestGoodConfig) { unordered_map<int, std::vector<int>> trackerToConditionMap; unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; @@ -293,10 +454,14 @@ TEST(MetricsManagerTest, TestGoodConfig) { allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, noReportMetricIds)); - EXPECT_EQ(1u, allMetricProducers.size()); - EXPECT_EQ(1u, allAnomalyTrackers.size()); - EXPECT_EQ(1u, noReportMetricIds.size()); + alertTrackerMap, metricsWithActivation, + noReportMetricIds)); + ASSERT_EQ(1u, allMetricProducers.size()); + ASSERT_EQ(1u, allAnomalyTrackers.size()); + ASSERT_EQ(1u, noReportMetricIds.size()); + ASSERT_EQ(1u, alertTrackerMap.size()); + EXPECT_NE(alertTrackerMap.find(kAlertId), alertTrackerMap.end()); + EXPECT_EQ(alertTrackerMap.find(kAlertId)->second, 0); } TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { @@ -316,6 +481,7 @@ TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { unordered_map<int, std::vector<int>> trackerToConditionMap; unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; @@ -325,7 +491,8 @@ TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, noReportMetricIds)); + alertTrackerMap, metricsWithActivation, + noReportMetricIds)); } TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { @@ -345,6 +512,7 @@ TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { unordered_map<int, std::vector<int>> trackerToConditionMap; unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; @@ -354,7 +522,8 @@ TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, noReportMetricIds)); + alertTrackerMap, metricsWithActivation, + noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingMatchers) { @@ -374,6 +543,7 @@ TEST(MetricsManagerTest, TestMissingMatchers) { unordered_map<int, std::vector<int>> trackerToConditionMap; unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, @@ -382,7 +552,8 @@ TEST(MetricsManagerTest, TestMissingMatchers) { allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, noReportMetricIds)); + alertTrackerMap, metricsWithActivation, + noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingPredicate) { @@ -402,6 +573,7 @@ TEST(MetricsManagerTest, TestMissingPredicate) { unordered_map<int, std::vector<int>> trackerToConditionMap; unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, @@ -410,7 +582,7 @@ TEST(MetricsManagerTest, TestMissingPredicate) { allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, noReportMetricIds)); + alertTrackerMap, metricsWithActivation, noReportMetricIds)); } TEST(MetricsManagerTest, TestCirclePredicateDependency) { @@ -430,6 +602,7 @@ TEST(MetricsManagerTest, TestCirclePredicateDependency) { unordered_map<int, std::vector<int>> trackerToConditionMap; unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; @@ -439,7 +612,8 @@ TEST(MetricsManagerTest, TestCirclePredicateDependency) { allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, noReportMetricIds)); + alertTrackerMap, metricsWithActivation, + noReportMetricIds)); } TEST(MetricsManagerTest, testAlertWithUnknownMetric) { @@ -459,6 +633,7 @@ TEST(MetricsManagerTest, testAlertWithUnknownMetric) { unordered_map<int, std::vector<int>> trackerToConditionMap; unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + unordered_map<int64_t, int> alertTrackerMap; vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; @@ -468,9 +643,157 @@ TEST(MetricsManagerTest, testAlertWithUnknownMetric) { allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, noReportMetricIds)); + alertTrackerMap, metricsWithActivation, + noReportMetricIds)); +} + +TEST(MetricsManagerTest, TestLogSources) { + string app1 = "app1"; + set<int32_t> app1Uids = {1111, 11111}; + string app2 = "app2"; + set<int32_t> app2Uids = {2222}; + string app3 = "app3"; + set<int32_t> app3Uids = {3333, 1111}; + + map<string, set<int32_t>> pkgToUids; + pkgToUids[app1] = app1Uids; + pkgToUids[app2] = app2Uids; + pkgToUids[app3] = app3Uids; + + int32_t atom1 = 10; + int32_t atom2 = 20; + int32_t atom3 = 30; + sp<MockUidMap> uidMap = new StrictMock<MockUidMap>(); + EXPECT_CALL(*uidMap, getAppUid(_)) + .Times(4) + .WillRepeatedly(Invoke([&pkgToUids](const string& pkg) { + const auto& it = pkgToUids.find(pkg); + if (it != pkgToUids.end()) { + return it->second; + } + return set<int32_t>(); + })); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1); + EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1); + + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> periodicAlarmMonitor; + + StatsdConfig config = buildGoodConfig(); + config.add_allowed_log_source("AID_SYSTEM"); + config.add_allowed_log_source(app1); + config.add_default_pull_packages("AID_SYSTEM"); + config.add_default_pull_packages("AID_ROOT"); + + const set<int32_t> defaultPullUids = {AID_SYSTEM, AID_ROOT}; + + PullAtomPackages* pullAtomPackages = config.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom1); + pullAtomPackages->add_packages(app1); + pullAtomPackages->add_packages(app3); + + pullAtomPackages = config.add_pull_atom_packages(); + pullAtomPackages->set_atom_id(atom2); + pullAtomPackages->add_packages(app2); + pullAtomPackages->add_packages("AID_STATSD"); + + MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, + pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); + + EXPECT_TRUE(metricsManager.isConfigValid()); + + ASSERT_EQ(metricsManager.mAllowedUid.size(), 1); + EXPECT_EQ(metricsManager.mAllowedUid[0], AID_SYSTEM); + + ASSERT_EQ(metricsManager.mAllowedPkg.size(), 1); + EXPECT_EQ(metricsManager.mAllowedPkg[0], app1); + + ASSERT_EQ(metricsManager.mAllowedLogSources.size(), 3); + EXPECT_TRUE(isSubset({AID_SYSTEM}, metricsManager.mAllowedLogSources)); + EXPECT_TRUE(isSubset(app1Uids, metricsManager.mAllowedLogSources)); + + ASSERT_EQ(metricsManager.mDefaultPullUids.size(), 2); + EXPECT_TRUE(isSubset(defaultPullUids, metricsManager.mDefaultPullUids)); + ; + + vector<int32_t> atom1Uids = metricsManager.getPullAtomUids(atom1); + ASSERT_EQ(atom1Uids.size(), 5); + set<int32_t> expectedAtom1Uids; + expectedAtom1Uids.insert(defaultPullUids.begin(), defaultPullUids.end()); + expectedAtom1Uids.insert(app1Uids.begin(), app1Uids.end()); + expectedAtom1Uids.insert(app3Uids.begin(), app3Uids.end()); + EXPECT_TRUE(isSubset(expectedAtom1Uids, set<int32_t>(atom1Uids.begin(), atom1Uids.end()))); + + vector<int32_t> atom2Uids = metricsManager.getPullAtomUids(atom2); + ASSERT_EQ(atom2Uids.size(), 4); + set<int32_t> expectedAtom2Uids; + expectedAtom1Uids.insert(defaultPullUids.begin(), defaultPullUids.end()); + expectedAtom1Uids.insert(app2Uids.begin(), app2Uids.end()); + expectedAtom1Uids.insert(AID_STATSD); + EXPECT_TRUE(isSubset(expectedAtom2Uids, set<int32_t>(atom2Uids.begin(), atom2Uids.end()))); + + vector<int32_t> atom3Uids = metricsManager.getPullAtomUids(atom3); + ASSERT_EQ(atom3Uids.size(), 2); + EXPECT_TRUE(isSubset(defaultPullUids, set<int32_t>(atom3Uids.begin(), atom3Uids.end()))); } +TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) { + sp<UidMap> uidMap; + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> periodicAlarmMonitor; + + StatsdConfig config = buildGoodConfig(); + config.add_whitelisted_atom_ids(3); + config.add_whitelisted_atom_ids(4); + + MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, + pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); + + LogEvent event(0 /* uid */, 0 /* pid */); + CreateNoValuesLogEvent(&event, 10 /* atom id */, 0 /* timestamp */); + EXPECT_FALSE(metricsManager.checkLogCredentials(event)); + + CreateNoValuesLogEvent(&event, 3 /* atom id */, 0 /* timestamp */); + EXPECT_TRUE(metricsManager.checkLogCredentials(event)); + + CreateNoValuesLogEvent(&event, 4 /* atom id */, 0 /* timestamp */); + EXPECT_TRUE(metricsManager.checkLogCredentials(event)); +} + +TEST(MetricsManagerTest, TestWhitelistedAtomStateTracker) { + sp<UidMap> uidMap; + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> periodicAlarmMonitor; + + StatsdConfig config = buildGoodConfig(); + config.add_allowed_log_source("AID_SYSTEM"); + config.add_whitelisted_atom_ids(3); + config.add_whitelisted_atom_ids(4); + + State state; + state.set_id(1); + state.set_atom_id(3); + + *config.add_state() = state; + + config.mutable_count_metric(0)->add_slice_by_state(state.id()); + + StateManager::getInstance().clear(); + + MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap, + pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor); + + EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount()); + EXPECT_FALSE(metricsManager.isConfigValid()); +} + +} // namespace statsd +} // namespace os +} // namespace android + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index fe25a257aa67..1e6680c47567 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -13,6 +13,11 @@ // limitations under the License. #include "StatsLogProcessor.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <stdio.h> + #include "StatsService.h" #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" @@ -20,18 +25,14 @@ #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" #include "packages/UidMap.h" +#include "statslog_statsdtest.h" #include "storage/StorageManager.h" -#include "statslog.h" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> - #include "tests/statsd_test_util.h" -#include <stdio.h> - using namespace android; using namespace testing; +using ::ndk::SharedRefBase; +using std::shared_ptr; namespace android { namespace os { @@ -49,10 +50,12 @@ public: MockMetricsManager() : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(), new StatsPullerManager(), - new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {}, - [](const sp<IStatsCompanionService>&) {}), - new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {}, - [](const sp<IStatsCompanionService>&) {})) { + new AlarmMonitor(10, + [](const shared_ptr<IStatsCompanionService>&, int64_t) {}, + [](const shared_ptr<IStatsCompanionService>&) {}), + new AlarmMonitor(10, + [](const shared_ptr<IStatsCompanionService>&, int64_t) {}, + [](const shared_ptr<IStatsCompanionService>&) {})) { } MOCK_METHOD0(byteSize, size_t()); @@ -76,9 +79,9 @@ TEST(StatsLogProcessorTest, TestRateLimitByteSize) { // Expect only the first flush to trigger a check for byte size since the last two are // rate-limited. EXPECT_CALL(mockMetricsManager, byteSize()).Times(1); - p.flushIfNecessaryLocked(99, key, mockMetricsManager); - p.flushIfNecessaryLocked(100, key, mockMetricsManager); - p.flushIfNecessaryLocked(101, key, mockMetricsManager); + p.flushIfNecessaryLocked(key, mockMetricsManager); + p.flushIfNecessaryLocked(key, mockMetricsManager); + p.flushIfNecessaryLocked(key, mockMetricsManager); } TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { @@ -103,7 +106,7 @@ TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { StatsdStats::kMaxMetricsBytesPerConfig * .95))); // Expect only one broadcast despite always returning a size that should trigger broadcast. - p.flushIfNecessaryLocked(1, key, mockMetricsManager); + p.flushIfNecessaryLocked(key, mockMetricsManager); EXPECT_EQ(1, broadcastCount); // b/73089712 @@ -136,7 +139,7 @@ TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) { EXPECT_CALL(mockMetricsManager, dropData(_)).Times(1); // Expect to call the onDumpReport and skip the broadcast. - p.flushIfNecessaryLocked(1, key, mockMetricsManager); + p.flushIfNecessaryLocked(key, mockMetricsManager); EXPECT_EQ(0, broadcastCount); } @@ -183,7 +186,7 @@ TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) { EXPECT_TRUE(output.reports_size() > 0); auto uidmap = output.reports(0).uid_map(); EXPECT_TRUE(uidmap.snapshots_size() > 0); - EXPECT_EQ(2, uidmap.snapshots(0).package_info_size()); + ASSERT_EQ(2, uidmap.snapshots(0).package_info_size()); } TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) { @@ -244,7 +247,7 @@ TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) { output.ParseFromArray(bytes.data(), bytes.size()); EXPECT_TRUE(output.reports_size() > 0); auto report = output.reports(0); - EXPECT_EQ(1, report.annotation_size()); + ASSERT_EQ(1, report.annotation_size()); EXPECT_EQ(1, report.annotation(0).field_int64()); EXPECT_EQ(2, report.annotation(0).field_int32()); } @@ -252,7 +255,7 @@ TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) { TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) { // Setup a simple config. StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); *config.add_atom_matcher() = wakelockAcquireMatcher; @@ -264,37 +267,97 @@ TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) { ConfigKey cfgKey; sp<StatsLogProcessor> processor = CreateStatsLogProcessor(1, 1, config, cfgKey); - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 2); + std::vector<int> attributionUids = {111}; + std::vector<string> attributionTags = {"App1"}; + std::unique_ptr<LogEvent> event = + CreateAcquireWakelockEvent(2 /*timestamp*/, attributionUids, attributionTags, "wl1"); processor->OnLogEvent(event.get()); vector<uint8_t> bytes; ConfigMetricsReportList output; // Dump report WITHOUT erasing data. - processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, FAST, &bytes); + processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, FAST, + &bytes); output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_EQ(output.reports_size(), 1); - EXPECT_EQ(output.reports(0).metrics_size(), 1); - EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); + ASSERT_EQ(output.reports_size(), 1); + ASSERT_EQ(output.reports(0).metrics_size(), 1); + ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); // Dump report WITH erasing data. There should be data since we didn't previously erase it. processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes); output.ParseFromArray(bytes.data(), bytes.size()); - EXPECT_EQ(output.reports_size(), 1); - EXPECT_EQ(output.reports(0).metrics_size(), 1); - EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); + ASSERT_EQ(output.reports_size(), 1); + ASSERT_EQ(output.reports(0).metrics_size(), 1); + ASSERT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1); // Dump report again. There should be no data since we erased it. processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, FAST, &bytes); output.ParseFromArray(bytes.data(), bytes.size()); // We don't care whether statsd has a report, as long as it has no count metrics in it. - bool noData = output.reports_size() == 0 - || output.reports(0).metrics_size() == 0 - || output.reports(0).metrics(0).count_metrics().data_size() == 0; + bool noData = output.reports_size() == 0 || output.reports(0).metrics_size() == 0 || + output.reports(0).metrics(0).count_metrics().data_size() == 0; EXPECT_TRUE(noData); } +TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) { + // Setup simple config key corresponding to empty config. + sp<UidMap> m = new UidMap(); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> subscriberAlarmMonitor; + StatsLogProcessor p( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector<int64_t>&) { return true; }); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(false); + p.OnConfigUpdated(0, key, config); + EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); + + config.add_default_pull_packages("AID_STATSD"); + p.OnConfigUpdated(5, key, config); + EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); + + p.OnConfigRemoved(key); + EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); +} + +TEST(StatsLogProcessorTest, InvalidConfigRemoved) { + // Setup simple config key corresponding to empty config. + StatsdStats::getInstance().reset(); + sp<UidMap> m = new UidMap(); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, + {String16("p1"), String16("p2")}, {String16(""), String16("")}); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> subscriberAlarmMonitor; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector<int64_t>&) {return true;}); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(true); + p.OnConfigUpdated(0, key, config); + EXPECT_EQ(1, p.mMetricsManagers.size()); + EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end()); + // Cannot assert the size of mConfigStats since it is static and does not get cleared on reset. + EXPECT_NE(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size()); + + StatsdConfig invalidConfig = MakeConfig(true); + invalidConfig.clear_allowed_log_source(); + p.OnConfigUpdated(0, key, invalidConfig); + EXPECT_EQ(0, p.mMetricsManagers.size()); + // The current configs should not contain the invalid config. + EXPECT_EQ(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + // Both "config" and "invalidConfig" should be in the icebox. + EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size()); + +} + + TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { int uid = 1111; @@ -392,15 +455,16 @@ TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { long timeBase1 = 1; int broadcastCount = 0; - StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, - timeBase1, [](const ConfigKey& key) { return true; }, + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, timeBase1, + [](const ConfigKey& key) { return true; }, [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector<int64_t>& activeConfigs) { + const vector<int64_t>& activeConfigs) { broadcastCount++; EXPECT_EQ(broadcastUid, uid); activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), - activeConfigs.begin(), activeConfigs.end()); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); return true; }); @@ -408,7 +472,7 @@ TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { processor.OnConfigUpdated(2, cfgKey2, config2); processor.OnConfigUpdated(3, cfgKey3, config3); - EXPECT_EQ(3, processor.mMetricsManagers.size()); + ASSERT_EQ(3, processor.mMetricsManagers.size()); // Expect the first config and both metrics in it to be active. auto it = processor.mMetricsManagers.find(cfgKey1); @@ -492,8 +556,10 @@ TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { EXPECT_EQ(broadcastCount, 0); // Activate all 3 metrics that were not active. - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1); + std::vector<int> attributionUids = {111}; + std::vector<string> attributionTags = {"App1"}; + std::unique_ptr<LogEvent> event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); processor.OnLogEvent(event.get()); // Assert that all 3 configs are active. @@ -503,23 +569,23 @@ TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { // A broadcast should have happened, and all 3 configs should be active in the broadcast. EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(activeConfigsBroadcast.size(), 3); - EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId1) - != activeConfigsBroadcast.end()); - EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId2) - != activeConfigsBroadcast.end()); - EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId3) - != activeConfigsBroadcast.end()); + ASSERT_EQ(activeConfigsBroadcast.size(), 3); + EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId1) != + activeConfigsBroadcast.end()); + EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId2) != + activeConfigsBroadcast.end()); + EXPECT_TRUE(std::find(activeConfigsBroadcast.begin(), activeConfigsBroadcast.end(), cfgId3) != + activeConfigsBroadcast.end()); // When we shut down, metrics 3 & 5 have 100ns remaining, metric 6 has 100s + 100ns. int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; processor.SaveActiveConfigsToDisk(shutDownTime); const int64_t ttl3 = event->GetElapsedTimestampNs() + - metric3ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; + metric3ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; const int64_t ttl5 = event->GetElapsedTimestampNs() + - metric5ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; + metric5ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; const int64_t ttl6 = event->GetElapsedTimestampNs() + - metric6ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; + metric6ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime; // Create a second StatsLogProcessor and push the same 3 configs. long timeBase2 = 1000; @@ -528,7 +594,7 @@ TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { processor2->OnConfigUpdated(timeBase2, cfgKey2, config2); processor2->OnConfigUpdated(timeBase2, cfgKey3, config3); - EXPECT_EQ(3, processor2->mMetricsManagers.size()); + ASSERT_EQ(3, processor2->mMetricsManagers.size()); // First config and both metrics are active. it = processor2->mMetricsManagers.find(cfgKey1); @@ -587,7 +653,7 @@ TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { EXPECT_TRUE(it != processor2->mMetricsManagers.end()); auto& metricsManager1003 = it->second; EXPECT_FALSE(metricsManager1003->isActive()); - EXPECT_EQ(2, metricsManager1003->mAllMetricProducers.size()); + ASSERT_EQ(2, metricsManager1003->mAllMetricProducers.size()); metricIt = metricsManager1003->mAllMetricProducers.begin(); for (; metricIt != metricsManager1002->mAllMetricProducers.end(); metricIt++) { @@ -670,7 +736,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBoot) { sp<StatsLogProcessor> processor = CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); - EXPECT_EQ(1, processor->mMetricsManagers.size()); + ASSERT_EQ(1, processor->mMetricsManagers.size()); auto it = processor->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor->mMetricsManagers.end()); auto& metricsManager1 = it->second; @@ -701,8 +767,10 @@ TEST(StatsLogProcessorTest, TestActivationOnBoot) { EXPECT_EQ(0, activation1->start_ns); EXPECT_EQ(kNotActive, activation1->state); - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1); + std::vector<int> attributionUids = {111}; + std::vector<string> attributionTags = {"App1"}; + std::unique_ptr<LogEvent> event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); processor->OnLogEvent(event.get()); EXPECT_FALSE(metricProducer1->isActive()); @@ -718,7 +786,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBoot) { sp<StatsLogProcessor> processor2 = CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1); - EXPECT_EQ(1, processor2->mMetricsManagers.size()); + ASSERT_EQ(1, processor2->mMetricsManagers.size()); it = processor2->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor2->mMetricsManagers.end()); auto& metricsManager1001 = it->second; @@ -801,7 +869,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor->mMetricsManagers.size()); + ASSERT_EQ(1, processor->mMetricsManagers.size()); auto it = processor->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor->mMetricsManagers.end()); auto& metricsManager1 = it->second; @@ -830,7 +898,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { int i = 0; for (; i < metricsManager1->mAllAtomMatchers.size(); i++) { if (metricsManager1->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { + metric1ActivationTrigger1->atom_matcher_id()) { break; } } @@ -842,7 +910,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { i = 0; for (; i < metricsManager1->mAllAtomMatchers.size(); i++) { if (metricsManager1->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { + metric1ActivationTrigger2->atom_matcher_id()) { break; } } @@ -853,8 +921,10 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { // }}}------------------------------------------------------------------------------ // Trigger Activation 1 for Metric 1 - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1); + std::vector<int> attributionUids = {111}; + std::vector<string> attributionTags = {"App1"}; + std::unique_ptr<LogEvent> event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); processor->OnLogEvent(event.get()); // Metric 1 is not active; Activation 1 set to kActiveOnBoot @@ -884,7 +954,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor2->mMetricsManagers.size()); + ASSERT_EQ(1, processor2->mMetricsManagers.size()); it = processor2->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor2->mMetricsManagers.end()); auto& metricsManager1001 = it->second; @@ -913,7 +983,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { i = 0; for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) { if (metricsManager1001->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { + metric1ActivationTrigger1->atom_matcher_id()) { break; } } @@ -925,7 +995,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { i = 0; for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) { if (metricsManager1001->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { + metric1ActivationTrigger2->atom_matcher_id()) { break; } } @@ -952,10 +1022,8 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { // }}}-------------------------------------------------------------------------------- // Trigger Activation 2 for Metric 1. - auto screenOnEvent = CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - timeBase2 + 200 - ); + auto screenOnEvent = + CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON); processor2->OnLogEvent(screenOnEvent.get()); // Metric 1 active; Activation 1 is active, Activation 2 is set to kActiveOnBoot @@ -987,7 +1055,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor3->mMetricsManagers.size()); + ASSERT_EQ(1, processor3->mMetricsManagers.size()); it = processor3->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor3->mMetricsManagers.end()); auto& metricsManagerTimeBase3 = it->second; @@ -1016,7 +1084,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { i = 0; for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) { if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { + metric1ActivationTrigger1->atom_matcher_id()) { break; } } @@ -1028,7 +1096,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { i = 0; for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) { if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { + metric1ActivationTrigger2->atom_matcher_id()) { break; } } @@ -1057,10 +1125,8 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { // }}}------------------------------------------------------------------------------- // Trigger Activation 2 for Metric 1 again. - screenOnEvent = CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - timeBase3 + 100 * NS_PER_SEC - ); + screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON); processor3->OnLogEvent(screenOnEvent.get()); // Metric 1 active; Activation 1 is not active, Activation 2 is set to active @@ -1091,7 +1157,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor4->mMetricsManagers.size()); + ASSERT_EQ(1, processor4->mMetricsManagers.size()); it = processor4->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor4->mMetricsManagers.end()); auto& metricsManagerTimeBase4 = it->second; @@ -1120,7 +1186,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { i = 0; for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) { if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { + metric1ActivationTrigger1->atom_matcher_id()) { break; } } @@ -1132,7 +1198,7 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) { i = 0; for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) { if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { + metric1ActivationTrigger2->atom_matcher_id()) { break; } } @@ -1199,87 +1265,68 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi ConfigKey cfgKey1(uid, 12341); long timeBase1 = 1; - sp<StatsLogProcessor> processor = + sp<StatsLogProcessor> processor1 = CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor->mMetricsManagers.size()); - auto it = processor->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor->mMetricsManagers.end()); + ASSERT_EQ(1, processor1->mMetricsManagers.size()); + auto it = processor1->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor1->mMetricsManagers.end()); auto& metricsManager1 = it->second; EXPECT_TRUE(metricsManager1->isActive()); - auto metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer1 = *metricIt; - EXPECT_FALSE(metricProducer1->isActive()); - - metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer2 = *metricIt; - EXPECT_TRUE(metricProducer2->isActive()); - - int i = 0; - for (; i < metricsManager1->mAllAtomMatchers.size(); i++) { - if (metricsManager1->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activation1 = metricProducer1->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kNotActive, activation1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType); - - i = 0; - for (; i < metricsManager1->mAllAtomMatchers.size(); i++) { - if (metricsManager1->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } - const auto& activation2 = metricProducer1->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType); + ASSERT_EQ(metricsManager1->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer1_1 = metricsManager1->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer1_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer1_1->isActive()); // inactive due to associated MetricActivation + + auto& metricProducer1_2 = metricsManager1->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer1_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer1_2->isActive()); + + ASSERT_EQ(metricProducer1_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation1_1_1 = metricProducer1_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation1_1_1->ttl_ns); + EXPECT_EQ(0, activation1_1_1->start_ns); + EXPECT_EQ(kNotActive, activation1_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation1_1_1->activationType); + + const auto& activation1_1_2 = metricProducer1_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation1_1_2->ttl_ns); + EXPECT_EQ(0, activation1_1_2->start_ns); + EXPECT_EQ(kNotActive, activation1_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1_1_2->activationType); // }}}------------------------------------------------------------------------------ // Trigger Activation 1 for Metric 1 - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1); - processor->OnLogEvent(event.get()); + std::vector<int> attributionUids = {111}; + std::vector<string> attributionTags = {"App1"}; + std::unique_ptr<LogEvent> event = + CreateAcquireWakelockEvent(timeBase1 + 100, attributionUids, attributionTags, "wl1"); + processor1->OnLogEvent(event.get()); // Metric 1 is not active; Activation 1 set to kActiveOnBoot // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_FALSE(metricProducer1->isActive()); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1->state); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); + EXPECT_FALSE(metricProducer1_1->isActive()); + EXPECT_EQ(0, activation1_1_1->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1_1_1->state); + EXPECT_EQ(0, activation1_1_2->start_ns); + EXPECT_EQ(kNotActive, activation1_1_2->state); - EXPECT_TRUE(metricProducer2->isActive()); + EXPECT_TRUE(metricProducer1_2->isActive()); // }}}----------------------------------------------------------------------------- // Simulate shutdown by saving state to disk int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; - processor->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_FALSE(metricProducer1->isActive()); - int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; + processor1->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_FALSE(metricProducer1_1->isActive()); // Simulate device restarted state by creating new instance of StatsLogProcessor with the // same config. @@ -1290,58 +1337,37 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor2->mMetricsManagers.size()); + ASSERT_EQ(1, processor2->mMetricsManagers.size()); it = processor2->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager1001 = it->second; - EXPECT_TRUE(metricsManager1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1001 = *metricIt; - EXPECT_FALSE(metricProducer1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1002 = *metricIt; - EXPECT_TRUE(metricProducer1002->isActive()); - - i = 0; - for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) { - if (metricsManager1001->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activation1001_1 = metricProducer1001->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activation1001_1->ttl_ns); - EXPECT_EQ(0, activation1001_1->start_ns); - EXPECT_EQ(kNotActive, activation1001_1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001_1->activationType); - - i = 0; - for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) { - if (metricsManager1001->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } + auto& metricsManager2 = it->second; + EXPECT_TRUE(metricsManager2->isActive()); - const auto& activation1001_2 = metricProducer1001->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activation1001_2->ttl_ns); - EXPECT_EQ(0, activation1001_2->start_ns); - EXPECT_EQ(kNotActive, activation1001_2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1001_2->activationType); + ASSERT_EQ(metricsManager2->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer2_1 = metricsManager2->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer2_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer2_1->isActive()); + + auto& metricProducer2_2 = metricsManager2->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer2_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer2_2->isActive()); + + ASSERT_EQ(metricProducer2_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation2_1_1 = metricProducer2_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation2_1_1->ttl_ns); + EXPECT_EQ(0, activation2_1_1->start_ns); + EXPECT_EQ(kNotActive, activation2_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation2_1_1->activationType); + + const auto& activation2_1_2 = metricProducer2_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation2_1_2->ttl_ns); + EXPECT_EQ(0, activation2_1_2->start_ns); + EXPECT_EQ(kNotActive, activation2_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2_1_2->activationType); // }}}----------------------------------------------------------------------------------- // Load saved state from disk. @@ -1350,42 +1376,41 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi // Metric 1 active; Activation 1 is active, Activation 2 is not active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); - EXPECT_EQ(kActive, activation1001_1->state); - EXPECT_EQ(0, activation1001_2->start_ns); - EXPECT_EQ(kNotActive, activation1001_2->state); + EXPECT_TRUE(metricProducer2_1->isActive()); + int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; + EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); + EXPECT_EQ(kActive, activation2_1_1->state); + EXPECT_EQ(0, activation2_1_2->start_ns); + EXPECT_EQ(kNotActive, activation2_1_2->state); - EXPECT_TRUE(metricProducer1002->isActive()); + EXPECT_TRUE(metricProducer2_2->isActive()); // }}}-------------------------------------------------------------------------------- // Trigger Activation 2 for Metric 1. - auto screenOnEvent = CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - timeBase2 + 200 - ); + auto screenOnEvent = + CreateScreenStateChangedEvent(timeBase2 + 200, android::view::DISPLAY_STATE_ON); processor2->OnLogEvent(screenOnEvent.get()); // Metric 1 active; Activation 1 is active, Activation 2 is active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); - EXPECT_EQ(kActive, activation1001_1->state); - EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation1001_2->start_ns); - EXPECT_EQ(kActive, activation1001_2->state); + EXPECT_TRUE(metricProducer2_1->isActive()); + EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); + EXPECT_EQ(kActive, activation2_1_1->state); + EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation2_1_2->start_ns); + EXPECT_EQ(kActive, activation2_1_2->state); - EXPECT_TRUE(metricProducer1002->isActive()); + EXPECT_TRUE(metricProducer2_2->isActive()); // }}}--------------------------------------------------------------------------- // Simulate shutdown by saving state to disk shutDownTime = timeBase2 + 50 * NS_PER_SEC; processor2->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_TRUE(metricProducer1002->isActive()); - ttl1 = timeBase2 + metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC - shutDownTime; - int64_t ttl2 = screenOnEvent->GetElapsedTimestampNs() + - metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - shutDownTime; + EXPECT_TRUE(metricProducer2_1->isActive()); + EXPECT_TRUE(metricProducer2_2->isActive()); + ttl1 -= shutDownTime - timeBase2; + int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - + (shutDownTime - screenOnEvent->GetElapsedTimestampNs()); // Simulate device restarted state by creating new instance of StatsLogProcessor with the // same config. @@ -1396,58 +1421,37 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor3->mMetricsManagers.size()); + ASSERT_EQ(1, processor3->mMetricsManagers.size()); it = processor3->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor3->mMetricsManagers.end()); - auto& metricsManagerTimeBase3 = it->second; - EXPECT_TRUE(metricsManagerTimeBase3->isActive()); - - metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); - auto& metricProducerTimeBase3_1 = *metricIt; - EXPECT_FALSE(metricProducerTimeBase3_1->isActive()); - - metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); - auto& metricProducerTimeBase3_2 = *metricIt; - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); - - i = 0; - for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) { - if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activationTimeBase3_1 = metricProducerTimeBase3_1->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase3_1->ttl_ns); - EXPECT_EQ(0, activationTimeBase3_1->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase3_1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activationTimeBase3_1->activationType); - - i = 0; - for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) { - if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } + auto& metricsManager3 = it->second; + EXPECT_TRUE(metricsManager3->isActive()); - const auto& activationTimeBase3_2 = metricProducerTimeBase3_1->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase3_2->ttl_ns); - EXPECT_EQ(0, activationTimeBase3_2->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase3_2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activationTimeBase3_2->activationType); + ASSERT_EQ(metricsManager3->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer3_1 = metricsManager3->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer3_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer3_1->isActive()); + + auto& metricProducer3_2 = metricsManager3->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer3_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer3_2->isActive()); + + ASSERT_EQ(metricProducer3_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation3_1_1 = metricProducer3_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation3_1_1->ttl_ns); + EXPECT_EQ(0, activation3_1_1->start_ns); + EXPECT_EQ(kNotActive, activation3_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation3_1_1->activationType); + + const auto& activation3_1_2 = metricProducer3_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation3_1_2->ttl_ns); + EXPECT_EQ(0, activation3_1_2->start_ns); + EXPECT_EQ(kNotActive, activation3_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation3_1_2->activationType); // }}}---------------------------------------------------------------------------------- // Load saved state from disk. @@ -1456,32 +1460,30 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi // Metric 1 active: Activation 1 is active, Activation 2 is active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); - EXPECT_EQ(timeBase3 + ttl1 - activationTimeBase3_1->ttl_ns, activationTimeBase3_1->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_1->state); - EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_2->state); + EXPECT_TRUE(metricProducer3_1->isActive()); + EXPECT_EQ(timeBase3 + ttl1 - activation3_1_1->ttl_ns, activation3_1_1->start_ns); + EXPECT_EQ(kActive, activation3_1_1->state); + EXPECT_EQ(timeBase3 + ttl2 - activation3_1_2->ttl_ns, activation3_1_2->start_ns); + EXPECT_EQ(kActive, activation3_1_2->state); - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + EXPECT_TRUE(metricProducer3_2->isActive()); // }}}------------------------------------------------------------------------------- - // Trigger Activation 2 for Metric 1 again. - screenOnEvent = CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - timeBase3 + 100 * NS_PER_SEC - ); + screenOnEvent = CreateScreenStateChangedEvent(timeBase3 + 100 * NS_PER_SEC, + android::view::DISPLAY_STATE_ON); processor3->OnLogEvent(screenOnEvent.get()); - // Metric 1 active; Activation 1 is not active, Activation 2 is set to active + // Metric 1 active; Activation 1 is inactive (above screenOnEvent causes ttl1 to expire), + // Activation 2 is set to active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); - EXPECT_EQ(kNotActive, activationTimeBase3_1->state); - EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activationTimeBase3_2->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_2->state); + EXPECT_TRUE(metricProducer3_1->isActive()); + EXPECT_EQ(kNotActive, activation3_1_1->state); + EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation3_1_2->start_ns); + EXPECT_EQ(kActive, activation3_1_2->state); - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + EXPECT_TRUE(metricProducer3_2->isActive()); // }}}--------------------------------------------------------------------------- } @@ -1549,9 +1551,9 @@ TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { metric2ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY); // Send the config. - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); string serialized = config1.SerializeAsString(); - service.addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()}); + service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()}); // Make sure the config is stored on disk. Otherwise, we will not reset on system server death. StatsdConfig tmpConfig; @@ -1562,13 +1564,13 @@ TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { // Metric 2 is not active. // Metric 3 is active. // {{{--------------------------------------------------------------------------- - sp<StatsLogProcessor> processor = service.mProcessor; - EXPECT_EQ(1, processor->mMetricsManagers.size()); + sp<StatsLogProcessor> processor = service->mProcessor; + ASSERT_EQ(1, processor->mMetricsManagers.size()); auto it = processor->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor->mMetricsManagers.end()); auto& metricsManager1 = it->second; EXPECT_TRUE(metricsManager1->isActive()); - EXPECT_EQ(3, metricsManager1->mAllMetricProducers.size()); + ASSERT_EQ(3, metricsManager1->mAllMetricProducers.size()); auto& metricProducer1 = metricsManager1->mAllMetricProducers[0]; EXPECT_EQ(metricId1, metricProducer1->getMetricId()); @@ -1583,7 +1585,7 @@ TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { EXPECT_TRUE(metricProducer3->isActive()); // Check event activations. - EXPECT_EQ(metricsManager1->mAllAtomMatchers.size(), 4); + ASSERT_EQ(metricsManager1->mAllAtomMatchers.size(), 4); EXPECT_EQ(metricsManager1->mAllAtomMatchers[0]->getId(), metric1ActivationTrigger1->atom_matcher_id()); const auto& activation1 = metricProducer1->mEventActivationMap.at(0); @@ -1619,13 +1621,16 @@ TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { // Trigger Activation 1 for Metric 1. Should activate on boot. // Trigger Activation 4 for Metric 2. Should activate immediately. - long configAddedTimeNs = metricsManager1->mLastReportTimeNs; - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 1 + configAddedTimeNs); - processor->OnLogEvent(event.get()); + int64_t configAddedTimeNs = metricsManager1->mLastReportTimeNs; + std::vector<int> attributionUids = {111}; + std::vector<string> attributionTags = {"App1"}; + std::unique_ptr<LogEvent> event1 = CreateAcquireWakelockEvent( + 1 + configAddedTimeNs, attributionUids, attributionTags, "wl1"); + processor->OnLogEvent(event1.get()); - event = CreateFinishScheduledJobEvent(attributions1, "finish1", 2 + configAddedTimeNs); - processor->OnLogEvent(event.get()); + std::unique_ptr<LogEvent> event2 = CreateFinishScheduledJobEvent( + 2 + configAddedTimeNs, attributionUids, attributionTags, "finish1"); + processor->OnLogEvent(event2.get()); // Metric 1 is not active; Activation 1 set to kActiveOnBoot // Metric 2 is active. Activation 4 set to kActive @@ -1653,16 +1658,16 @@ TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { EXPECT_TRUE(approximateSystemServerDeath < NS_PER_SEC + configAddedTimeNs); // System server dies. - service.binderDied(nullptr); + service->statsCompanionServiceDiedImpl(); // We should have a new metrics manager. Lets get it and ensure activation status is restored. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor->mMetricsManagers.size()); + ASSERT_EQ(1, processor->mMetricsManagers.size()); it = processor->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor->mMetricsManagers.end()); auto& metricsManager2 = it->second; EXPECT_TRUE(metricsManager2->isActive()); - EXPECT_EQ(3, metricsManager2->mAllMetricProducers.size()); + ASSERT_EQ(3, metricsManager2->mAllMetricProducers.size()); auto& metricProducer1001 = metricsManager2->mAllMetricProducers[0]; EXPECT_EQ(metricId1, metricProducer1001->getMetricId()); @@ -1680,7 +1685,7 @@ TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { // Activation 1 is kActiveOnBoot. // Activation 2 and 3 are not active. // Activation 4 is active. - EXPECT_EQ(metricsManager2->mAllAtomMatchers.size(), 4); + ASSERT_EQ(metricsManager2->mAllAtomMatchers.size(), 4); EXPECT_EQ(metricsManager2->mAllAtomMatchers[0]->getId(), metric1ActivationTrigger1->atom_matcher_id()); const auto& activation1001 = metricProducer1001->mEventActivationMap.at(0); @@ -1713,6 +1718,163 @@ TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart) { EXPECT_EQ(kActive, activation1004->state); EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1004->activationType); // }}}------------------------------------------------------------------------------ + + // Clear the data stored on disk as a result of the system server death. + vector<uint8_t> buffer; + processor->onDumpReport(cfgKey1, configAddedTimeNs + NS_PER_SEC, false, true, ADB_DUMP, FAST, + &buffer); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUid) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr<LogEvent> logEvent = makeUidLogEvent(atomId, eventTimeNs, hostUid, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector<FieldValue>* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUid) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr<LogEvent> logEvent = + makeUidLogEvent(atomId, eventTimeNs, isolatedUid, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector<FieldValue>* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(field1, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(2).mValue.int_value); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUidAttributionChain) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr<LogEvent> logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {hostUid, 200}, + {"tag1", "tag2"}, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector<FieldValue>* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); +} + +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUidAttributionChain) { + int hostUid = 20; + int isolatedUid = 30; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + shared_ptr<LogEvent> logEvent = makeAttributionLogEvent(atomId, eventTimeNs, {isolatedUid, 200}, + {"tag1", "tag2"}, field1, field2); + + processor->OnLogEvent(logEvent.get()); + + const vector<FieldValue>* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(200, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(field1, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); +} + +TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) { + int hostUid = 20; + int isolatedUid = 30; + sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey key(3, 4); + + // TODO: All tests should not persist state on disk. This removes any reports that were present. + ProtoOutputStream proto; + StorageManager::appendConfigMetricsReport(key, &proto, /*erase data=*/true, /*isAdb=*/false); + + StatsdConfig config = MakeConfig(false); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(1, 1, config, key, nullptr, 0, mockUidMap); + vector<uint8_t> bytes; + + int64_t dumpTime1Ns = 1 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime1Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + ConfigMetricsReportList output; + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime2Ns = 5 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime2Ns, false /* include_current_bucket */, + false /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the dump report without clearing data is successful. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime2Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime3Ns = 10 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime3Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the previous dump report that didn't clear data did not overwrite the first dump's + // timestamps. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime3Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + } #else diff --git a/cmds/statsd/tests/StatsService_test.cpp b/cmds/statsd/tests/StatsService_test.cpp index 7c00531d560b..cc38c4a4067a 100644 --- a/cmds/statsd/tests/StatsService_test.cpp +++ b/cmds/statsd/tests/StatsService_test.cpp @@ -16,6 +16,7 @@ #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include <android/binder_interface_utils.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -29,33 +30,34 @@ namespace os { namespace statsd { using android::util::ProtoOutputStream; +using ::ndk::SharedRefBase; #ifdef __ANDROID__ TEST(StatsServiceTest, TestAddConfig_simple) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); StatsdConfig config; config.set_id(12345); string serialized = config.SerializeAsString(); EXPECT_TRUE( - service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); + service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); } TEST(StatsServiceTest, TestAddConfig_empty) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); string serialized = ""; EXPECT_TRUE( - service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); + service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); } TEST(StatsServiceTest, TestAddConfig_invalid) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); string serialized = "Invalid config!"; EXPECT_FALSE( - service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); + service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); } TEST(StatsServiceTest, TestGetUidFromArgs) { @@ -63,38 +65,34 @@ TEST(StatsServiceTest, TestGetUidFromArgs) { args.push(String8("-1")); args.push(String8("0")); args.push(String8("1")); - args.push(String8("9999999999999999999999999999999999")); args.push(String8("a1")); args.push(String8("")); int32_t uid; - StatsService service(nullptr, nullptr); - service.mEngBuild = true; + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + service->mEngBuild = true; // "-1" - EXPECT_FALSE(service.getUidFromArgs(args, 0, uid)); + EXPECT_FALSE(service->getUidFromArgs(args, 0, uid)); // "0" - EXPECT_TRUE(service.getUidFromArgs(args, 1, uid)); + EXPECT_TRUE(service->getUidFromArgs(args, 1, uid)); EXPECT_EQ(0, uid); // "1" - EXPECT_TRUE(service.getUidFromArgs(args, 2, uid)); + EXPECT_TRUE(service->getUidFromArgs(args, 2, uid)); EXPECT_EQ(1, uid); - // "999999999999999999" - EXPECT_FALSE(service.getUidFromArgs(args, 3, uid)); - // "a1" - EXPECT_FALSE(service.getUidFromArgs(args, 4, uid)); + EXPECT_FALSE(service->getUidFromArgs(args, 3, uid)); // "" - EXPECT_FALSE(service.getUidFromArgs(args, 5, uid)); + EXPECT_FALSE(service->getUidFromArgs(args, 4, uid)); // For a non-userdebug, uid "1" cannot be impersonated. - service.mEngBuild = false; - EXPECT_FALSE(service.getUidFromArgs(args, 2, uid)); + service->mEngBuild = false; + EXPECT_FALSE(service->getUidFromArgs(args, 2, uid)); } #else diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index d9fa4e99d54d..293e8ed1c44c 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -18,7 +18,7 @@ #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" #include "hash.h" -#include "statslog.h" +#include "statslog_statsdtest.h" #include "statsd_test_util.h" #include <android/util/ProtoOutputStream.h> @@ -45,26 +45,20 @@ TEST(UidMapTest, TestIsolatedUID) { sp<AlarmMonitor> anomalyAlarmMonitor; sp<AlarmMonitor> subscriberAlarmMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. - StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [](const ConfigKey& key) { return true; }, - [](const int&, const vector<int64_t>&) {return true;}); - LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1); - addEvent.write(100); // parent UID - addEvent.write(101); // isolated UID - addEvent.write(1); // Indicates creation. - addEvent.init(); + StatsLogProcessor p( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector<int64_t>&) { return true; }); + std::unique_ptr<LogEvent> addEvent = CreateIsolatedUidChangedEvent( + 1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 1 /*is_create*/); EXPECT_EQ(101, m->getHostUidOrSelf(101)); - - p.OnLogEvent(&addEvent); + p.OnLogEvent(addEvent.get()); EXPECT_EQ(100, m->getHostUidOrSelf(101)); - LogEvent removeEvent(android::util::ISOLATED_UID_CHANGED, 1); - removeEvent.write(100); // parent UID - removeEvent.write(101); // isolated UID - removeEvent.write(0); // Indicates removal. - removeEvent.init(); - p.OnLogEvent(&removeEvent); + std::unique_ptr<LogEvent> removeEvent = CreateIsolatedUidChangedEvent( + 1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 0 /*is_create*/); + p.OnLogEvent(removeEvent.get()); EXPECT_EQ(101, m->getHostUidOrSelf(101)); } @@ -92,7 +86,7 @@ TEST(UidMapTest, TestMatching) { EXPECT_FALSE(m.hasApp(1000, "not.app")); std::set<string> name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */); - EXPECT_EQ(name_set.size(), 2u); + ASSERT_EQ(name_set.size(), 2u); EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); @@ -121,7 +115,7 @@ TEST(UidMapTest, TestAddAndRemove) { m.updateMap(1, uids, versions, versionStrings, apps, installers); std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - EXPECT_EQ(name_set.size(), 2u); + ASSERT_EQ(name_set.size(), 2u); EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); @@ -130,7 +124,7 @@ TEST(UidMapTest, TestAddAndRemove) { EXPECT_EQ(40, m.getAppVersion(1000, kApp1)); name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - EXPECT_EQ(name_set.size(), 2u); + ASSERT_EQ(name_set.size(), 2u); EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); @@ -138,7 +132,7 @@ TEST(UidMapTest, TestAddAndRemove) { EXPECT_FALSE(m.hasApp(1000, kApp1)); EXPECT_TRUE(m.hasApp(1000, kApp2)); name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - EXPECT_EQ(name_set.size(), 1u); + ASSERT_EQ(name_set.size(), 1u); EXPECT_TRUE(name_set.find(kApp1) == name_set.end()); EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); @@ -155,14 +149,14 @@ TEST(UidMapTest, TestUpdateApp) { m.updateMap(1, {1000, 1000}, {4, 5}, {String16("v4"), String16("v5")}, {String16(kApp1.c_str()), String16(kApp2.c_str())}, {String16(""), String16("")}); std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - EXPECT_EQ(name_set.size(), 2u); + ASSERT_EQ(name_set.size(), 2u); EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); // Adds a new name for uid 1000. m.updateApp(2, String16("NeW_aPP1_NAmE"), 1000, 40, String16("v40"), String16("")); name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */); - EXPECT_EQ(name_set.size(), 3u); + ASSERT_EQ(name_set.size(), 3u); EXPECT_TRUE(name_set.find(kApp1) != name_set.end()); EXPECT_TRUE(name_set.find(kApp2) != name_set.end()); EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end()); @@ -171,7 +165,7 @@ TEST(UidMapTest, TestUpdateApp) { // This name is also reused by another uid 2000. m.updateApp(3, String16("NeW_aPP1_NAmE"), 2000, 1, String16("v1"), String16("")); name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */); - EXPECT_EQ(name_set.size(), 1u); + ASSERT_EQ(name_set.size(), 1u); EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end()); EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end()); } @@ -218,7 +212,7 @@ TEST(UidMapTest, TestOutputIncludesAtLeastOneSnapshot) { // Check there's still a uidmap attached this one. UidMapping results; protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(1, results.snapshots_size()); + ASSERT_EQ(1, results.snapshots_size()); EXPECT_EQ("v1", results.snapshots(0).package_info(0).version_string()); } @@ -246,7 +240,7 @@ TEST(UidMapTest, TestRemovedAppRetained) { // Snapshot should still contain this item as deleted. UidMapping results; protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(1, results.snapshots(0).package_info_size()); + ASSERT_EQ(1, results.snapshots(0).package_info_size()); EXPECT_EQ(true, results.snapshots(0).package_info(0).deleted()); } @@ -275,7 +269,7 @@ TEST(UidMapTest, TestRemovedAppOverGuardrail) { ProtoOutputStream proto; m.appendUidMap(3, config1, nullptr, true, true, &proto); protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size()); + ASSERT_EQ(maxDeletedApps + 10, results.snapshots(0).package_info_size()); // Now remove all the apps. m.updateMap(1, uids, versions, versionStrings, apps, installers); @@ -287,7 +281,7 @@ TEST(UidMapTest, TestRemovedAppOverGuardrail) { m.appendUidMap(5, config1, nullptr, true, true, &proto); // Snapshot drops the first nine items. protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(maxDeletedApps, results.snapshots(0).package_info_size()); + ASSERT_EQ(maxDeletedApps, results.snapshots(0).package_info_size()); } TEST(UidMapTest, TestClearingOutput) { @@ -319,44 +313,44 @@ TEST(UidMapTest, TestClearingOutput) { m.appendUidMap(2, config1, nullptr, true, true, &proto); UidMapping results; protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(1, results.snapshots_size()); + ASSERT_EQ(1, results.snapshots_size()); // We have to keep at least one snapshot in memory at all times. proto.clear(); m.appendUidMap(2, config1, nullptr, true, true, &proto); protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(1, results.snapshots_size()); + ASSERT_EQ(1, results.snapshots_size()); // Now add another configuration. m.OnConfigUpdated(config2); m.updateApp(5, String16(kApp1.c_str()), 1000, 40, String16("v40"), String16("")); - EXPECT_EQ(1U, m.mChanges.size()); + ASSERT_EQ(1U, m.mChanges.size()); proto.clear(); m.appendUidMap(6, config1, nullptr, true, true, &proto); protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(1, results.snapshots_size()); - EXPECT_EQ(1, results.changes_size()); - EXPECT_EQ(1U, m.mChanges.size()); + ASSERT_EQ(1, results.snapshots_size()); + ASSERT_EQ(1, results.changes_size()); + ASSERT_EQ(1U, m.mChanges.size()); // Add another delta update. m.updateApp(7, String16(kApp2.c_str()), 1001, 41, String16("v41"), String16("")); - EXPECT_EQ(2U, m.mChanges.size()); + ASSERT_EQ(2U, m.mChanges.size()); // We still can't remove anything. proto.clear(); m.appendUidMap(8, config1, nullptr, true, true, &proto); protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(1, results.snapshots_size()); - EXPECT_EQ(1, results.changes_size()); - EXPECT_EQ(2U, m.mChanges.size()); + ASSERT_EQ(1, results.snapshots_size()); + ASSERT_EQ(1, results.changes_size()); + ASSERT_EQ(2U, m.mChanges.size()); proto.clear(); m.appendUidMap(9, config2, nullptr, true, true, &proto); protoOutputStreamToUidMapping(&proto, &results); - EXPECT_EQ(1, results.snapshots_size()); - EXPECT_EQ(2, results.changes_size()); + ASSERT_EQ(1, results.snapshots_size()); + ASSERT_EQ(2, results.changes_size()); // At this point both should be cleared. - EXPECT_EQ(0U, m.mChanges.size()); + ASSERT_EQ(0U, m.mChanges.size()); } TEST(UidMapTest, TestMemoryComputed) { @@ -414,13 +408,13 @@ TEST(UidMapTest, TestMemoryGuardrail) { m.updateApp(3, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 2, String16("v2"), String16("")); - EXPECT_EQ(1U, m.mChanges.size()); + ASSERT_EQ(1U, m.mChanges.size()); // Now force deletion by limiting the memory to hold one delta change. m.maxBytesOverride = 120; // Since the app string alone requires >45 characters. m.updateApp(5, String16("EXTREMELY_LONG_STRING_FOR_APP_TO_WASTE_MEMORY.0"), 1000, 4, String16("v4"), String16("")); - EXPECT_EQ(1U, m.mChanges.size()); + ASSERT_EQ(1U, m.mChanges.size()); } #else diff --git a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp index e664023a1647..64ea219c8465 100644 --- a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp @@ -15,12 +15,14 @@ #include "src/anomaly/AlarmTracker.h" #include <gtest/gtest.h> +#include <log/log_time.h> #include <stdio.h> #include <vector> using namespace testing; using android::sp; using std::set; +using std::shared_ptr; using std::unordered_map; using std::vector; @@ -34,8 +36,9 @@ const ConfigKey kConfigKey(0, 12345); TEST(AlarmTrackerTest, TestTriggerTimestamp) { sp<AlarmMonitor> subscriberAlarmMonitor = - new AlarmMonitor(100, [](const sp<IStatsCompanionService>&, int64_t){}, - [](const sp<IStatsCompanionService>&){}); + new AlarmMonitor(100, + [](const shared_ptr<IStatsCompanionService>&, int64_t){}, + [](const shared_ptr<IStatsCompanionService>&){}); Alarm alarm; alarm.set_offset_millis(15 * MS_PER_SEC); alarm.set_period_millis(60 * 60 * MS_PER_SEC); // 1hr @@ -56,7 +59,7 @@ TEST(AlarmTrackerTest, TestTriggerTimestamp) { currentTimeSec = startMillis / MS_PER_SEC + 7000; nextAlarmTime = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60; firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec)); - EXPECT_EQ(firedAlarmSet.size(), 1u); + ASSERT_EQ(firedAlarmSet.size(), 1u); tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); EXPECT_TRUE(firedAlarmSet.empty()); EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp index c10703c36b98..0cc8af16c782 100644 --- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp @@ -13,13 +13,15 @@ // limitations under the License. #include "src/anomaly/AnomalyTracker.h" -#include "../metrics/metrics_test_helper.h" #include <gtest/gtest.h> #include <math.h> #include <stdio.h> + #include <vector> +#include "tests/statsd_test_util.h" + using namespace testing; using android::sp; using std::set; @@ -147,7 +149,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}}); // Start time with no events. - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); // Event from bucket #0 occurs. @@ -158,7 +160,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds past bucket #0 anomalyTracker.addPastBucket(bucket0, 0); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); @@ -172,7 +174,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds past bucket #0 again. The sum does not change. anomalyTracker.addPastBucket(bucket0, 0); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); @@ -185,7 +187,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds past bucket #1. anomalyTracker.addPastBucket(bucket1, 1); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); @@ -199,7 +201,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds past bucket #1 again. Nothing changes. anomalyTracker.addPastBucket(bucket1, 1); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); @@ -212,7 +214,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds past bucket #2. anomalyTracker.addPastBucket(bucket2, 2); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); @@ -225,7 +227,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds bucket #3. anomalyTracker.addPastBucket(bucket3, 3L); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); @@ -238,7 +240,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds bucket #4. anomalyTracker.addPastBucket(bucket4, 4); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL); @@ -251,7 +253,7 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) { // Adds bucket #5. anomalyTracker.addPastBucket(bucket5, 5); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 5LL); @@ -292,7 +294,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { int64_t eventTimestamp6 = bucketSizeNs * 27 + 3; EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 9, bucket9, {}, {keyA, keyB, keyC, keyD})); detectAndDeclareAnomalies(anomalyTracker, 9, bucket9, eventTimestamp1); checkRefractoryTimes(anomalyTracker, eventTimestamp1, refractoryPeriodSec, @@ -301,15 +303,15 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { // Add past bucket #9 anomalyTracker.addPastBucket(bucket9, 9); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 16, bucket16, {keyB}, {keyA, keyC, keyD})); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); detectAndDeclareAnomalies(anomalyTracker, 16, bucket16, eventTimestamp2); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L); checkRefractoryTimes(anomalyTracker, eventTimestamp2, refractoryPeriodSec, {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); @@ -317,27 +319,27 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { // Add past bucket #16 anomalyTracker.addPastBucket(bucket16, 16); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 18, bucket18, {keyB}, {keyA, keyC, keyD})); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); // Within refractory period. detectAndDeclareAnomalies(anomalyTracker, 18, bucket18, eventTimestamp3); checkRefractoryTimes(anomalyTracker, eventTimestamp3, refractoryPeriodSec, {{keyA, -1}, {keyB, eventTimestamp2}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL); // Add past bucket #18 anomalyTracker.addPastBucket(bucket18, 18); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD})); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4); @@ -347,11 +349,11 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { // Add bucket #18 again. Nothing changes. anomalyTracker.addPastBucket(bucket18, 18); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 20, bucket20, {keyB}, {keyA, keyC, keyD})); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); detectAndDeclareAnomalies(anomalyTracker, 20, bucket20, eventTimestamp4 + 1); @@ -362,12 +364,12 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { // Add past bucket #20 anomalyTracker.addPastBucket(bucket20, 20); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL); EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 25, bucket25, {}, {keyA, keyB, keyC, keyD})); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); detectAndDeclareAnomalies(anomalyTracker, 25, bucket25, eventTimestamp5); checkRefractoryTimes(anomalyTracker, eventTimestamp5, refractoryPeriodSec, {{keyA, -1}, {keyB, eventTimestamp4}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); @@ -375,14 +377,14 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { // Add past bucket #25 anomalyTracker.addPastBucket(bucket25, 25); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL); EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL); EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {}, {keyA, keyB, keyC, keyD, keyE})); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, -1}}); @@ -391,9 +393,9 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) { EXPECT_TRUE(detectAnomaliesPass(anomalyTracker, 28, bucket28, {keyE}, {keyA, keyB, keyC, keyD})); EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); detectAndDeclareAnomalies(anomalyTracker, 28, bucket28, eventTimestamp6 + 7); - EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); + ASSERT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL); checkRefractoryTimes(anomalyTracker, eventTimestamp6, refractoryPeriodSec, {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, eventTimestamp6 + 7}}); } diff --git a/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp index 6529d65a5825..1d501fd5a87c 100644 --- a/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp @@ -24,6 +24,7 @@ using namespace android::os::statsd; using std::vector; #ifdef __ANDROID__ + TEST(ConditionTrackerTest, TestUnknownCondition) { LogicalOperation operation = LogicalOperation::AND; diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index e826a52c2f33..07b5311b1207 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "src/condition/SimpleConditionTracker.h" +#include "stats_event.h" #include "tests/statsd_test_util.h" #include <gmock/gmock.h> @@ -31,6 +32,8 @@ namespace android { namespace os { namespace statsd { +namespace { + const ConfigKey kConfigKey(0, 12345); const int ATTRIBUTION_NODE_FIELD_ID = 1; @@ -57,24 +60,24 @@ SimplePredicate getWakeLockHeldCondition(bool countNesting, bool defaultFalse, return simplePredicate; } -void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) { - std::vector<AttributionNodeInternal> nodes; - for (size_t i = 0; i < uids.size(); ++i) { - AttributionNodeInternal node; - node.set_uid(uids[i]); - nodes.push_back(node); - } - event->write(nodes); // attribution chain. -} +void makeWakeLockEvent(LogEvent* logEvent, uint32_t atomId, uint64_t timestamp, + const vector<int>& uids, const string& wl, int acquire) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestamp); -void makeWakeLockEvent( - LogEvent* event, const std::vector<int> &uids, const string& wl, int acquire) { - writeAttributionNodesToEvent(event, uids); - event->write(wl); - event->write(acquire); - event->init(); + vector<std::string> tags(uids.size()); // vector of empty strings + writeAttribution(statsEvent, uids, tags); + + AStatsEvent_writeString(statsEvent, wl.c_str()); + AStatsEvent_writeInt32(statsEvent, acquire); + + parseStatsEventToLogEvent(statsEvent, logEvent); } +} // anonymous namespace + + std::map<int64_t, HashableDimensionKey> getWakeLockQueryKey( const Position position, const std::vector<int> &uids, const string& conditionName) { @@ -109,6 +112,114 @@ std::map<int64_t, HashableDimensionKey> getWakeLockQueryKey( return outputKeyMap; } +TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueFalse) { + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); + simplePredicate.set_count_nesting(false); + simplePredicate.set_initial_value(SimplePredicate_InitialValue_FALSE); + + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), + 0 /*tracker index*/, simplePredicate, + trackerNameIndexMap); + + ConditionKey queryKey; + vector<sp<ConditionTracker>> allPredicates; + vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); + + // Check that initial condition is false. + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + + vector<MatchingState> matcherState; + vector<bool> changedCache(1, false); + + // Matched stop event. + // Check that condition is still false. + unique_ptr<LogEvent> screenOffEvent = + CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); // On matcher not matched + matcherState.push_back(MatchingState::kMatched); // Off matcher matched + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + EXPECT_FALSE(changedCache[0]); + + // Matched start event. + // Check that condition has changed to true. + unique_ptr<LogEvent> screenOnEvent = + CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); // On matcher matched + matcherState.push_back(MatchingState::kNotMatched); // Off matcher not matched + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); +} + +TEST(SimpleConditionTrackerTest, TestNonSlicedInitialValueUnknown) { + SimplePredicate simplePredicate; + simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); + simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); + simplePredicate.set_count_nesting(false); + simplePredicate.set_initial_value(SimplePredicate_InitialValue_UNKNOWN); + + unordered_map<int64_t, int> trackerNameIndexMap; + trackerNameIndexMap[StringToId("SCREEN_TURNED_ON")] = 0; + trackerNameIndexMap[StringToId("SCREEN_TURNED_OFF")] = 1; + + SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), + 0 /*tracker index*/, simplePredicate, + trackerNameIndexMap); + + ConditionKey queryKey; + vector<sp<ConditionTracker>> allPredicates; + vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); + + // Check that initial condition is unknown. + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); + EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]); + + vector<MatchingState> matcherState; + vector<bool> changedCache(1, false); + + // Matched stop event. + // Check that condition is changed to false. + unique_ptr<LogEvent> screenOffEvent = + CreateScreenStateChangedEvent(/*timestamp=*/50, android::view::DISPLAY_STATE_OFF); + matcherState.clear(); + matcherState.push_back(MatchingState::kNotMatched); // On matcher not matched + matcherState.push_back(MatchingState::kMatched); // Off matcher matched + conditionCache[0] = ConditionState::kNotEvaluated; + conditionTracker.evaluateCondition(*screenOffEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); + + // Matched start event. + // Check that condition has changed to true. + unique_ptr<LogEvent> screenOnEvent = + CreateScreenStateChangedEvent(/*timestamp=*/100, android::view::DISPLAY_STATE_ON); + matcherState.clear(); + matcherState.push_back(MatchingState::kMatched); // On matcher matched + matcherState.push_back(MatchingState::kNotMatched); // Off matcher not matched + conditionCache[0] = ConditionState::kNotEvaluated; + changedCache[0] = false; + conditionTracker.evaluateCondition(*screenOnEvent, matcherState, allPredicates, conditionCache, + changedCache); + EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); + EXPECT_TRUE(changedCache[0]); +} + TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { SimplePredicate simplePredicate; simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); @@ -124,7 +235,9 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { simplePredicate, trackerNameIndexMap); EXPECT_FALSE(conditionTracker.isSliced()); - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); + // This event is not accessed in this test besides dimensions which is why this is okay. + // This is technically an invalid LogEvent because we do not call parseBuffer. + LogEvent event(/*uid=*/0, /*pid=*/0); vector<MatchingState> matcherState; matcherState.push_back(MatchingState::kNotMatched); @@ -209,7 +322,9 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { trackerNameIndexMap); EXPECT_FALSE(conditionTracker.isSliced()); - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); + // This event is not accessed in this test besides dimensions which is why this is okay. + // This is technically an invalid LogEvent because we do not call parseBuffer. + LogEvent event(/*uid=*/0, /*pid=*/0); // one matched start vector<MatchingState> matcherState; @@ -266,11 +381,7 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { TEST(SimpleConditionTrackerTest, TestSlicedCondition) { std::vector<sp<ConditionTracker>> allConditions; - for (Position position : - { Position::FIRST, Position::LAST}) { - vector<Matcher> dimensionInCondition; - std::unordered_set<HashableDimensionKey> dimensionKeys; - + for (Position position : {Position::FIRST, Position::LAST}) { SimplePredicate simplePredicate = getWakeLockHeldCondition( true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, position); @@ -287,8 +398,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { std::vector<int> uids = {111, 222, 333}; - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uids, "wl1", 1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/1); // one matched start vector<MatchingState> matcherState; @@ -299,36 +410,33 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); vector<bool> changedCache(1, false); - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, changedCache); - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); } else { - EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); } EXPECT_TRUE(changedCache[0]); - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u); EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); } else { - EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), uids.size()); + EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), + uids.size()); } // Now test query const auto queryKey = getWakeLockQueryKey(position, uids, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid - LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uids, "wl2", 1); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/1); matcherState.clear(); matcherState.push_back(MatchingState::kMatched); matcherState.push_back(MatchingState::kNotMatched); @@ -337,19 +445,18 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache); EXPECT_FALSE(changedCache[0]); - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); } else { - EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); } EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); // wake lock 1 release - LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event3, uids, "wl1", 0); // now release it. + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/0); matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -359,17 +466,16 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { changedCache); // nothing changes, because wake lock 2 is still held for this uid EXPECT_FALSE(changedCache[0]); - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); } else { - EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size()); } EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); - LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event4, uids, "wl2", 0); // now release it. + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids, "wl2", /*acquire=*/0); matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -377,21 +483,19 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { changedCache[0] = false; conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, changedCache); - EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); EXPECT_TRUE(changedCache[0]); - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u); EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); } else { - EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), uids.size()); + EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), + uids.size()); } // query again conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } @@ -399,12 +503,10 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { std::vector<sp<ConditionTracker>> allConditions; - vector<Matcher> dimensionInCondition; - std::unordered_set<HashableDimensionKey> dimensionKeys; - SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, false /*slice output by uid*/, - Position::ANY /* position */); + SimplePredicate simplePredicate = + getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/, + false /*slice output by uid*/, Position::ANY /* position */); string conditionName = "WL_HELD"; unordered_map<int64_t, int> trackerNameIndexMap; @@ -418,13 +520,13 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { EXPECT_FALSE(conditionTracker.isSliced()); - std::vector<int> uid_list1 = {111, 1111, 11111}; + std::vector<int> uids1 = {111, 1111, 11111}; string uid1_wl1 = "wl1_1"; - std::vector<int> uid_list2 = {222, 2222, 22222}; + std::vector<int> uids2 = {222, 2222, 22222}; string uid2_wl1 = "wl2_1"; - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid_list1, uid1_wl1, 1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1, /*acquire=*/1); // one matched start for uid1 vector<MatchingState> matcherState; @@ -435,24 +537,23 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); vector<bool> changedCache(1, false); - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, changedCache); - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); EXPECT_TRUE(changedCache[0]); // Now test query ConditionKey queryKey; conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - true, true, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by this uid - LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid_list2, uid2_wl1, 1); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1, /*acquire=*/1); + matcherState.clear(); matcherState.push_back(MatchingState::kMatched); matcherState.push_back(MatchingState::kNotMatched); @@ -463,8 +564,10 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { EXPECT_FALSE(changedCache[0]); // uid1 wake lock 1 release - LogEvent event3(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event3, uid_list1, uid1_wl1, 0); // now release it. + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids1, uid1_wl1, + /*release=*/0); // now release it. + matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -475,8 +578,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { // nothing changes, because uid2 is still holding wl. EXPECT_FALSE(changedCache[0]); - LogEvent event4(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event4, uid_list2, uid2_wl1, 0); // now release it. + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event4, /*atomId=*/1, /*timestamp=*/0, uids2, uid2_wl1, + /*acquire=*/0); // now release it. matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kMatched); @@ -484,27 +588,21 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { changedCache[0] = false; conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache, changedCache); - EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); EXPECT_TRUE(changedCache[0]); // query again conditionCache[0] = ConditionState::kNotEvaluated; - dimensionKeys.clear(); - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - true, true, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, true, conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } TEST(SimpleConditionTrackerTest, TestStopAll) { std::vector<sp<ConditionTracker>> allConditions; - for (Position position : - { Position::FIRST, Position::LAST }) { - vector<Matcher> dimensionInCondition; - std::unordered_set<HashableDimensionKey> dimensionKeys; - SimplePredicate simplePredicate = getWakeLockHeldCondition( - true /*nesting*/, true /*default to false*/, true /*output slice by uid*/, - position); + for (Position position : {Position::FIRST, Position::LAST}) { + SimplePredicate simplePredicate = + getWakeLockHeldCondition(true /*nesting*/, true /*default to false*/, + true /*output slice by uid*/, position); string conditionName = "WL_HELD_BY_UID3"; unordered_map<int64_t, int> trackerNameIndexMap; @@ -516,11 +614,11 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { 0 /*condition tracker index*/, simplePredicate, trackerNameIndexMap); - std::vector<int> uid_list1 = {111, 1111, 11111}; - std::vector<int> uid_list2 = {222, 2222, 22222}; + std::vector<int> uids1 = {111, 1111, 11111}; + std::vector<int> uids2 = {222, 2222, 22222}; - LogEvent event(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event, uid_list1, "wl1", 1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event1, /*atomId=*/1, /*timestamp=*/0, uids1, "wl1", /*acquire=*/1); // one matched start vector<MatchingState> matcherState; @@ -531,38 +629,36 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); vector<bool> changedCache(1, false); - conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, + conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache, changedCache); - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.mSlicedConditionState.size()); } else { - EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(uids1.size(), conditionTracker.mSlicedConditionState.size()); } EXPECT_TRUE(changedCache[0]); { - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); } else { - EXPECT_EQ(uid_list1.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + EXPECT_EQ(uids1.size(), + conditionTracker.getChangedToTrueDimensions(allConditions)->size()); EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); } } // Now test query - const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName); + const auto queryKey = getWakeLockQueryKey(position, uids1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); // another wake lock acquired by uid2 - LogEvent event2(1 /*tagId*/, 0 /*timestamp*/); - makeWakeLockEvent(&event2, uid_list2, "wl2", 1); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event2, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1); + matcherState.clear(); matcherState.push_back(MatchingState::kMatched); matcherState.push_back(MatchingState::kNotMatched); @@ -571,38 +667,34 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { changedCache[0] = false; conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache); - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(2UL, conditionTracker.mSlicedConditionState.size()); } else { - EXPECT_EQ(uid_list1.size() + uid_list2.size(), - conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(uids1.size() + uids2.size(), conditionTracker.mSlicedConditionState.size()); } EXPECT_TRUE(changedCache[0]); { - if (position == Position::FIRST || - position == Position::LAST) { - EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + if (position == Position::FIRST || position == Position::LAST) { + ASSERT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size()); EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); } else { - EXPECT_EQ(uid_list2.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size()); + EXPECT_EQ(uids2.size(), + conditionTracker.getChangedToTrueDimensions(allConditions)->size()); EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty()); } } - // TEST QUERY - const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName); + const auto queryKey2 = getWakeLockQueryKey(position, uids2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); - // stop all event - LogEvent event3(2 /*tagId*/, 0 /*timestamp*/); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids2, "wl2", /*acquire=*/1); + matcherState.clear(); matcherState.push_back(MatchingState::kNotMatched); matcherState.push_back(MatchingState::kNotMatched); @@ -613,32 +705,28 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache); EXPECT_TRUE(changedCache[0]); - EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); + ASSERT_EQ(0UL, conditionTracker.mSlicedConditionState.size()); { if (position == Position::FIRST || position == Position::LAST) { - EXPECT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size()); + ASSERT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size()); EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); } else { - EXPECT_EQ(uid_list1.size() + uid_list2.size(), + EXPECT_EQ(uids1.size() + uids2.size(), conditionTracker.getChangedToFalseDimensions(allConditions)->size()); EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty()); } } // TEST QUERY - const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName); + const auto queryKey3 = getWakeLockQueryKey(position, uids1, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); // TEST QUERY - const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName); + const auto queryKey4 = getWakeLockQueryKey(position, uids2, conditionName); conditionCache[0] = ConditionState::kNotEvaluated; - conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, - false, false, - conditionCache, dimensionKeys); + conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache); EXPECT_EQ(ConditionState::kFalse, conditionCache[0]); } } diff --git a/cmds/statsd/tests/condition/StateTracker_test.cpp b/cmds/statsd/tests/condition/StateTracker_test.cpp deleted file mode 100644 index 9a66254afce0..000000000000 --- a/cmds/statsd/tests/condition/StateTracker_test.cpp +++ /dev/null @@ -1,112 +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. - -#include "src/condition/StateTracker.h" -#include "tests/statsd_test_util.h" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <stdio.h> -#include <numeric> -#include <vector> - -using std::map; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ -namespace android { -namespace os { -namespace statsd { - -const int kUidProcTag = 27; - -SimplePredicate getUidProcStatePredicate() { - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("UidProcState")); - - simplePredicate.mutable_dimensions()->set_field(kUidProcTag); - simplePredicate.mutable_dimensions()->add_child()->set_field(1); - simplePredicate.mutable_dimensions()->add_child()->set_field(2); - - simplePredicate.set_count_nesting(false); - return simplePredicate; -} - -void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) { - event->write(uid); - event->write(state); - event->init(); -} - -TEST(StateTrackerTest, TestStateChange) { - int uid1 = 111; - int uid2 = 222; - - int state1 = 1001; - int state2 = 1002; - unordered_map<int64_t, int> trackerNameIndexMap; - trackerNameIndexMap[StringToId("UidProcState")] = 0; - vector<Matcher> primaryFields; - primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1)); - StateTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(), - trackerNameIndexMap, primaryFields); - - LogEvent event(kUidProcTag, 0 /*timestamp*/); - makeUidProcStateEvent(uid1, state1, &event); - - vector<MatchingState> matcherState; - matcherState.push_back(MatchingState::kMatched); - vector<sp<ConditionTracker>> allPredicates; - vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); - vector<bool> changedCache(1, false); - - tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); - EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); - EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); - EXPECT_TRUE(changedCache[0]); - - changedCache[0] = false; - conditionCache[0] = ConditionState::kNotEvaluated; - tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); - EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size()); - EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); - EXPECT_FALSE(changedCache[0]); - - LogEvent event2(kUidProcTag, 0 /*timestamp*/); - makeUidProcStateEvent(uid1, state2, &event2); - - changedCache[0] = false; - conditionCache[0] = ConditionState::kNotEvaluated; - tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache); - EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); - EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size()); - EXPECT_TRUE(changedCache[0]); - - LogEvent event3(kUidProcTag, 0 /*timestamp*/); - makeUidProcStateEvent(uid2, state1, &event3); - changedCache[0] = false; - conditionCache[0] = ConditionState::kNotEvaluated; - tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache); - EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); - EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); - EXPECT_TRUE(changedCache[0]); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp b/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp index 9ea0b8109209..93b278388a1b 100644 --- a/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp @@ -52,9 +52,9 @@ TEST(AlarmE2eTest, TestMultipleAlarms) { ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - EXPECT_EQ(2u, processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers.size()); + ASSERT_EQ(2u, processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers.size()); auto alarmTracker1 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[0]; auto alarmTracker2 = processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers[1]; @@ -68,7 +68,7 @@ TEST(AlarmE2eTest, TestMultipleAlarms) { const int64_t alarmFiredTimestampSec0 = alarmTimestampSec1 + 5; auto alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan( static_cast<uint32_t>(alarmFiredTimestampSec0)); - EXPECT_EQ(1u, alarmSet.size()); + ASSERT_EQ(1u, alarmSet.size()); processor->onPeriodicAlarmFired(alarmFiredTimestampSec0 * NS_PER_SEC, alarmSet); EXPECT_EQ(alarmTimestampSec0, alarmTracker1->getAlarmTimestampSec()); EXPECT_EQ(alarmTimestampSec1 + 30 * 60, alarmTracker2->getAlarmTimestampSec()); @@ -77,7 +77,7 @@ TEST(AlarmE2eTest, TestMultipleAlarms) { const int64_t alarmFiredTimestampSec1 = alarmTimestampSec0 + 2 * 60 * 60 + 125; alarmSet = processor->getPeriodicAlarmMonitor()->popSoonerThan( static_cast<uint32_t>(alarmFiredTimestampSec1)); - EXPECT_EQ(2u, alarmSet.size()); + ASSERT_EQ(2u, alarmSet.size()); processor->onPeriodicAlarmFired(alarmFiredTimestampSec1 * NS_PER_SEC, alarmSet); EXPECT_EQ(alarmTimestampSec0 + 60 * 60 * 3, alarmTracker1->getAlarmTimestampSec()); EXPECT_EQ(alarmTimestampSec1 + 30 * 60 * 5, alarmTracker2->getAlarmTimestampSec()); diff --git a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp index c78d99e768f7..af9436b98ec8 100644 --- a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp @@ -14,6 +14,7 @@ #include <gtest/gtest.h> +#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" #include "src/StatsLogProcessor.h" #include "src/stats_log_util.h" #include "tests/statsd_test_util.h" @@ -28,7 +29,7 @@ namespace statsd { namespace { -StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) { +StatsdConfig CreateStatsdConfig(int num_buckets, int threshold, int refractory_period_sec) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher(); @@ -39,14 +40,14 @@ StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) { countMetric->set_id(123456); countMetric->set_what(wakelockAcquireMatcher.id()); *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); countMetric->set_bucket(FIVE_MINUTES); auto alert = config.add_alert(); alert->set_id(StringToId("alert")); alert->set_metric_id(123456); alert->set_num_buckets(num_buckets); - alert->set_refractory_period_secs(10); + alert->set_refractory_period_secs(refractory_period_sec); alert->set_trigger_if_sum_gt(threshold); return config; } @@ -56,101 +57,115 @@ StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) { TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) { const int num_buckets = 1; const int threshold = 3; - auto config = CreateStatsdConfig(num_buckets, threshold); + const int refractory_period_sec = 10; + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); const uint64_t alert_id = config.alert(0).id(); - const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); sp<AnomalyTracker> anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")}; - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(111, "App1"), CreateAttribution(333, "App3")}; - std::vector<AttributionNodeInternal> attributions4 = { - CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")}; - std::vector<AttributionNodeInternal> attributions5 = { - CreateAttribution(222, "GMSCoreModule1") }; - - FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector<int> attributionUids1 = {111}; + std::vector<string> attributionTags1 = {"App1"}; + std::vector<int> attributionUids2 = {111, 222}; + std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"}; + std::vector<int> attributionUids3 = {111, 333}; + std::vector<string> attributionTags3 = {"App1", "App3"}; + std::vector<int> attributionUids4 = {222, 333}; + std::vector<string> attributionTags4 = {"GMSCoreModule1", "App3"}; + std::vector<int> attributionUids5 = {222}; + std::vector<string> attributionTags5 = {"GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), Value((int32_t)111)); HashableDimensionKey whatKey1({fieldValue1}); MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), Value((int32_t)222)); HashableDimensionKey whatKey2({fieldValue2}); MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2); + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions4, "wl2", bucketStartTimeNs + 2); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids4, attributionTags4, + "wl2"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 3); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2, + "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + 3); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids5, attributionTags5, + "wl2"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - event = CreateAcquireWakelockEvent(attributions3, "wl1", bucketStartTimeNs + 4); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids3, attributionTags3, + "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + 4); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids5, attributionTags5, + "wl2"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); // Fired alarm and refractory period end timestamp updated. - event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 5); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 5, attributionUids1, attributionTags1, + "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 100); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 100, attributionUids1, attributionTags1, + "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + bucketStartTimeNs / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 1); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs - 1) / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions4, "wl2", bucketStartTimeNs + bucketSizeNs + 1); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids4, + attributionTags4, "wl2"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 2); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids5, + attributionTags5, "wl2"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 3); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 3, attributionUids5, + attributionTags5, "wl2"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); - event = CreateAcquireWakelockEvent(attributions5, "wl2", bucketStartTimeNs + bucketSizeNs + 4); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 4, attributionUids5, + attributionTags5, "wl2"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 4) / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); @@ -159,79 +174,213 @@ TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) { TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) { const int num_buckets = 3; const int threshold = 3; - auto config = CreateStatsdConfig(num_buckets, threshold); + const int refractory_period_sec = 10; + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); const uint64_t alert_id = config.alert(0).id(); - const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); sp<AnomalyTracker> anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; - - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1")}; - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(111, "App1"), CreateAttribution(333, "App3")}; - std::vector<AttributionNodeInternal> attributions4 = { - CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")}; - std::vector<AttributionNodeInternal> attributions5 = { - CreateAttribution(222, "GMSCoreModule1") }; - - FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector<int> attributionUids1 = {111}; + std::vector<string> attributionTags1 = {"App1"}; + std::vector<int> attributionUids2 = {111, 222}; + std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), Value((int32_t)111)); HashableDimensionKey whatKey1({fieldValue1}); MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), - Value((int32_t)222)); - HashableDimensionKey whatKey2({fieldValue2}); - MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); - - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2); + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 3); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3, attributionUids2, attributionTags2, + "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Fired alarm and refractory period end timestamp updated. - event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 4); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 4, attributionUids1, attributionTags1, + "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 2); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids2, + attributionTags2, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 3 * bucketSizeNs + 1); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, attributionUids2, + attributionTags2, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + 1) / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - event = CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 3 * bucketSizeNs + 2); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 2, attributionUids2, + attributionTags2, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 3 * bucketSizeNs + 2) / NS_PER_SEC + 1, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); } +TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written) { + const int num_buckets = 1; + const int threshold = 0; + const int refractory_period_sec = 86400 * 365; // 1 year + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const int64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + + int configUid = 2000; + int64_t configId = 1000; + ConfigKey cfgKey(configUid, configId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + metadata::StatsMetadataList result; + int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; + int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; + processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result); + + ASSERT_EQ(result.stats_metadata_size(), 0); +} + +TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk) { + const int num_buckets = 1; + const int threshold = 0; + const int refractory_period_sec = 86400 * 365; // 1 year + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const int64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + + int configUid = 2000; + int64_t configId = 1000; + ConfigKey cfgKey(configUid, configId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp<AnomalyTracker> anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector<int> attributionUids1 = {111}; + std::vector<string> attributionTags1 = {"App1"}; + std::vector<int> attributionUids2 = {111, 222}; + std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + metadata::StatsMetadataList result; + int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; + int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; + processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result); + + metadata::StatsMetadata statsMetadata = result.stats_metadata(0); + ASSERT_EQ(result.stats_metadata_size(), 1); + EXPECT_EQ(statsMetadata.config_key().config_id(), configId); + EXPECT_EQ(statsMetadata.config_key().uid(), configUid); + + metadata::AlertMetadata alertMetadata = statsMetadata.alert_metadata(0); + ASSERT_EQ(statsMetadata.alert_metadata_size(), 1); + EXPECT_EQ(alertMetadata.alert_id(), alert_id); + metadata::AlertDimensionKeyedData keyedData = alertMetadata.alert_dim_keyed_data(0); + ASSERT_EQ(alertMetadata.alert_dim_keyed_data_size(), 1); + EXPECT_EQ(keyedData.last_refractory_ends_sec(), + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) - + mockElapsedTimeNs / NS_PER_SEC + + mockWallClockNs / NS_PER_SEC); + + metadata::MetricDimensionKey metadataDimKey = keyedData.dimension_key(); + metadata::FieldValue dimKeyInWhat = metadataDimKey.dimension_key_in_what(0); + EXPECT_EQ(dimKeyInWhat.field().tag(), fieldValue1.mField.getTag()); + EXPECT_EQ(dimKeyInWhat.field().field(), fieldValue1.mField.getField()); + EXPECT_EQ(dimKeyInWhat.value_int(), fieldValue1.mValue.int_value); +} + +TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk) { + const int num_buckets = 1; + const int threshold = 0; + const int refractory_period_sec = 86400 * 365; // 1 year + auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec); + const int64_t alert_id = config.alert(0).id(); + + int64_t bucketStartTimeNs = 10000000000; + + int configUid = 2000; + int64_t configId = 1000; + ConfigKey cfgKey(configUid, configId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + + sp<AnomalyTracker> anomalyTracker = + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + + std::vector<int> attributionUids1 = {111}; + std::vector<string> attributionTags1 = {"App1"}; + std::vector<int> attributionUids2 = {111, 222}; + std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"}; + + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + Value((int32_t)111)); + HashableDimensionKey whatKey1({fieldValue1}); + MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); + + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + processor->OnLogEvent(event.get()); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + int64_t mockWallClockNs = 1584991200 * NS_PER_SEC; + int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC; + processor->SaveMetadataToDisk(mockWallClockNs, mockElapsedTimeNs); + + auto processor2 = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + int64_t mockElapsedTimeSinceBoot = 10 * NS_PER_SEC; + processor2->LoadMetadataFromDisk(mockWallClockNs, mockElapsedTimeSinceBoot); + + sp<AnomalyTracker> anomalyTracker2 = + processor2->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + EXPECT_EQ(anomalyTracker2->getRefractoryPeriodEndsSec(dimensionKey1) - + mockElapsedTimeSinceBoot / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) - + mockElapsedTimeNs / NS_PER_SEC); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp index 50da9e2d48b1..95e301002a1b 100644 --- a/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp @@ -45,7 +45,7 @@ StatsdConfig CreateStatsdConfig(int num_buckets, auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); FieldMatcher dimensions = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; holdingWakelockPredicate.mutable_simple_predicate()->set_count_nesting(nesting); @@ -57,7 +57,7 @@ StatsdConfig CreateStatsdConfig(int num_buckets, durationMetric->set_condition(screenIsOffPredicate.id()); durationMetric->set_aggregation_type(aggregationType); *durationMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions(android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); durationMetric->set_bucket(FIVE_MINUTES); auto alert = config.add_alert(); @@ -69,26 +69,28 @@ StatsdConfig CreateStatsdConfig(int num_buckets, return config; } -} // namespace - -std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1")}; +std::vector<int> attributionUids1 = {111, 222}; +std::vector<string> attributionTags1 = {"App1", "GMSCoreModule1"}; -std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App2"), - CreateAttribution(222, "GMSCoreModule1")}; +std::vector<int> attributionUids2 = {111, 222}; +std::vector<string> attributionTags2 = {"App2", "GMSCoreModule1"}; -std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1")}; +std::vector<int> attributionUids3 = {222}; +std::vector<string> attributionTags3 = {"GMSCoreModule1"}; -MetricDimensionKey dimensionKey( - HashableDimensionKey({FieldValue(Field(android::util::WAKELOCK_STATE_CHANGED, - (int32_t)0x02010101), Value((int32_t)111))}), - DEFAULT_DIMENSION_KEY); +MetricDimensionKey dimensionKey1( + HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED, + (int32_t)0x02010101), + Value((int32_t)111))}), + DEFAULT_DIMENSION_KEY); MetricDimensionKey dimensionKey2( - HashableDimensionKey({FieldValue(Field(android::util::WAKELOCK_STATE_CHANGED, + HashableDimensionKey({FieldValue(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), Value((int32_t)222))}), DEFAULT_DIMENSION_KEY); +} // namespace + TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket) { const int num_buckets = 1; const uint64_t threshold_ns = NS_PER_SEC; @@ -98,172 +100,176 @@ TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket) { int64_t bucketStartTimeNs = 10 * NS_PER_SEC; int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); sp<AnomalyTracker> anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; auto screen_on_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1); + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); auto screen_off_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 10); + bucketStartTimeNs + 10, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); processor->OnLogEvent(screen_on_event.get()); processor->OnLogEvent(screen_off_event.get()); // Acquire wakelock wl1. - auto acquire_event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 11); + auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 11, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + 11 + threshold_ns) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Release wakelock wl1. No anomaly detected. Alarm cancelled at the "release" event. - auto release_event = CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 101); + auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 101, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Acquire wakelock wl1 within bucket #0. - acquire_event = CreateAcquireWakelockEvent(attributions2, "wl1", bucketStartTimeNs + 110); + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 110, attributionUids2, + attributionTags2, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + 110 + threshold_ns - 90) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Release wakelock wl1. One anomaly detected. - release_event = CreateReleaseWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + NS_PER_SEC + 109); + release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 109, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Acquire wakelock wl1. - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + NS_PER_SEC + 112); + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + NS_PER_SEC + 112, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); // Wakelock has been hold longer than the threshold in bucket #0. The alarm is set at the // end of the refractory period. - const int64_t alarmFiredTimestampSec0 = anomalyTracker->getAlarmTimestampSec(dimensionKey); + const int64_t alarmFiredTimestampSec0 = anomalyTracker->getAlarmTimestampSec(dimensionKey1); EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + NS_PER_SEC + 109) / NS_PER_SEC + 1, (uint32_t)alarmFiredTimestampSec0); // Anomaly alarm fired. auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( static_cast<uint32_t>(alarmFiredTimestampSec0)); - EXPECT_EQ(1u, alarmSet.size()); + ASSERT_EQ(1u, alarmSet.size()); processor->onAnomalyAlarmFired(alarmFiredTimestampSec0 * NS_PER_SEC, alarmSet); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Release wakelock wl1. - release_event = CreateReleaseWakelockEvent( - attributions1, "wl1", alarmFiredTimestampSec0 * NS_PER_SEC + NS_PER_SEC + 1); + release_event = + CreateReleaseWakelockEvent(alarmFiredTimestampSec0 * NS_PER_SEC + NS_PER_SEC + 1, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); // Within refractory period. No more anomaly detected. EXPECT_EQ(refractory_period_sec + alarmFiredTimestampSec0, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Acquire wakelock wl1. - acquire_event = CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC - 11); + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC - 11, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(acquire_event.get()); - const int64_t alarmFiredTimestampSec1 = anomalyTracker->getAlarmTimestampSec(dimensionKey); + const int64_t alarmFiredTimestampSec1 = anomalyTracker->getAlarmTimestampSec(dimensionKey1); EXPECT_EQ((bucketStartTimeNs + bucketSizeNs - 5 * NS_PER_SEC) / NS_PER_SEC, (uint64_t)alarmFiredTimestampSec1); // Release wakelock wl1. - release_event = CreateReleaseWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10); + release_event = + CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + (bucketStartTimeNs + bucketSizeNs - 4 * NS_PER_SEC - 10) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( - static_cast<uint32_t>(alarmFiredTimestampSec1)); - EXPECT_EQ(0u, alarmSet.size()); + static_cast<uint32_t>(alarmFiredTimestampSec1)); + ASSERT_EQ(0u, alarmSet.size()); // Acquire wakelock wl1 near the end of bucket #0. - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 2); + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 2, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); // Release the event at early bucket #1. - release_event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + NS_PER_SEC - 1); + release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC - 1, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); // Anomaly detected when stopping the alarm. The refractory period does not change. - EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Condition changes to false. - screen_on_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + 2 * bucketSizeNs + 20); + screen_on_event = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 20, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); processor->OnLogEvent(screen_on_event.get()); - EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - acquire_event = CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 30); + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 30, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(acquire_event.get()); // The condition is false. Do not start the alarm. - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + bucketSizeNs + NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Condition turns true. - screen_off_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC); + screen_off_event = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); processor->OnLogEvent(screen_off_event.get()); EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs + NS_PER_SEC + threshold_ns) / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); // Condition turns to false. - screen_on_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1); + screen_on_event = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); processor->OnLogEvent(screen_on_event.get()); // Condition turns to false. Cancelled the alarm. - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); // Detected one anomaly. EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 1) / NS_PER_SEC + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Condition turns to true again. - screen_off_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 2); + screen_off_event = + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC + 2, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); processor->OnLogEvent(screen_off_event.get()); EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 2 + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - release_event = CreateReleaseWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC); + release_event = + CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(release_event.get()); EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC) / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + (bucketStartTimeNs + 2 * bucketSizeNs + 5 * NS_PER_SEC) / NS_PER_SEC, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); } TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets) { @@ -275,107 +281,115 @@ TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets) { int64_t bucketStartTimeNs = 10 * NS_PER_SEC; int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); sp<AnomalyTracker> anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; auto screen_off_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 1); + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); processor->OnLogEvent(screen_off_event.get()); // Acquire wakelock "wc1" in bucket #0. - auto acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - NS_PER_SEC / 2 - 1); + auto acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - NS_PER_SEC / 2 - 1, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Release wakelock "wc1" in bucket #0. - auto release_event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 1); + auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Acquire wakelock "wc1" in bucket #1. - acquire_event = CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 1); + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 1, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - release_event = CreateReleaseWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + bucketSizeNs + 100); + release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + 100, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Acquire wakelock "wc2" in bucket #2. - acquire_event = CreateAcquireWakelockEvent( - attributions3, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1); + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, + attributionUids3, attributionTags3, "wl2"); processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2, + EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); // Release wakelock "wc2" in bucket #2. - release_event = CreateReleaseWakelockEvent( - attributions3, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC); + release_event = + CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC, + attributionUids3, attributionTags3, "wl2"); processor->OnLogEvent(release_event.get()); EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); EXPECT_EQ(refractory_period_sec + - (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC) / NS_PER_SEC, + (bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC) / NS_PER_SEC, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey2)); // Acquire wakelock "wc1" in bucket #2. - acquire_event = CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC); + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + 2 * bucketSizeNs) / NS_PER_SEC + 2 + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Release wakelock "wc1" in bucket #2. - release_event = CreateReleaseWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC); + release_event = + CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC, + attributionUids2, attributionTags2, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ(refractory_period_sec + - (int64_t)(bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); - - acquire_event = CreateAcquireWakelockEvent( - attributions3, "wl2", bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 4); + (int64_t)(bucketStartTimeNs + 2 * bucketSizeNs + 2.5 * NS_PER_SEC) / + NS_PER_SEC + + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); + + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 4, + attributionUids3, attributionTags3, "wl2"); processor->OnLogEvent(acquire_event.get()); - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 5); + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs - NS_PER_SEC + 5, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ((bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); - release_event = CreateReleaseWakelockEvent( - attributions3, "wl2", bucketStartTimeNs + 6 * bucketSizeNs + 2); + release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs + 2, + attributionUids3, attributionTags3, "wl2"); processor->OnLogEvent(release_event.get()); - release_event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 6 * bucketSizeNs + 6); + release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 6 * bucketSizeNs + 6, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey2)); // The buckets are not messed up across dimensions. Only one dimension has anomaly triggered. - EXPECT_EQ(refractory_period_sec + - (int64_t)(bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + 1, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + EXPECT_EQ(refractory_period_sec + (int64_t)(bucketStartTimeNs + 6 * bucketSizeNs) / NS_PER_SEC + + 1, + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); } TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) { @@ -384,7 +398,7 @@ TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) { auto config = CreateStatsdConfig(num_buckets, threshold_ns, DurationMetric::SUM, false); int64_t bucketStartTimeNs = 10 * NS_PER_SEC; int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000; const uint64_t alert_id = config.alert(0).id(); const uint32_t refractory_period_sec = 3 * bucketSizeNs / NS_PER_SEC; @@ -392,89 +406,94 @@ TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period) { ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); + ASSERT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size()); sp<AnomalyTracker> anomalyTracker = - processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; + processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0]; auto screen_off_event = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 1); + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); processor->OnLogEvent(screen_off_event.get()); // Acquire wakelock "wc1" in bucket #0. - auto acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + bucketSizeNs - 100); + auto acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 100, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Acquire the wakelock "wc1" again. - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2 * NS_PER_SEC + 1); + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2 * NS_PER_SEC + 1, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); // The alarm does not change. EXPECT_EQ((bucketStartTimeNs + bucketSizeNs) / NS_PER_SEC + 3, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); - EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); + EXPECT_EQ(0u, anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // Anomaly alarm fired late. const int64_t firedAlarmTimestampNs = bucketStartTimeNs + 2 * bucketSizeNs - NS_PER_SEC; auto alarmSet = processor->getAnomalyAlarmMonitor()->popSoonerThan( static_cast<uint32_t>(firedAlarmTimestampNs / NS_PER_SEC)); - EXPECT_EQ(1u, alarmSet.size()); + ASSERT_EQ(1u, alarmSet.size()); processor->onAnomalyAlarmFired(firedAlarmTimestampNs, alarmSet); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs - 100); + acquire_event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - auto release_event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 1); + auto release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); // Within the refractory period. No anomaly. EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); // A new wakelock, but still within refractory period. - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 2 * bucketSizeNs + 10 * NS_PER_SEC); + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 10 * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - release_event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 3 * bucketSizeNs - NS_PER_SEC); + release_event = CreateReleaseWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - NS_PER_SEC, + attributionUids1, attributionTags1, "wl1"); // Still in the refractory period. No anomaly. processor->OnLogEvent(release_event.get()); EXPECT_EQ(refractory_period_sec + firedAlarmTimestampNs / NS_PER_SEC, - anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey)); + anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1)); - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 5); + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 5, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - release_event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 4); + release_event = + CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 4, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(release_event.get()); - EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey)); + EXPECT_EQ(0u, anomalyTracker->getAlarmTimestampSec(dimensionKey1)); - acquire_event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 3); + acquire_event = + CreateAcquireWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs - 3 * NS_PER_SEC - 3, + attributionUids1, attributionTags1, "wl1"); processor->OnLogEvent(acquire_event.get()); EXPECT_EQ((bucketStartTimeNs + 5 * bucketSizeNs) / NS_PER_SEC, - anomalyTracker->getAlarmTimestampSec(dimensionKey)); + anomalyTracker->getAlarmTimestampSec(dimensionKey1)); } #else diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp index 33825250446a..4c2caa904f6a 100644 --- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp @@ -48,22 +48,60 @@ StatsdConfig CreateStatsdConfig(const Position position) { countMetric->set_what(wakelockAcquireMatcher.id()); *countMetric->mutable_dimensions_in_what() = CreateAttributionUidAndTagDimensions( - android::util::WAKELOCK_STATE_CHANGED, {position}); + util::WAKELOCK_STATE_CHANGED, {position}); countMetric->set_bucket(FIVE_MINUTES); return config; } +// GMS core node is in the middle. +std::vector<int> attributionUids1 = {111, 222, 333}; +std::vector<string> attributionTags1 = {"App1", "GMSCoreModule1", "App3"}; + +// GMS core node is the last one. +std::vector<int> attributionUids2 = {111, 333, 222}; +std::vector<string> attributionTags2 = {"App1", "App3", "GMSCoreModule1"}; + +// GMS core node is the first one. +std::vector<int> attributionUids3 = {222, 333}; +std::vector<string> attributionTags3 = {"GMSCoreModule1", "App3"}; + +// Single GMS core node. +std::vector<int> attributionUids4 = {222}; +std::vector<string> attributionTags4 = {"GMSCoreModule1"}; + +// GMS core has another uid. +std::vector<int> attributionUids5 = {111, 444, 333}; +std::vector<string> attributionTags5 = {"App1", "GMSCoreModule2", "App3"}; + +// Multiple GMS core nodes. +std::vector<int> attributionUids6 = {444, 222}; +std::vector<string> attributionTags6 = {"GMSCoreModule2", "GMSCoreModule1"}; + +// No GMS core nodes +std::vector<int> attributionUids7 = {111, 333}; +std::vector<string> attributionTags7 = {"App1", "App3"}; + +std::vector<int> attributionUids8 = {111}; +std::vector<string> attributionTags8 = {"App1"}; + +// GMS core node with isolated uid. +const int isolatedUid = 666; +std::vector<int> attributionUids9 = {isolatedUid}; +std::vector<string> attributionTags9 = {"GMSCoreModule3"}; + +std::vector<int> attributionUids10 = {isolatedUid}; +std::vector<string> attributionTags10 = {"GMSCoreModule1"}; + } // namespace TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) { auto config = CreateStatsdConfig(Position::FIRST); int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); // Here it assumes that GMS core has two uids. @@ -74,70 +112,34 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) { String16("APP3")}, {String16(""), String16(""), String16(""), String16("")}); - // GMS core node is in the middle. - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(333, "App3")}; - - // GMS core node is the last one. - std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App1"), - CreateAttribution(333, "App3"), - CreateAttribution(222, "GMSCoreModule1")}; - - // GMS core node is the first one. - std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(333, "App3")}; - - // Single GMS core node. - std::vector<AttributionNodeInternal> attributions4 = {CreateAttribution(222, "GMSCoreModule1")}; - - // GMS core has another uid. - std::vector<AttributionNodeInternal> attributions5 = {CreateAttribution(111, "App1"), - CreateAttribution(444, "GMSCoreModule2"), - CreateAttribution(333, "App3")}; - - // Multiple GMS core nodes. - std::vector<AttributionNodeInternal> attributions6 = {CreateAttribution(444, "GMSCoreModule2"), - CreateAttribution(222, "GMSCoreModule1")}; - - // No GMS core nodes. - std::vector<AttributionNodeInternal> attributions7 = {CreateAttribution(111, "App1"), - CreateAttribution(333, "App3")}; - std::vector<AttributionNodeInternal> attributions8 = {CreateAttribution(111, "App1")}; - - // GMS core node with isolated uid. - const int isolatedUid = 666; - std::vector<AttributionNodeInternal> attributions9 = { - CreateAttribution(isolatedUid, "GMSCoreModule3")}; - std::vector<std::unique_ptr<LogEvent>> events; // Events 1~4 are in the 1st bucket. - events.push_back(CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 2)); - events.push_back(CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 200)); - events.push_back(CreateAcquireWakelockEvent( - attributions3, "wl1", bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateAcquireWakelockEvent( - attributions4, "wl1", bucketStartTimeNs + bucketSizeNs)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2, + attributionTags2, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, + attributionUids3, attributionTags3, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4, + attributionTags4, "wl1")); // Events 5~8 are in the 3rd bucket. - events.push_back(CreateAcquireWakelockEvent( - attributions5, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1)); - events.push_back(CreateAcquireWakelockEvent( - attributions6, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 100)); - events.push_back(CreateAcquireWakelockEvent( - attributions7, "wl2", bucketStartTimeNs + 3 * bucketSizeNs - 2)); - events.push_back(CreateAcquireWakelockEvent( - attributions8, "wl2", bucketStartTimeNs + 3 * bucketSizeNs)); - events.push_back(CreateAcquireWakelockEvent( - attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 1)); - events.push_back(CreateAcquireWakelockEvent( - attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 100)); - events.push_back(CreateIsolatedUidChangedEvent( - isolatedUid, 222, true/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs - 1)); - events.push_back(CreateIsolatedUidChangedEvent( - isolatedUid, 222, false/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs + 10)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, + attributionUids5, attributionTags5, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids6, attributionTags6, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2, + attributionUids7, attributionTags7, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs, + attributionUids8, attributionTags8, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, + attributionUids9, attributionTags9, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100, + attributionUids9, attributionTags9, "wl2")); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222, + isolatedUid, true /*is_create*/)); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222, + isolatedUid, false /*is_create*/)); sortLogEventsByTimestamp(&events); @@ -146,37 +148,37 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) { } ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); StatsLogReport::CountMetricDataWrapper countMetrics; sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - EXPECT_EQ(countMetrics.data_size(), 4); + ASSERT_EQ(countMetrics.data_size(), 4); auto data = countMetrics.data(0); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 111, - "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ASSERT_EQ(data.bucket_info_size(), 2); EXPECT_EQ(data.bucket_info(0).count(), 2); EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(data.bucket_info(1).count(), 1); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), + bucketStartTimeNs + 2 * bucketSizeNs); EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); data = countMetrics.data(1); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule1"); - EXPECT_EQ(data.bucket_info_size(), 2); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(data.bucket_info_size(), 2); EXPECT_EQ(data.bucket_info(0).count(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); @@ -185,33 +187,34 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) { EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); data = countMetrics.data(2); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, - "GMSCoreModule3"); - EXPECT_EQ(data.bucket_info_size(), 1); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule3"); + ASSERT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), + bucketStartTimeNs + 3 * bucketSizeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 4 * bucketSizeNs); data = countMetrics.data(3); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 444, - "GMSCoreModule2"); - EXPECT_EQ(data.bucket_info_size(), 1); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 444, + "GMSCoreModule2"); + ASSERT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), + bucketStartTimeNs + 2 * bucketSizeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); } TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) { auto config = CreateStatsdConfig(Position::ALL); int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); // Here it assumes that GMS core has two uids. @@ -222,70 +225,34 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) { String16("APP3")}, {String16(""), String16(""), String16(""), String16("")}); - // GMS core node is in the middle. - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(333, "App3")}; - - // GMS core node is the last one. - std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App1"), - CreateAttribution(333, "App3"), - CreateAttribution(222, "GMSCoreModule1")}; - - // GMS core node is the first one. - std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(333, "App3")}; - - // Single GMS core node. - std::vector<AttributionNodeInternal> attributions4 = {CreateAttribution(222, "GMSCoreModule1")}; - - // GMS core has another uid. - std::vector<AttributionNodeInternal> attributions5 = {CreateAttribution(111, "App1"), - CreateAttribution(444, "GMSCoreModule2"), - CreateAttribution(333, "App3")}; - - // Multiple GMS core nodes. - std::vector<AttributionNodeInternal> attributions6 = {CreateAttribution(444, "GMSCoreModule2"), - CreateAttribution(222, "GMSCoreModule1")}; - - // No GMS core nodes. - std::vector<AttributionNodeInternal> attributions7 = {CreateAttribution(111, "App1"), - CreateAttribution(333, "App3")}; - std::vector<AttributionNodeInternal> attributions8 = {CreateAttribution(111, "App1")}; - - // GMS core node with isolated uid. - const int isolatedUid = 666; - std::vector<AttributionNodeInternal> attributions9 = { - CreateAttribution(isolatedUid, "GMSCoreModule1")}; - std::vector<std::unique_ptr<LogEvent>> events; // Events 1~4 are in the 1st bucket. - events.push_back(CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 2)); - events.push_back(CreateAcquireWakelockEvent( - attributions2, "wl1", bucketStartTimeNs + 200)); - events.push_back(CreateAcquireWakelockEvent( - attributions3, "wl1", bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateAcquireWakelockEvent( - attributions4, "wl1", bucketStartTimeNs + bucketSizeNs)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 200, attributionUids2, + attributionTags2, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 1, + attributionUids3, attributionTags3, "wl1")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs, attributionUids4, + attributionTags4, "wl1")); // Events 5~8 are in the 3rd bucket. - events.push_back(CreateAcquireWakelockEvent( - attributions5, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1)); - events.push_back(CreateAcquireWakelockEvent( - attributions6, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 100)); - events.push_back(CreateAcquireWakelockEvent( - attributions7, "wl2", bucketStartTimeNs + 3 * bucketSizeNs - 2)); - events.push_back(CreateAcquireWakelockEvent( - attributions8, "wl2", bucketStartTimeNs + 3 * bucketSizeNs)); - events.push_back(CreateAcquireWakelockEvent( - attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 1)); - events.push_back(CreateAcquireWakelockEvent( - attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 100)); - events.push_back(CreateIsolatedUidChangedEvent( - isolatedUid, 222, true/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs - 1)); - events.push_back(CreateIsolatedUidChangedEvent( - isolatedUid, 222, false/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs + 10)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 1, + attributionUids5, attributionTags5, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids6, attributionTags6, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs - 2, + attributionUids7, attributionTags7, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs, + attributionUids8, attributionTags8, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 1, + attributionUids10, attributionTags10, "wl2")); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 3 * bucketSizeNs + 100, + attributionUids10, attributionTags10, "wl2")); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs - 1, 222, + isolatedUid, true /*is_create*/)); + events.push_back(CreateIsolatedUidChangedEvent(bucketStartTimeNs + 3 * bucketSizeNs + 10, 222, + isolatedUid, false /*is_create*/)); sortLogEventsByTimestamp(&events); @@ -294,124 +261,109 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) { } ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); StatsLogReport::CountMetricDataWrapper countMetrics; sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - EXPECT_EQ(countMetrics.data_size(), 6); + ASSERT_EQ(countMetrics.data_size(), 6); auto data = countMetrics.data(0); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1"); - EXPECT_EQ(2, data.bucket_info_size()); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(2, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); EXPECT_EQ(1, data.bucket_info(1).count()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, - data.bucket_info(1).end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); data = countMetrics.data(1); - ValidateUidDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1"); - ValidateUidDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3"); - EXPECT_EQ(data.bucket_info_size(), 1); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ASSERT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); data = countMetrics.data(2); - ValidateUidDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2"); - ValidateUidDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1"); - EXPECT_EQ(data.bucket_info_size(), 1); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 444); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 444, + "GMSCoreModule2"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(3); - ValidateUidDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1"); - ValidateUidDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1"); - ValidateUidDimension( - data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3"); - EXPECT_EQ(data.bucket_info_size(), 1); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ASSERT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(bucketStartTimeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(4); - ValidateUidDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1"); - ValidateUidDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3"); - ValidateUidDimension( - data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1"); - EXPECT_EQ(data.bucket_info_size(), 1); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 222); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, + util::WAKELOCK_STATE_CHANGED, 222, + "GMSCoreModule1"); + ASSERT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(bucketStartTimeNs, - data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(5); - ValidateUidDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1"); - ValidateUidDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2"); - ValidateUidDimension( - data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333); - ValidateAttributionUidAndTagDimension( - data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3"); - EXPECT_EQ(data.bucket_info_size(), 1); + ValidateUidDimension(data.dimensions_in_what(), 0, util::WAKELOCK_STATE_CHANGED, 111); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 0, + util::WAKELOCK_STATE_CHANGED, 111, "App1"); + ValidateUidDimension(data.dimensions_in_what(), 1, util::WAKELOCK_STATE_CHANGED, 444); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 1, + util::WAKELOCK_STATE_CHANGED, 444, + "GMSCoreModule2"); + ValidateUidDimension(data.dimensions_in_what(), 2, util::WAKELOCK_STATE_CHANGED, 333); + ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), 2, + util::WAKELOCK_STATE_CHANGED, 333, "App3"); + ASSERT_EQ(data.bucket_info_size(), 1); EXPECT_EQ(data.bucket_info(0).count(), 1); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); } #else diff --git a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp index b98dc60086ac..0bce0baa049b 100644 --- a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp @@ -39,7 +39,7 @@ StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) { countMetric->set_id(123456); countMetric->set_what(wakelockAcquireMatcher.id()); *countMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); countMetric->set_bucket(FIVE_MINUTES); auto alert = config.add_alert(); @@ -64,40 +64,46 @@ TEST(ConfigTtlE2eTest, TestCountMetric) { const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs(); int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; + std::vector<int> attributionUids1 = {111}; + std::vector<string> attributionTags1 = {"App1"}; - FieldValue fieldValue1(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), Value((int32_t)111)); HashableDimensionKey whatKey1({fieldValue1}); MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY); - FieldValue fieldValue2(Field(android::util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), + FieldValue fieldValue2(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101), Value((int32_t)222)); HashableDimensionKey whatKey2({fieldValue2}); MetricDimensionKey dimensionKey2(whatKey2, DEFAULT_DIMENSION_KEY); - auto event = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2); + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(event.get()); - event = CreateAcquireWakelockEvent(attributions1, "wl2", bucketStartTimeNs + bucketSizeNs + 2); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, attributionUids1, + attributionTags1, "wl2"); processor->OnLogEvent(event.get()); - event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 25 * bucketSizeNs + 2); + event = CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * bucketSizeNs + 2, attributionUids1, + attributionTags1, "wl1"); processor->OnLogEvent(event.get()); EXPECT_EQ((int64_t)(bucketStartTimeNs + 25 * bucketSizeNs + 2 + 2 * 3600 * NS_PER_SEC), processor->mMetricsManagers.begin()->second->getTtlEndNs()); -} + // Clear the data stored on disk as a result of the ttl. + vector<uint8_t> buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 25 * bucketSizeNs + 3, false, true, + ADB_DUMP, FAST, &buffer); +} #else GTEST_LOG_(INFO) << "This test does nothing.\n"; diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp new file mode 100644 index 000000000000..04eb40080631 --- /dev/null +++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp @@ -0,0 +1,901 @@ +/* + * Copyright (C) 2019, 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 "src/StatsLogProcessor.h" +#include "src/state/StateManager.h" +#include "src/state/StateTracker.h" +#include "tests/statsd_test_util.h" + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +/** + * Tests the initial condition and condition after the first log events for + * count metrics with either a combination condition or simple condition. + * + * Metrics should be initialized with condition kUnknown (given that the + * predicate is using the default InitialValue of UNKNOWN). The condition should + * be updated to either kFalse or kTrue if a condition event is logged for all + * children conditions. + */ +TEST(CountMetricE2eTest, TestInitialConditionChanges) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenOnOnBatteryPredicate = config.add_predicate(); + screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); + screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); + + // CountSyncStartWhileScreenOnOnBattery (CombinationCondition) + CountMetric* countMetric1 = config.add_count_metric(); + countMetric1->set_id(StringToId("CountSyncStartWhileScreenOnOnBattery")); + countMetric1->set_what(syncStartMatcher.id()); + countMetric1->set_condition(screenOnOnBatteryPredicate->id()); + countMetric1->set_bucket(FIVE_MINUTES); + + // CountSyncStartWhileOnBattery (SimpleCondition) + CountMetric* countMetric2 = config.add_count_metric(); + countMetric2->set_id(StringToId("CountSyncStartWhileOnBatterySliceScreen")); + countMetric2->set_what(syncStartMatcher.id()); + countMetric2->set_condition(deviceUnpluggedPredicate.id()); + countMetric2->set_bucket(FIVE_MINUTES); + + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(2, metricsManager->mAllMetricProducers.size()); + + sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0]; + sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[1]; + + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOnEvent = + CreateScreenStateChangedEvent(bucketStartTimeNs + 30, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto pluggedUsbEvent = CreateBatteryStateChangedEvent( + bucketStartTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(pluggedUsbEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); + + auto pluggedNoneEvent = CreateBatteryStateChangedEvent( + bucketStartTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); + processor->OnLogEvent(pluggedNoneEvent.get()); + EXPECT_EQ(ConditionState::kTrue, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); +} + +/** +* Test a count metric that has one slice_by_state with no primary fields. +* +* Once the CountMetricProducer is initialized, it has one atom id in +* mSlicedStateAtoms and no entries in mStateGroupMap. + +* One StateTracker tracks the state atom, and it has one listener which is the +* CountMetricProducer that was initialized. +*/ +TEST(CountMetricE2eTest, TestSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + + auto state = CreateScreenState(); + *config.add_state() = state; + + // Create count metric that slices by screen state. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(syncStartMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state.id()); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + x x x x x x (syncStartEvents) + | | (ScreenIsOnEvent) + | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events - first bucket. + std::vector<int> attributionUids1 = {123}; + std::vector<string> attributionTags1 = {"App1"}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:00 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 75 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 1:25 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 2:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 200 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:30 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 4:20 + + // Initialize log events - second bucket. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 350 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 6:00 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 6:50 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 450 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 7:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 475 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 8:05 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 500 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 8:30 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 520 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 8:50 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(2, data.bucket_info(1).count()); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(1, data.bucket_info(1).count()); +} + +/** + * Test a count metric that has one slice_by_state with a mapping and no + * primary fields. + * + * Once the CountMetricProducer is initialized, it has one atom id in + * mSlicedStateAtoms and has one entry per state value in mStateGroupMap. + * + * One StateTracker tracks the state atom, and it has one listener which is the + * CountMetricProducer that was initialized. + */ +TEST(CountMetricE2eTest, TestSlicedStateWithMap) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto syncStartMatcher = CreateSyncStartAtomMatcher(); + *config.add_atom_matcher() = syncStartMatcher; + + int64_t screenOnId = 4444; + int64_t screenOffId = 9876; + auto state = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); + *config.add_state() = state; + + // Create count metric that slices by screen state with on/off map. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(syncStartMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state.id()); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); + + StateMap map = state.map(); + for (auto group : map.group()) { + for (auto value : group.value()) { + EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), + group.group_id()); + } + } + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + x x x x x x x x x (syncStartEvents) + -----------------------------------------------------------SCREEN_OFF events + | | (ScreenStateOffEvent = 1) + | | (ScreenStateDozeEvent = 3) + | (ScreenStateDozeSuspendEvent = + 4) + -----------------------------------------------------------SCREEN_ON events + | | (ScreenStateOnEvent = 2) + | (ScreenStateVrEvent = 5) + | (ScreenStateOnSuspendEvent = 6) + */ + // Initialize log events - first bucket. + std::vector<int> attributionUids1 = {123}; + std::vector<string> attributionTags1 = {"App1"}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 20 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 1:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 120 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 2:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 180 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:10 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 210 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:40 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 250 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 4:20 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 280 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:50 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 285 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 4:55 + + // Initialize log events - second bucket. + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 360 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 6:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 440 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 7:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 540 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 9:10 + events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 570 * NS_PER_SEC, attributionUids1, + attributionTags1, "sync_name")); // 9:40 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(1, data.bucket_info(1).count()); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(4, data.bucket_info(0).count()); + EXPECT_EQ(2, data.bucket_info(1).count()); +} + +/** +* Test a count metric that has one slice_by_state with a primary field. + +* Once the CountMetricProducer is initialized, it should have one +* MetricStateLink stored. State querying using a non-empty primary key +* should also work as intended. +*/ +TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto appCrashMatcher = + CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED); + *config.add_atom_matcher() = appCrashMatcher; + + auto state = CreateUidProcessState(); + *config.add_state() = state; + + // Create count metric that slices by uid process state. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state.id()); + MetricStateLink* stateLink = countMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/}); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1); + + /* + NOTE: "1" or "2" represents the uid associated with the state/app crash event + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 + |------------------------|-------------------------|-- + 1 1 1 1 1 2 1 1 2 (AppCrashEvents) + -----------------------------------------------------PROCESS STATE events + 1 2 (TopEvent = 1002) + 1 1 (ForegroundServiceEvent = 1003) + 2 (ImportantBackgroundEvent = 1006) + 1 1 1 (ImportantForegroundEvent = 1005) + + Based on the diagram above, an AppCrashEvent querying for process state value would return: + - StateTracker::kStateUnknown + - Important foreground + - Top + - Important foreground + - Foreground service + - Top (both the app crash and state still have matching uid = 2) + + - Foreground service + - Foreground service + - Important background + */ + // Initialize log events - first bucket. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 1:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/)); // 2:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 2:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/)); // 3:30 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 210 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 3:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/)); // 4:20 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 4:50 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/)); // 4:55 + + // Initialize log events - second bucket. + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/)); // 6:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 6:40 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 7:20 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 440 * NS_PER_SEC, 1 /*uid*/)); // 7:30 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 9:10 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/)); // 9:40 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(2, data.bucket_info(1).count()); + + data = countMetrics.data(3); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + + data = countMetrics.data(4); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); +} + +TEST(CountMetricE2eTest, TestMultipleSlicedStates) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto appCrashMatcher = + CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED); + *config.add_atom_matcher() = appCrashMatcher; + + int64_t screenOnId = 4444; + int64_t screenOffId = 9876; + auto state1 = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); + *config.add_state() = state1; + auto state2 = CreateUidProcessState(); + *config.add_state() = state2; + + // Create count metric that slices by screen state with on/off map and + // slices by uid process state. + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(appCrashMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + countMetric->add_slice_by_state(state1.id()); + countMetric->add_slice_by_state(state2.id()); + MetricStateLink* stateLink = countMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /*uid*/}); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were properly initialized. + EXPECT_EQ(2, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Check that CountMetricProducer was initialized correctly. + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 2); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(1), UID_PROCESS_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); + ASSERT_EQ(metricProducer->mMetric2StateLinks.size(), 1); + + StateMap map = state1.map(); + for (auto group : map.group()) { + for (auto value : group.value()) { + EXPECT_EQ(metricProducer->mStateGroupMap.at(SCREEN_STATE_ATOM_ID).at(value), + group.group_id()); + } + } + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |------------------------|------------------------|-- + 1 1 1 1 1 2 1 1 2 (AppCrashEvents) + ---------------------------------------------------SCREEN_OFF events + | | (ScreenOffEvent = 1) + | | (ScreenDozeEvent = 3) + ---------------------------------------------------SCREEN_ON events + | | (ScreenOnEvent = 2) + | (ScreenOnSuspendEvent = 6) + ---------------------------------------------------PROCESS STATE events + 1 2 (TopEvent = 1002) + 1 (ForegroundServiceEvent = 1003) + 2 (ImportantBackgroundEvent = 1006) + 1 1 1 (ImportantForegroundEvent = 1005) + + Based on the diagram above, Screen State / Process State pairs for each + AppCrashEvent are: + - StateTracker::kStateUnknown / important foreground + - off / important foreground + - off / Top + - on / important foreground + - off / important foreground + - off / top + + - off / important foreground + - off / foreground service + - on / important background + + */ + // Initialize log events - first bucket. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 5 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:15 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 30 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 1:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 90 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 120 * NS_PER_SEC, 1 /*uid*/)); // 2:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 150 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 2:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 160 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:50 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 200 * NS_PER_SEC, 1 /*uid*/)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 210 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:40 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 250 * NS_PER_SEC, 1 /*uid*/)); // 4:20 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 280 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP)); // 4:50 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 285 * NS_PER_SEC, 2 /*uid*/)); // 4:55 + + // Initialize log events - second bucket. + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 360 * NS_PER_SEC, 1 /*uid*/)); // 6:10 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 380 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 6:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 420 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 7:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 440 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 7:30 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 450 * NS_PER_SEC, 1 /*uid*/)); // 7:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 520 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 8:50 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 540 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 9:10 + events.push_back( + CreateAppCrashOccurredEvent(bucketStartTimeNs + 570 * NS_PER_SEC, 2 /*uid*/)); // 9:40 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(6, countMetrics.data_size()); + + // For each CountMetricData, check StateValue info is correct and buckets + // have correct counts. + auto data = countMetrics.data(0); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1, data.slice_by_state(0).value()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(1); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(2); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(3); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + + data = countMetrics.data(4); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(1).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + + data = countMetrics.data(5); + ASSERT_EQ(2, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id()); + EXPECT_TRUE(data.slice_by_state(1).has_value()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).count()); + EXPECT_EQ(1, data.bucket_info(1).count()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp deleted file mode 100644 index e4186b7200a0..000000000000 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp +++ /dev/null @@ -1,961 +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. - -#include <gtest/gtest.h> - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -#include <vector> - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition, - bool hashStringInReport) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensions->add_child()->set_field(2); // job name field. - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - config.set_hash_strings_in_metric_report(hashStringInReport); - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - auto dimensionWhat = metric->mutable_dimensions_in_what(); - dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensionWhat->add_child()->set_field(2); // job name field. - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -/* - The following test has the following input. - -{ 10000000002 10000000002 (8)9999[I], [S], job0[S], 1[I], } -{ 10000000010 10000000010 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } -{ 10000000011 10000000011 (29)1[I], } -{ 10000000040 10000000040 (29)2[I], } -{ 10000000050 10000000050 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } -{ 10000000101 10000000101 (8)9999[I], [S], job0[S], 0[I], } -{ 10000000102 10000000102 (29)1[I], } -{ 10000000200 10000000200 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } -{ 10000000201 10000000201 (8)9999[I], [S], job2[S], 1[I], } -{ 10000000400 10000000400 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 1[I], } -{ 10000000401 10000000401 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 1[I], } -{ 10000000450 10000000450 (29)2[I], } -{ 10000000500 10000000500 (8)9999[I], [S], job2[S], 0[I], } -{ 10000000600 10000000600 (8)8888[I], [S], job2[S], 1[I], } -{ 10000000650 10000000650 (29)1[I], } -{ 309999999999 309999999999 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadDoc[S], 0[I], } -{ 310000000100 310000000100 (29)2[I], } -{ 310000000300 310000000300 (7)111[I], App1[S], 222[I], GMSCoreModule1[S], 222[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } -{ 310000000600 310000000600 (8)8888[I], [S], job1[S], 1[I], } -{ 310000000640 310000000640 (29)1[I], } -{ 310000000650 310000000650 (29)2[I], } -{ 310000000700 310000000700 (7)333[I], App2[S], 222[I], GMSCoreModule1[S], 555[I], GMSCoreModule2[S], ReadEmail[S], 0[I], } -{ 310000000850 310000000850 (8)8888[I], [S], job2[S], 0[I], } -{ 310000000900 310000000900 (8)8888[I], [S], job1[S], 0[I], } -*/ -TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition) { - for (const bool hashStringInReport : { true, false }) { - for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : { true, false }) { - for (auto aggregationType : {DurationMetric::MAX_SPARSE, DurationMetric::SUM}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition( - aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension, - hashStringInReport); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis( - config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 11)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 40)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 102)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 650)); - events.push_back(CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 640)); - events.push_back(CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 650)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 2)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", - bucketStartTimeNs + bucketSizeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 10)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201 + bucketSizeNs - 650); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401 + bucketSizeNs - 650); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100 + 650 - 640); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple(). - dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension( - data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 650 + 110); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } - } -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition) { - for (bool isFullLink : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition( - aggregationType, !isFullLink); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 55)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 120)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 121)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 501)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_PartialLink_AND_CombinationCondition( - DurationMetric::AggregationType aggregationType, bool hashStringInReport) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field*/); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - - config.set_hash_strings_in_metric_report(hashStringInReport); - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(StringToId("CombinationPredicate")); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *metric->mutable_dimensions_in_condition() = *syncDimension; - - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition) { - for (const bool hashStringInReport : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = - CreateDurationMetricConfig_PartialLink_AND_CombinationCondition( - aggregationType, hashStringInReport); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 55)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 120)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 121)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 450)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 501)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 600 + 50); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 50); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple(). - dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp deleted file mode 100644 index f3ecd56dd946..000000000000 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp +++ /dev/null @@ -1,816 +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. - -#include <gtest/gtest.h> - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -#include <vector> - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateCountMetric_NoLink_CombinationCondition_Config() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher(); - *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); - *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - *config.add_predicate() = screenIsOffPredicate; - - auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); - // The predicate is dimensioning by any attribution node and both by uid and tag. - *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateAttributionUidAndTagDimensions(android::util::WAKELOCK_STATE_CHANGED, - {Position::FIRST}); - *config.add_predicate() = holdingWakelockPredicate; - - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(holdingWakelockPredicate, combinationPredicate); - - auto metric = config.add_count_metric(); - metric->set_id(StringToId("ScreenBrightnessChangeMetric")); - metric->set_what(screenBrightnessChangeAtomMatcher.id()); - metric->set_condition(combinationPredicate->id()); - *metric->mutable_dimensions_in_what() = - CreateDimensions(android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */}); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - metric->set_bucket(FIVE_MINUTES); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition) { - ConfigKey cfgKey; - auto config = CreateCountMetric_NoLink_CombinationCondition_Config(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - events.push_back( - CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 1)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 2 * bucketSizeNs - 10)); - - events.push_back(CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 200)); - events.push_back( - CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1)); - - events.push_back(CreateAcquireWakelockEvent(attributions2, "wl2", - bucketStartTimeNs + bucketSizeNs - 100)); - events.push_back(CreateReleaseWakelockEvent(attributions2, "wl2", - bucketStartTimeNs + 2 * bucketSizeNs - 50)); - - events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 11)); - events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 101)); - events.push_back(CreateScreenBrightnessChangedEvent(123, bucketStartTimeNs + 201)); - events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + 203)); - events.push_back( - CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs - 99)); - events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back(CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateScreenBrightnessChangedEvent(456, bucketStartTimeNs + bucketSizeNs + 2)); - events.push_back( - CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 11)); - events.push_back( - CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 9)); - events.push_back( - CreateScreenBrightnessChangedEvent(789, bucketStartTimeNs + 2 * bucketSizeNs - 1)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - - EXPECT_EQ(countMetrics.data_size(), 7); - auto data = countMetrics.data(0); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - - data = countMetrics.data(1); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 111); - - data = countMetrics.data(2); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 3); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 111); - - data = countMetrics.data(3); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).count(), 1); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 333); - - data = countMetrics.data(4); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - - data = countMetrics.data(5); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 111); - - data = countMetrics.data(6); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789); - ValidateAttributionUidDimension(data.dimensions_in_condition(), - android::util::WAKELOCK_STATE_CHANGED, 333); -} - -namespace { - -StatsdConfig CreateCountMetric_Link_CombinationCondition() { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto appCrashMatcher = CreateProcessCrashAtomMatcher(); - *config.add_atom_matcher() = appCrashMatcher; - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field*/); - - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_count_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("AppCrashMetric")); - metric->set_what(appCrashMatcher.id()); - metric->set_condition(combinationPredicate->id()); - *metric->mutable_dimensions_in_what() = - CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1 /* uid */}); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - - // Links between crash atom and condition of app is in syncing. - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - dimensionWhat->add_child()->set_field(1); // uid field. - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition) { - ConfigKey cfgKey; - auto config = CreateCountMetric_Link_CombinationCondition(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 11)); - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 101)); - events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 101)); - - events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 201)); - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 211)); - events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 211)); - - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 401)); - events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 401)); - events.push_back(CreateAppCrashEvent(555, bucketStartTimeNs + 401)); - - events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + bucketSizeNs + 301)); - events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + bucketSizeNs + 301)); - - events.push_back(CreateAppCrashEvent(777, bucketStartTimeNs + bucketSizeNs + 701)); - - events.push_back( - CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 202)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 700)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions2, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 600)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); - - EXPECT_EQ(countMetrics.data_size(), 5); - auto data = countMetrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = countMetrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = countMetrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 222); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = countMetrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).count(), 1); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - - data = countMetrics.data(4); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 777); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).count(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_NoLink_CombinationCondition( - DurationMetric::AggregationType aggregationType) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); - *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto inBatterySaverModePredicate = CreateBatterySaverModePredicate(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field */); - - *config.add_predicate() = inBatterySaverModePredicate; - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("BatterySaverModeDurationMetric")); - metric->set_what(inBatterySaverModePredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition) { - for (auto aggregationType : { DurationMetric::MAX_SPARSE, DurationMetric::SUM}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_NoLink_CombinationCondition(aggregationType); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 1)); - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 101)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 110)); - - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 201)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 500)); - - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 600)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + bucketSizeNs + 870)); - events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 202)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 800)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); - - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - EXPECT_FALSE(data.dimensions_in_what().has_field()); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - - data = metrics.data(1); - EXPECT_FALSE(data.dimensions_in_what().has_field()); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300); - } else { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 300); - } - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_FALSE(data.dimensions_in_what().has_field()); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - } else { - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs + 700 - 600); - } - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } -} - -namespace { - -StatsdConfig CreateDurationMetricConfig_Link_CombinationCondition( - DurationMetric::AggregationType aggregationType) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); - *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); - *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto screenIsOffPredicate = CreateScreenIsOffPredicate(); - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field */); - - auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); - *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */}); - - *config.add_predicate() = screenIsOffPredicate; - *config.add_predicate() = isSyncingPredicate; - *config.add_predicate() = isInBackgroundPredicate; - auto combinationPredicate = config.add_predicate(); - combinationPredicate->set_id(987654); - combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR); - addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate); - addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("AppInBackgroundMetric")); - metric->set_what(isInBackgroundPredicate.id()); - metric->set_condition(combinationPredicate->id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */}); - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - - // Links between crash atom and condition of app is in syncing. - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); - dimensionWhat->add_child()->set_field(1); // uid field. - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_Link_CombinationCondition(aggregationType); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 101)); - events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + 110)); - - events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 201)); - events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + bucketSizeNs + 100)); - - events.push_back(CreateMoveToBackgroundEvent(333, bucketStartTimeNs + 399)); - events.push_back(CreateMoveToForegroundEvent(333, bucketStartTimeNs + bucketSizeNs + 800)); - - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 10)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + 100)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 202)); - events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - bucketStartTimeNs + bucketSizeNs + 801)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400)); - events.push_back( - CreateSyncEndEvent(attributions1, "ReadDoc", bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics); - - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - EXPECT_FALSE(data.dimensions_in_condition().has_field()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 100 - 201); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333); - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 299); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp deleted file mode 100644 index 489bb0b21a2e..000000000000 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp +++ /dev/null @@ -1,814 +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. - -#include <gtest/gtest.h> - -#include "src/StatsLogProcessor.h" -#include "src/stats_log_util.h" -#include "tests/statsd_test_util.h" - -#include <vector> - -namespace android { -namespace os { -namespace statsd { - -#ifdef __ANDROID__ - -namespace { - -StatsdConfig CreateDurationMetricConfig_NoLink_SimpleCondition( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED, - {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = isSyncingPredicate; - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(isSyncingPredicate.id()); - metric->set_aggregation_type(aggregationType); - auto dimensionWhat = metric->mutable_dimensions_in_what(); - dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED); - dimensionWhat->add_child()->set_field(2); // job name field. - *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition) { - for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = CreateDurationMetricConfig_NoLink_SimpleCondition( - aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 10)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 200)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 300)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 401)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job0"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job1"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 111, "App1"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 300); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::SCHEDULED_JOB_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), - 2); // job name field - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), - "job2"); // job name - ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(), - android::util::SYNC_STATE_CHANGED, 333, "App2"); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 ); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -namespace { - -StatsdConfig createDurationMetric_Link_SimpleConditionConfig( - DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - if (addExtraDimensionInCondition) { - syncDimension->add_child()->set_field(2 /* name field*/); - } - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = isSyncingPredicate; - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(isSyncingPredicate.id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition) { - for (bool isFullLink : {true, false}) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = createDurationMetric_Link_SimpleConditionConfig( - aggregationType, !isFullLink); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(333, "App2")}, "job2", - bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, - true, ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 3); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } - } -} - -namespace { - -StatsdConfig createDurationMetric_PartialLink_SimpleConditionConfig( - DurationMetric::AggregationType aggregationType) { - StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher(); - *config.add_atom_matcher() = CreateSyncStartAtomMatcher(); - *config.add_atom_matcher() = CreateSyncEndAtomMatcher(); - - auto scheduledJobPredicate = CreateScheduledJobPredicate(); - auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions(); - *dimensions = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - dimensions->add_child()->set_field(2); // job name field. - - auto isSyncingPredicate = CreateIsSyncingPredicate(); - auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); - *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - syncDimension->add_child()->set_field(2 /* name field*/); - - *config.add_predicate() = scheduledJobPredicate; - *config.add_predicate() = isSyncingPredicate; - - auto metric = config.add_duration_metric(); - metric->set_bucket(FIVE_MINUTES); - metric->set_id(StringToId("scheduledJob")); - metric->set_what(scheduledJobPredicate.id()); - metric->set_condition(isSyncingPredicate.id()); - metric->set_aggregation_type(aggregationType); - *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *metric->mutable_dimensions_in_condition() = *syncDimension; - - auto links = metric->add_links(); - links->set_condition(isSyncingPredicate.id()); - *links->mutable_fields_in_what() = - CreateAttributionUidDimensions( - android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST}); - *links->mutable_fields_in_condition() = - CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST}); - return config; -} - -} // namespace - -TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition) { - for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) { - ConfigKey cfgKey; - auto config = createDurationMetric_PartialLink_SimpleConditionConfig( - aggregationType); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; - - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); - EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); - - std::vector<AttributionNodeInternal> attributions1 = { - CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions2 = { - CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<AttributionNodeInternal> attributions3 = { - CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(555, "GMSCoreModule2")}; - - std::vector<std::unique_ptr<LogEvent>> events; - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101)); - - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500)); - events.push_back(CreateStartScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600)); - events.push_back(CreateFinishScheduledJobEvent( - {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + bucketSizeNs + 850)); - - events.push_back( - CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs - 2)); - events.push_back( - CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3", - bucketStartTimeNs + bucketSizeNs + 900)); - - events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 50)); - events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail", - bucketStartTimeNs + 110)); - - events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", - bucketStartTimeNs + 300)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail", - bucketStartTimeNs + bucketSizeNs + 700)); - events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc", - bucketStartTimeNs + 400)); - events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 550)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + 800)); - events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs - 1)); - events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc", - bucketStartTimeNs + bucketSizeNs + 700)); - - sortLogEventsByTimestamp(&events); - - for (const auto& event : events) { - processor->OnLogEvent(event.get()); - } - - ConfigMetricsReportList reports; - vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_TRUE(buffer.size() > 0); - EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); - backfillDimensionPath(&reports); - backfillStringInReport(&reports); - backfillStartEndTimestamp(&reports); - - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - StatsLogReport::DurationMetricDataWrapper metrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).duration_metrics(), &metrics); - - if (aggregationType == DurationMetric::SUM) { - EXPECT_EQ(4, metrics.data_size()); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 400 - 100); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } else { - EXPECT_EQ(metrics.data_size(), 4); - auto data = metrics.data(0); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - - data = metrics.data(1); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600); - - data = metrics.data(2); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333); - EXPECT_EQ("ReadEmail", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 2); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300); - EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700); - EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - - data = metrics.data(3); - ValidateAttributionUidDimension( - data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444); - ValidateAttributionUidDimension( - data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444); - EXPECT_EQ("ReadDoc", - data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str()); - EXPECT_EQ(data.bucket_info_size(), 1); - EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701); - EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), - bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), - bucketStartTimeNs + 2 * bucketSizeNs); - } - } -} - -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp index 5da0fca2f3ed..4efb038e538d 100644 --- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp @@ -14,12 +14,13 @@ #include <gtest/gtest.h> +#include <vector> + #include "src/StatsLogProcessor.h" +#include "src/state/StateTracker.h" #include "src/stats_log_util.h" #include "tests/statsd_test_util.h" -#include <vector> - namespace android { namespace os { namespace statsd { @@ -28,7 +29,7 @@ namespace statsd { TEST(DurationMetricE2eTest, TestOneBucket) { StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); @@ -45,9 +46,8 @@ TEST(DurationMetricE2eTest, TestOneBucket) { durationMetric->set_bucket(FIVE_MINUTES); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - - const int64_t baseTimeNs = 0; // 0:00 - const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 + const int64_t baseTimeNs = 0; // 0:00 + const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; @@ -57,10 +57,10 @@ TEST(DurationMetricE2eTest, TestOneBucket) { auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); @@ -68,42 +68,43 @@ TEST(DurationMetricE2eTest, TestOneBucket) { std::unique_ptr<LogEvent> event; // Screen is off at start of bucket. - event = CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_OFF, configAddedTimeNs); // 0:01 + event = CreateScreenStateChangedEvent(configAddedTimeNs, + android::view::DISPLAY_STATE_OFF); // 0:01 processor->OnLogEvent(event.get()); // Turn screen on. - const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs); + const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); processor->OnLogEvent(event.get()); // Turn off screen 30 seconds after turning on. - const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, durationEndNs); + const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(event.get()); - event = CreateScreenBrightnessChangedEvent(64, durationEndNs + 1 * NS_PER_SEC); // 0:42 + event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64); // 0:42 processor->OnLogEvent(event.get()); ConfigMetricsReportList reports; vector<uint8_t> buffer; processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true, - ADB_DUMP, FAST, &buffer); // 5:01 + ADB_DUMP, FAST, &buffer); // 5:01 EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - const StatsLogReport::DurationMetricDataWrapper& durationMetrics = - reports.reports(0).metrics(0).duration_metrics(); - EXPECT_EQ(1, durationMetrics.data_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); - EXPECT_EQ(1, data.bucket_info_size()); + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos()); EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); @@ -111,7 +112,7 @@ TEST(DurationMetricE2eTest, TestOneBucket) { TEST(DurationMetricE2eTest, TestTwoBuckets) { StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); @@ -128,9 +129,8 @@ TEST(DurationMetricE2eTest, TestTwoBuckets) { durationMetric->set_bucket(FIVE_MINUTES); durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); - - const int64_t baseTimeNs = 0; // 0:00 - const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 + const int64_t baseTimeNs = 0; // 0:00 + const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; @@ -140,10 +140,10 @@ TEST(DurationMetricE2eTest, TestTwoBuckets) { auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); @@ -151,42 +151,43 @@ TEST(DurationMetricE2eTest, TestTwoBuckets) { std::unique_ptr<LogEvent> event; // Screen is off at start of bucket. - event = CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_OFF, configAddedTimeNs); // 0:01 + event = CreateScreenStateChangedEvent(configAddedTimeNs, + android::view::DISPLAY_STATE_OFF); // 0:01 processor->OnLogEvent(event.get()); // Turn screen on. - const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs); + const int64_t durationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; // 0:11 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); processor->OnLogEvent(event.get()); // Turn off screen 30 seconds after turning on. - const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, durationEndNs); + const int64_t durationEndNs = durationStartNs + 30 * NS_PER_SEC; // 0:41 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(event.get()); - event = CreateScreenBrightnessChangedEvent(64, durationEndNs + 1 * NS_PER_SEC); // 0:42 + event = CreateScreenBrightnessChangedEvent(durationEndNs + 1 * NS_PER_SEC, 64); // 0:42 processor->OnLogEvent(event.get()); ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, false, true, - ADB_DUMP, FAST, &buffer); // 10:01 + processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, false, + true, ADB_DUMP, FAST, &buffer); // 10:01 EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - const StatsLogReport::DurationMetricDataWrapper& durationMetrics = - reports.reports(0).metrics(0).duration_metrics(); - EXPECT_EQ(1, durationMetrics.data_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); - EXPECT_EQ(1, data.bucket_info_size()); + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); EXPECT_EQ(0, bucketInfo.bucket_num()); @@ -197,7 +198,7 @@ TEST(DurationMetricE2eTest, TestTwoBuckets) { TEST(DurationMetricE2eTest, TestWithActivation) { StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher(); @@ -220,7 +221,7 @@ TEST(DurationMetricE2eTest, TestWithActivation) { metric_activation1->set_metric_id(metricId); auto event_activation1 = metric_activation1->add_event_activation(); event_activation1->set_atom_matcher_id(crashMatcher.id()); - event_activation1->set_ttl_seconds(30); // 30 secs. + event_activation1->set_ttl_seconds(30); // 30 secs. const int64_t bucketStartTimeNs = 10000000000; const int64_t bucketSizeNs = @@ -237,30 +238,31 @@ TEST(DurationMetricE2eTest, TestWithActivation) { vector<int64_t> activeConfigsBroadcast; int broadcastCount = 0; - StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, - bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector<int64_t>& activeConfigs) { + const vector<int64_t>& activeConfigs) { broadcastCount++; EXPECT_EQ(broadcastUid, uid); activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), - activeConfigs.begin(), activeConfigs.end()); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); return true; }); - processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); // 0:00 + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); // 0:00 - EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap.size(), 1u); + ASSERT_EQ(eventActivationMap.size(), 1u); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); @@ -269,25 +271,25 @@ TEST(DurationMetricE2eTest, TestWithActivation) { std::unique_ptr<LogEvent> event; // Turn screen off. - event = CreateScreenStateChangedEvent( - android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 2 * NS_PER_SEC); // 0:02 - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * NS_PER_SEC, + android::view::DISPLAY_STATE_OFF); // 0:02 + processor.OnLogEvent(event.get(), bucketStartTimeNs + 2 * NS_PER_SEC); // Turn screen on. - const int64_t durationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:05 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs); - processor.OnLogEvent(event.get()); + const int64_t durationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:05 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), durationStartNs); // Activate metric. - const int64_t activationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:10 + const int64_t activationStartNs = bucketStartTimeNs + 5 * NS_PER_SEC; // 0:10 const int64_t activationEndNs = - activationStartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 0:40 - event = CreateAppCrashEvent(111, activationStartNs); - processor.OnLogEvent(event.get()); + activationStartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 0:40 + event = CreateAppCrashEvent(activationStartNs, 111); + processor.OnLogEvent(event.get(), activationStartNs); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs); @@ -295,43 +297,43 @@ TEST(DurationMetricE2eTest, TestWithActivation) { // Expire activation. const int64_t expirationNs = activationEndNs + 7 * NS_PER_SEC; - event = CreateScreenBrightnessChangedEvent(64, expirationNs); // 0:47 - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(expirationNs, 64); // 0:47 + processor.OnLogEvent(event.get(), expirationNs); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 2); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap.size(), 1u); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(eventActivationMap.size(), 1u); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, activationStartNs); EXPECT_EQ(eventActivationMap[2]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); // Turn off screen 10 seconds after activation expiration. - const int64_t durationEndNs = activationEndNs + 10 * NS_PER_SEC; // 0:50 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, durationEndNs); - processor.OnLogEvent(event.get()); + const int64_t durationEndNs = activationEndNs + 10 * NS_PER_SEC; // 0:50 + event = CreateScreenStateChangedEvent(durationEndNs, android::view::DISPLAY_STATE_OFF); + processor.OnLogEvent(event.get(), durationEndNs); // Turn screen on. - const int64_t duration2StartNs = durationEndNs + 5 * NS_PER_SEC; // 0:55 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, duration2StartNs); - processor.OnLogEvent(event.get()); + const int64_t duration2StartNs = durationEndNs + 5 * NS_PER_SEC; // 0:55 + event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), duration2StartNs); // Turn off screen. - const int64_t duration2EndNs = duration2StartNs + 10 * NS_PER_SEC; // 1:05 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, duration2EndNs); - processor.OnLogEvent(event.get()); + const int64_t duration2EndNs = duration2StartNs + 10 * NS_PER_SEC; // 1:05 + event = CreateScreenStateChangedEvent(duration2EndNs, android::view::DISPLAY_STATE_OFF); + processor.OnLogEvent(event.get(), duration2EndNs); // Activate metric. - const int64_t activation2StartNs = duration2EndNs + 5 * NS_PER_SEC; // 1:10 + const int64_t activation2StartNs = duration2EndNs + 5 * NS_PER_SEC; // 1:10 const int64_t activation2EndNs = - activation2StartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 1:40 - event = CreateAppCrashEvent(211, activation2StartNs); - processor.OnLogEvent(event.get()); + activation2StartNs + event_activation1->ttl_seconds() * NS_PER_SEC; // 1:40 + event = CreateAppCrashEvent(activation2StartNs, 211); + processor.OnLogEvent(event.get(), activation2StartNs); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, activation2StartNs); @@ -340,22 +342,23 @@ TEST(DurationMetricE2eTest, TestWithActivation) { ConfigMetricsReportList reports; vector<uint8_t> buffer; processor.onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true, - ADB_DUMP, FAST, &buffer); // 5:01 + ADB_DUMP, FAST, &buffer); // 5:01 EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); - const StatsLogReport::DurationMetricDataWrapper& durationMetrics = - reports.reports(0).metrics(0).duration_metrics(); - EXPECT_EQ(1, durationMetrics.data_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); - auto data = durationMetrics.data(0); - EXPECT_EQ(1, data.bucket_info_size()); + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); EXPECT_EQ(0, bucketInfo.bucket_num()); @@ -366,7 +369,7 @@ TEST(DurationMetricE2eTest, TestWithActivation) { TEST(DurationMetricE2eTest, TestWithCondition) { StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); @@ -390,10 +393,10 @@ TEST(DurationMetricE2eTest, TestWithCondition) { uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; EXPECT_TRUE(metricsManager->isActive()); @@ -401,41 +404,47 @@ TEST(DurationMetricE2eTest, TestWithCondition) { EXPECT_TRUE(eventActivationMap.empty()); int appUid = 123; - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(appUid, "App1")}; + vector<int> attributionUids1 = {appUid}; + vector<string> attributionTags1 = {"App1"}; - auto event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 10 * NS_PER_SEC); // 0:10 + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, + "wl1"); // 0:10 processor->OnLogEvent(event.get()); - event = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 22 * NS_PER_SEC); // 0:22 + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 processor->OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent( - appUid, bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC); // 3:15 + event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, + appUid); // 3:15 processor->OnLogEvent(event.get()); - event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 4 * 60 * NS_PER_SEC); // 4:00 + event = CreateReleaseWakelockEvent(bucketStartTimeNs + 4 * 60 * NS_PER_SEC, attributionUids1, + attributionTags1, + "wl1"); // 4:00 processor->OnLogEvent(event.get()); vector<uint8_t> buffer; ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_GT(buffer.size(), 0); + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = durationMetrics.data(0); // Validate bucket info. - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); @@ -445,7 +454,7 @@ TEST(DurationMetricE2eTest, TestWithCondition) { TEST(DurationMetricE2eTest, TestWithSlicedCondition) { StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); @@ -454,14 +463,14 @@ TEST(DurationMetricE2eTest, TestWithSlicedCondition) { auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); // The predicate is dimensioning by first attribution node by uid. - FieldMatcher dimensions = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, + {Position::FIRST}); *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; *config.add_predicate() = holdingWakelockPredicate; auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); *config.add_predicate() = isInBackgroundPredicate; auto durationMetric = config.add_duration_metric(); @@ -470,29 +479,28 @@ TEST(DurationMetricE2eTest, TestWithSlicedCondition) { durationMetric->set_condition(isInBackgroundPredicate.id()); durationMetric->set_aggregation_type(DurationMetric::SUM); // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); durationMetric->set_bucket(FIVE_MINUTES); // Links between wakelock state atom and condition of app is in background. auto links = durationMetric->add_links(); links->set_condition(isInBackgroundPredicate.id()); auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::WAKELOCK_STATE_CHANGED); + dimensionWhat->set_field(util::WAKELOCK_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( - android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, { Position::FIRST }); + util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); ConfigKey cfgKey; uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; EXPECT_TRUE(metricsManager->isActive()); @@ -500,44 +508,47 @@ TEST(DurationMetricE2eTest, TestWithSlicedCondition) { EXPECT_TRUE(eventActivationMap.empty()); int appUid = 123; - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(appUid, "App1")}; + std::vector<int> attributionUids1 = {appUid}; + std::vector<string> attributionTags1 = {"App1"}; - auto event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 10 * NS_PER_SEC); // 0:10 + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 0:10 processor->OnLogEvent(event.get()); - event = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 22 * NS_PER_SEC); // 0:22 + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 processor->OnLogEvent(event.get()); - event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 60 * NS_PER_SEC); // 1:00 + event = CreateReleaseWakelockEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 1:00 processor->OnLogEvent(event.get()); - - event = CreateMoveToForegroundEvent( - appUid, bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC); // 3:15 + event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, + appUid); // 3:15 processor->OnLogEvent(event.get()); vector<uint8_t> buffer; ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_GT(buffer.size(), 0); + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = durationMetrics.data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, appUid); + util::WAKELOCK_STATE_CHANGED, appUid); // Validate bucket info. - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); @@ -547,7 +558,7 @@ TEST(DurationMetricE2eTest, TestWithSlicedCondition) { TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { StatsdConfig config; - config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); @@ -557,14 +568,14 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); // The predicate is dimensioning by first attribution node by uid. - FieldMatcher dimensions = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + FieldMatcher dimensions = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, + {Position::FIRST}); *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; *config.add_predicate() = holdingWakelockPredicate; auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); *config.add_predicate() = isInBackgroundPredicate; auto durationMetric = config.add_duration_metric(); @@ -573,19 +584,18 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { durationMetric->set_condition(isInBackgroundPredicate.id()); durationMetric->set_aggregation_type(DurationMetric::SUM); // The metric is dimensioning by first attribution node and only by uid. - *durationMetric->mutable_dimensions_in_what() = - CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); durationMetric->set_bucket(FIVE_MINUTES); // Links between wakelock state atom and condition of app is in background. auto links = durationMetric->add_links(); links->set_condition(isInBackgroundPredicate.id()); auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::WAKELOCK_STATE_CHANGED); + dimensionWhat->set_field(util::WAKELOCK_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( - android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, { Position::FIRST }); + util::ACTIVITY_FOREGROUND_STATE_CHANGED, {Position::FIRST}); auto metric_activation1 = config.add_metric_activation(); metric_activation1->set_metric_id(durationMetric->id()); @@ -598,25 +608,26 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap.size(), 1u); + ASSERT_EQ(eventActivationMap.size(), 1u); EXPECT_TRUE(eventActivationMap.find(4) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[4]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[4]->start_ns, 0); EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); int appUid = 123; - std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(appUid, "App1")}; + std::vector<int> attributionUids1 = {appUid}; + std::vector<string> attributionTags1 = {"App1"}; - auto event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + 10 * NS_PER_SEC); // 0:10 + auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1, + attributionTags1, "wl1"); // 0:10 processor->OnLogEvent(event.get()); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); @@ -624,7 +635,7 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(eventActivationMap[4]->start_ns, 0); EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - event = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 22 * NS_PER_SEC); // 0:22 + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, appUid); // 0:22 processor->OnLogEvent(event.get()); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); @@ -632,8 +643,8 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(eventActivationMap[4]->start_ns, 0); EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - const int64_t durationStartNs = bucketStartTimeNs + 30 * NS_PER_SEC; // 0:30 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, durationStartNs); + const int64_t durationStartNs = bucketStartTimeNs + 30 * NS_PER_SEC; // 0:30 + event = CreateScreenStateChangedEvent(durationStartNs, android::view::DISPLAY_STATE_ON); processor->OnLogEvent(event.get()); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); @@ -642,8 +653,8 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); const int64_t durationEndNs = - durationStartNs + (event_activation1->ttl_seconds() + 30) * NS_PER_SEC; // 3:00 - event = CreateAppCrashEvent(333, durationEndNs); + durationStartNs + (event_activation1->ttl_seconds() + 30) * NS_PER_SEC; // 3:00 + event = CreateAppCrashEvent(durationEndNs, 333); processor->OnLogEvent(event.get()); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); @@ -651,24 +662,24 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(eventActivationMap[4]->start_ns, durationStartNs); EXPECT_EQ(eventActivationMap[4]->ttl_ns, event_activation1->ttl_seconds() * NS_PER_SEC); - event = CreateMoveToForegroundEvent( - appUid, bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC); // 3:15 + event = CreateMoveToForegroundEvent(bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, + appUid); // 3:15 processor->OnLogEvent(event.get()); - event = CreateReleaseWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + (4 * 60 + 17) * NS_PER_SEC); // 4:17 + event = CreateReleaseWakelockEvent(bucketStartTimeNs + (4 * 60 + 17) * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1"); // 4:17 processor->OnLogEvent(event.get()); - event = CreateMoveToBackgroundEvent( - appUid, bucketStartTimeNs + (4 * 60 + 20) * NS_PER_SEC); // 4:20 + event = CreateMoveToBackgroundEvent(bucketStartTimeNs + (4 * 60 + 20) * NS_PER_SEC, + appUid); // 4:20 processor->OnLogEvent(event.get()); - event = CreateAcquireWakelockEvent( - attributions1, "wl1", bucketStartTimeNs + (4 * 60 + 25) * NS_PER_SEC); // 4:25 + event = CreateAcquireWakelockEvent(bucketStartTimeNs + (4 * 60 + 25) * NS_PER_SEC, + attributionUids1, attributionTags1, "wl1"); // 4:25 processor->OnLogEvent(event.get()); - const int64_t duration2StartNs = bucketStartTimeNs + (4 * 60 + 30) * NS_PER_SEC; // 4:30 - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, duration2StartNs); + const int64_t duration2StartNs = bucketStartTimeNs + (4 * 60 + 30) * NS_PER_SEC; // 4:30 + event = CreateScreenStateChangedEvent(duration2StartNs, android::view::DISPLAY_STATE_ON); processor->OnLogEvent(event.get()); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); @@ -678,24 +689,27 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { vector<uint8_t> buffer; ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); - EXPECT_GT(buffer.size(), 0); + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); - auto data = reports.reports(0).metrics(0).duration_metrics().data(0); + DurationMetricData data = durationMetrics.data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, appUid); + util::WAKELOCK_STATE_CHANGED, appUid); // Validate bucket info. - EXPECT_EQ(2, data.bucket_info_size()); + ASSERT_EQ(2, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos()); @@ -708,6 +722,748 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos()); } +TEST(DurationMetricE2eTest, TestWithSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + | | | (ScreenIsOnEvent) + | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 310 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:20 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); // 6:10 + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(3, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create duration metric that has a condition and slices by screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->set_condition(deviceUnpluggedPredicate.id()); + durationMetric->add_slice_by_state(screenState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 (minutes) + |---------------------------------------|------------------ + ON OFF ON (BatterySaverMode) + T F T (DeviceUnpluggedPredicate) + | | | (ScreenIsOnEvent) + | | | (ScreenIsOffEvent) + | (ScreenDozeEvent) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 20 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:30 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 60 * NS_PER_SEC)); // 1:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 80 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 2:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 145 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:35 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:00 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // 3:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 200 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:30 + events.push_back( + CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC, + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 4:00 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 260 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 4:30 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary. + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 380 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 6:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(3, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(2); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher(); + *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher(); + + auto batterySaverModePredicate = CreateBatterySaverModePredicate(); + *config.add_predicate() = batterySaverModePredicate; + + int64_t screenOnId = 4444; + int64_t screenOffId = 9876; + auto screenStateWithMap = CreateScreenStateWithOnOffMap(screenOnId, screenOffId); + *config.add_state() = screenStateWithMap; + + // Create duration metric that slices by mapped screen state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped")); + durationMetric->set_what(batterySaverModePredicate.id()); + durationMetric->add_slice_by_state(screenStateWithMap.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 1); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + /* + bucket #1 bucket #2 + | 1 2 3 4 5 6 7 8 9 10 (minutes) + |-----------------------------|-----------------------------|-- + ON OFF ON (BatterySaverMode) + ---------------------------------------------------------SCREEN_OFF events + | | (ScreenStateOffEvent = 1) + | (ScreenStateDozeEvent = 3) + | (ScreenStateDozeSuspendEvent = 4) + ---------------------------------------------------------SCREEN_ON events + | | | (ScreenStateOnEvent = 2) + | (ScreenStateVrEvent = 5) + | (ScreenStateOnSuspendEvent = 6) + */ + // Initialize log events. + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 70 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:20 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 100 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:50 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 170 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:00 + events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 250 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20 + events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50 + + // Bucket boundary 5:10. + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 320 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:30 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 390 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40 + events.push_back(CreateScreenStateChangedEvent( + bucketStartTimeNs + 430 * NS_PER_SEC, + android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20 + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(2, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); +} + +TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // This config is rejected because the dimension in what fields are not a superset of the sliced + // state primary fields. + ASSERT_EQ(processor->mMetricsManagers.size(), 0); +} + +TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) { + // Initialize config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher(); + *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher(); + + auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); + *config.add_predicate() = holdingWakelockPredicate; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create duration metric that slices by uid process state. + auto durationMetric = config.add_duration_metric(); + durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState")); + durationMetric->set_what(holdingWakelockPredicate.id()); + durationMetric->add_slice_by_state(uidProcessState.id()); + durationMetric->set_aggregation_type(DurationMetric::SUM); + durationMetric->set_bucket(FIVE_MINUTES); + + // The metric is dimensioning by first uid of attribution node and tag. + *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions( + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */}); + // The state has only one primary field (uid). + auto stateLink = durationMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + // Initialize StatsLogProcessor. + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); + EXPECT_TRUE(metricsManager->isActive()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + EXPECT_TRUE(metricProducer->mIsActive); + ASSERT_EQ(metricProducer->mSlicedStateAtoms.size(), 1); + EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID); + ASSERT_EQ(metricProducer->mStateGroupMap.size(), 0); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Initialize log events. + int appUid1 = 1001; + int appUid2 = 1002; + std::vector<int> attributionUids1 = {appUid1}; + std::vector<string> attributionTags1 = {"App1"}; + + std::vector<int> attributionUids2 = {appUid2}; + std::vector<string> attributionTags2 = {"App2"}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 10 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:20 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock1")); // 0:30 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 0:35 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 0:40 + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock2")); // 0:45 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 50 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:00 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 60 * NS_PER_SEC, appUid1, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC, + attributionUids2, attributionTags2, + "wakelock1")); // 1:50 + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, appUid2, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 2:10 + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC, + attributionUids1, attributionTags1, + "wakelock2")); // 3:30 + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC, + true /* include current partial bucket */, true, ADB_DUMP, FAST, + &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(9, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(1); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(2); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(3); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(4); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(5); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock1"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(6); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(7); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos()); + EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos()); + + data = durationMetrics.data(8); + ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2, + "wakelock2"); + ASSERT_EQ(1, data.slice_by_state_size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp index c7ba9be3ca5a..1be261297e0a 100644 --- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp @@ -12,13 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <android/binder_interface_utils.h> #include <gtest/gtest.h> +#include <vector> + #include "src/StatsLogProcessor.h" #include "src/stats_log_util.h" #include "tests/statsd_test_util.h" -#include <vector> +using ::ndk::SharedRefBase; namespace android { namespace os { @@ -29,12 +32,14 @@ namespace statsd { namespace { const int64_t metricId = 123456; +const int32_t ATOM_TAG = util::SUBSYSTEM_SLEEP_STATE; StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type, bool useCondition = true) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. - auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE); + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", ATOM_TAG); *config.add_atom_matcher() = atomMatcher; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); @@ -51,7 +56,7 @@ StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type, gaugeMetric->set_sampling_type(sampling_type); gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true); *gaugeMetric->mutable_dimensions_in_what() = - CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + CreateDimensions(ATOM_TAG, {1 /* subsystem name */}); gaugeMetric->set_bucket(FIVE_MINUTES); gaugeMetric->set_max_pull_delay_sec(INT_MAX); config.set_hash_strings_in_metric_report(false); @@ -59,67 +64,67 @@ StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type, return config; } -} // namespace +} // namespaces TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents) { auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE); int64_t baseTimeNs = getElapsedRealtimeNs(); int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); EXPECT_GT(startBucketNum, (int64_t)0); // When creating the config, the gauge metric producer should register the alarm at the // end of the current bucket. - EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); EXPECT_EQ(bucketSizeNs, processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); int64_t& nextPullTimeNs = processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); - auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 55); + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); // Pulling alarm arrives on time and reset the sequential pulling alarm. processor->informPullAlarmFired(nextPullTimeNs + 1); EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); - auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + bucketSizeNs + 10); + auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + bucketSizeNs + 100); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); processor->informPullAlarmFired(nextPullTimeNs + 1); - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, - nextPullTimeNs); + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); processor->informPullAlarmFired(nextPullTimeNs + 1); EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs); - screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + 3 * bucketSizeNs + 2); + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); processor->informPullAlarmFired(nextPullTimeNs + 3); EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 5 * bucketSizeNs + 1); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); processor->informPullAlarmFired(nextPullTimeNs + 2); @@ -136,70 +141,64 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - EXPECT_GT((int)gaugeMetrics.data_size(), 1); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 1); auto data = gaugeMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - EXPECT_EQ(6, data.bucket_info_size()); + ASSERT_EQ(6, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).atom_size()); - EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, - data.bucket_info(1).elapsed_timestamp_nanos(0)); + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(2).atom_size()); - EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, - data.bucket_info(2).elapsed_timestamp_nanos(0)); + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(3).atom_size()); - EXPECT_EQ(1, data.bucket_info(3).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 1, - data.bucket_info(3).elapsed_timestamp_nanos(0)); + ASSERT_EQ(1, data.bucket_info(3).atom_size()); + ASSERT_EQ(1, data.bucket_info(3).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 1, data.bucket_info(3).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(3).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(3).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(4).atom_size()); - EXPECT_EQ(1, data.bucket_info(4).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, - data.bucket_info(4).elapsed_timestamp_nanos(0)); + ASSERT_EQ(1, data.bucket_info(4).atom_size()); + ASSERT_EQ(1, data.bucket_info(4).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(4).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(4).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(4).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(4).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(4).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(5).atom_size()); - EXPECT_EQ(1, data.bucket_info(5).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs + 2, - data.bucket_info(5).elapsed_timestamp_nanos(0)); + ASSERT_EQ(1, data.bucket_info(5).atom_size()); + ASSERT_EQ(1, data.bucket_info(5).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs + 2, data.bucket_info(5).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(5).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(5).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(5).atom(0).subsystem_sleep_state().subsystem_name().empty()); @@ -210,44 +209,45 @@ TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents) { auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE); int64_t baseTimeNs = getElapsedRealtimeNs(); int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); EXPECT_GT(startBucketNum, (int64_t)0); - auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 55); + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); - auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + bucketSizeNs + 10); + auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + bucketSizeNs + 100); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 100, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); - screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + 3 * bucketSizeNs + 2); + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 2, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 5 * bucketSizeNs + 1); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 1, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); - screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + 5 * bucketSizeNs + 3); + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 3, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 5 * bucketSizeNs + 10); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 5 * bucketSizeNs + 10, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); ConfigMetricsReportList reports; @@ -259,45 +259,41 @@ TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - EXPECT_GT((int)gaugeMetrics.data_size(), 1); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 1); auto data = gaugeMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - EXPECT_EQ(3, data.bucket_info_size()); + ASSERT_EQ(3, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).atom_size()); - EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 100, - data.bucket_info(1).elapsed_timestamp_nanos(0)); + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 100, data.bucket_info(1).elapsed_timestamp_nanos(0)); EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(2, data.bucket_info(2).atom_size()); - EXPECT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, - data.bucket_info(2).elapsed_timestamp_nanos(0)); - EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 10, - data.bucket_info(2).elapsed_timestamp_nanos(1)); + ASSERT_EQ(2, data.bucket_info(2).atom_size()); + ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1, data.bucket_info(2).elapsed_timestamp_nanos(0)); + EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 10, data.bucket_info(2).elapsed_timestamp_nanos(1)); EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); @@ -306,48 +302,48 @@ TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents) { EXPECT_GT(data.bucket_info(2).atom(1).subsystem_sleep_state().time_millis(), 0); } - TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm) { auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE); int64_t baseTimeNs = getElapsedRealtimeNs(); int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); EXPECT_GT(startBucketNum, (int64_t)0); // When creating the config, the gauge metric producer should register the alarm at the // end of the current bucket. - EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); EXPECT_EQ(bucketSizeNs, processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); int64_t& nextPullTimeNs = processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs); - auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 55); + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); - auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + bucketSizeNs + 10); + auto screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + bucketSizeNs + 10, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); // Pulling alarm arrives one bucket size late. processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs); EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 3 * bucketSizeNs + 11); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 3 * bucketSizeNs + 11, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); // Pulling alarm arrives more than one bucket size late. @@ -363,30 +359,29 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - EXPECT_GT((int)gaugeMetrics.data_size(), 1); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 1); auto data = gaugeMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - EXPECT_EQ(3, data.bucket_info_size()); + ASSERT_EQ(3, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).atom_size()); - EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(1).atom_size()); + ASSERT_EQ(1, data.bucket_info(1).atom_size()); EXPECT_EQ(configAddedTimeNs + 3 * bucketSizeNs + 11, data.bucket_info(1).elapsed_timestamp_nanos(0)); EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0)); @@ -395,10 +390,9 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm) { EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(2).atom_size()); - EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); - EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs + 12, - data.bucket_info(2).elapsed_timestamp_nanos(0)); + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs + 12, data.bucket_info(2).elapsed_timestamp_nanos(0)); EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); @@ -410,12 +404,11 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation) { int64_t baseTimeNs = getElapsedRealtimeNs(); int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher(); *config.add_atom_matcher() = batterySaverStartMatcher; - const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. + const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. auto metric_activation = config.add_metric_activation(); metric_activation->set_metric_id(metricId); metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY); @@ -424,20 +417,22 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation) { event_activation->set_ttl_seconds(ttlNs / 1000000000); ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); EXPECT_GT(startBucketNum, (int64_t)0); EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); // When creating the config, the gauge metric producer should register the alarm at the // end of the current bucket. - EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); EXPECT_EQ(bucketSizeNs, processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); int64_t& nextPullTimeNs = @@ -446,27 +441,26 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation) { // Pulling alarm arrives on time and reset the sequential pulling alarm. // Event should not be kept. - processor->informPullAlarmFired(nextPullTimeNs + 1); // 15 mins + 1 ns. + processor->informPullAlarmFired(nextPullTimeNs + 1); // 15 mins + 1 ns. EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs); EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); // Activate the metric. A pull occurs upon activation. - const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. + const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs); - processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. + processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); // This event should be kept. 2 total. - processor->informPullAlarmFired(nextPullTimeNs + 1); // 20 mins + 1 ns. - EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, - nextPullTimeNs); + processor->informPullAlarmFired(nextPullTimeNs + 1); // 20 mins + 1 ns. + EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs); // This event should be kept. 3 total. - processor->informPullAlarmFired(nextPullTimeNs + 2); // 25 mins + 2 ns. + processor->informPullAlarmFired(nextPullTimeNs + 2); // 25 mins + 2 ns. EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs); // Create random event to deactivate metric. - auto deactivationEvent = CreateScreenBrightnessChangedEvent(50, activationNs + ttlNs + 1); + auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50); processor->OnLogEvent(deactivationEvent.get()); EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); @@ -485,50 +479,49 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - EXPECT_GT((int)gaugeMetrics.data_size(), 0); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + ASSERT_GT((int)gaugeMetrics.data_size(), 0); auto data = gaugeMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - EXPECT_EQ(3, data.bucket_info_size()); + ASSERT_EQ(3, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); - EXPECT_EQ(1, bucketInfo.atom_size()); - EXPECT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, bucketInfo.atom_size()); + ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); EXPECT_EQ(activationNs, bucketInfo.elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); bucketInfo = data.bucket_info(1); - EXPECT_EQ(1, bucketInfo.atom_size()); - EXPECT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, bucketInfo.atom_size()); + ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1, bucketInfo.elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); bucketInfo = data.bucket_info(2); - EXPECT_EQ(1, bucketInfo.atom_size()); - EXPECT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, bucketInfo.atom_size()); + ASSERT_EQ(1, bucketInfo.elapsed_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 2, bucketInfo.elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, bucketInfo.wall_clock_timestamp_nanos_size()); EXPECT_EQ(MillisToNano(NanoToMillis(baseTimeNs + 5 * bucketSizeNs)), - bucketInfo.start_bucket_elapsed_nanos()); + bucketInfo.start_bucket_elapsed_nanos()); EXPECT_EQ(MillisToNano(NanoToMillis(activationNs + ttlNs + 1)), - bucketInfo.end_bucket_elapsed_nanos()); + bucketInfo.end_bucket_elapsed_nanos()); EXPECT_TRUE(bucketInfo.atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(bucketInfo.atom(0).subsystem_sleep_state().time_millis(), 0); } @@ -542,9 +535,10 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition) { TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), + ATOM_TAG); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); @@ -554,7 +548,7 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition) { // When creating the config, the gauge metric producer should register the alarm at the // end of the current bucket. - EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); EXPECT_EQ(bucketSizeNs, processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); int64_t& nextPullTimeNs = @@ -578,43 +572,43 @@ TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; sortMetricDataByDimensionsValue( reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - EXPECT_GT((int)gaugeMetrics.data_size(), 0); + ASSERT_GT((int)gaugeMetrics.data_size(), 0); auto data = gaugeMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - EXPECT_EQ(3, data.bucket_info_size()); + ASSERT_EQ(3, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).atom_size()); - EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(0).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1, data.bucket_info(1).elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, data.bucket_info(1).wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, data.bucket_info(1).wall_clock_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(1).atom(0).subsystem_sleep_state().subsystem_name().empty()); EXPECT_GT(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 0); - EXPECT_EQ(1, data.bucket_info(2).atom_size()); - EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 4, data.bucket_info(2).elapsed_timestamp_nanos(0)); - EXPECT_EQ(0, data.bucket_info(2).wall_clock_timestamp_nanos_size()); + ASSERT_EQ(0, data.bucket_info(2).wall_clock_timestamp_nanos_size()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); EXPECT_TRUE(data.bucket_info(2).atom(0).subsystem_sleep_state().subsystem_name().empty()); diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp index cd80310c4e72..a40a9484a841 100644 --- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp @@ -14,12 +14,13 @@ #include <gtest/gtest.h> +#include <vector> + #include "src/StatsLogProcessor.h" #include "src/stats_log_util.h" +#include "stats_event.h" #include "tests/statsd_test_util.h" -#include <vector> - namespace android { namespace os { namespace statsd { @@ -34,12 +35,12 @@ StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sa *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); - auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_OCCURRED); + auto atomMatcher = CreateSimpleAtomMatcher("", util::APP_START_OCCURRED); *config.add_atom_matcher() = atomMatcher; auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); *config.add_predicate() = isInBackgroundPredicate; auto gaugeMetric = config.add_gauge_metric(); @@ -49,39 +50,43 @@ StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sa gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); gaugeMetric->set_sampling_type(sampling_type); auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); - fieldMatcher->set_field(android::util::APP_START_OCCURRED); + fieldMatcher->set_field(util::APP_START_OCCURRED); fieldMatcher->add_child()->set_field(3); // type (enum) fieldMatcher->add_child()->set_field(4); // activity_name(str) fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) *gaugeMetric->mutable_dimensions_in_what() = - CreateDimensions(android::util::APP_START_OCCURRED, {1 /* uid field */ }); + CreateDimensions(util::APP_START_OCCURRED, {1 /* uid field */ }); gaugeMetric->set_bucket(FIVE_MINUTES); auto links = gaugeMetric->add_links(); links->set_condition(isInBackgroundPredicate.id()); auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::APP_START_OCCURRED); + dimensionWhat->set_field(util::APP_START_OCCURRED); dimensionWhat->add_child()->set_field(1); // uid field. auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); dimensionCondition->add_child()->set_field(1); // uid field. return config; } std::unique_ptr<LogEvent> CreateAppStartOccurredEvent( - const int uid, const string& pkg_name, AppStartOccurred::TransitionType type, - const string& activity_name, const string& calling_pkg_name, const bool is_instant_app, - int64_t activity_start_msec, uint64_t timestampNs) { - auto logEvent = std::make_unique<LogEvent>( - android::util::APP_START_OCCURRED, timestampNs); - logEvent->write(uid); - logEvent->write(pkg_name); - logEvent->write(type); - logEvent->write(activity_name); - logEvent->write(calling_pkg_name); - logEvent->write(is_instant_app); - logEvent->write(activity_start_msec); - logEvent->init(); + uint64_t timestampNs, const int uid, const string& pkg_name, + AppStartOccurred::TransitionType type, const string& activity_name, + const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::APP_START_OCCURRED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, pkg_name.c_str()); + AStatsEvent_writeInt32(statsEvent, type); + AStatsEvent_writeString(statsEvent, activity_name.c_str()); + AStatsEvent_writeString(statsEvent, calling_pkg_name.c_str()); + AStatsEvent_writeInt32(statsEvent, is_instant_app); + AStatsEvent_writeInt32(statsEvent, activity_start_msec); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } @@ -89,58 +94,57 @@ std::unique_ptr<LogEvent> CreateAppStartOccurredEvent( TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { for (const auto& sampling_type : - { GaugeMetric::FIRST_N_SAMPLES, GaugeMetric:: RANDOM_ONE_SAMPLE }) { + {GaugeMetric::FIRST_N_SAMPLES, GaugeMetric::RANDOM_ONE_SAMPLE}) { auto config = CreateStatsdConfigForPushedEvent(sampling_type); int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); int appUid1 = 123; int appUid2 = 456; std::vector<std::unique_ptr<LogEvent>> events; - events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15)); - events.push_back(CreateMoveToForegroundEvent( - appUid1, bucketStartTimeNs + bucketSizeNs + 250)); - events.push_back(CreateMoveToBackgroundEvent( - appUid1, bucketStartTimeNs + bucketSizeNs + 350)); - events.push_back(CreateMoveToForegroundEvent( - appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100)); - + events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid1)); + events.push_back( + CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid1)); + events.push_back( + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid1)); + events.push_back( + CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, appUid1)); events.push_back(CreateAppStartOccurredEvent( - appUid1, "app1", AppStartOccurred::WARM, "activity_name1", "calling_pkg_name1", - true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10)); + bucketStartTimeNs + 10, appUid1, "app1", AppStartOccurred::WARM, "activity_name1", + "calling_pkg_name1", true /*is_instant_app*/, 101 /*activity_start_msec*/)); events.push_back(CreateAppStartOccurredEvent( - appUid1, "app1", AppStartOccurred::HOT, "activity_name2", "calling_pkg_name2", - true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20)); + bucketStartTimeNs + 20, appUid1, "app1", AppStartOccurred::HOT, "activity_name2", + "calling_pkg_name2", true /*is_instant_app*/, 102 /*activity_start_msec*/)); events.push_back(CreateAppStartOccurredEvent( - appUid1, "app1", AppStartOccurred::COLD, "activity_name3", "calling_pkg_name3", - true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30)); + bucketStartTimeNs + 30, appUid1, "app1", AppStartOccurred::COLD, "activity_name3", + "calling_pkg_name3", true /*is_instant_app*/, 103 /*activity_start_msec*/)); events.push_back(CreateAppStartOccurredEvent( - appUid1, "app1", AppStartOccurred::WARM, "activity_name4", "calling_pkg_name4", - true /*is_instant_app*/, 104 /*activity_start_msec*/, - bucketStartTimeNs + bucketSizeNs + 30)); + bucketStartTimeNs + bucketSizeNs + 30, appUid1, "app1", AppStartOccurred::WARM, + "activity_name4", "calling_pkg_name4", true /*is_instant_app*/, + 104 /*activity_start_msec*/)); events.push_back(CreateAppStartOccurredEvent( - appUid1, "app1", AppStartOccurred::COLD, "activity_name5", "calling_pkg_name5", - true /*is_instant_app*/, 105 /*activity_start_msec*/, - bucketStartTimeNs + 2 * bucketSizeNs)); + bucketStartTimeNs + 2 * bucketSizeNs, appUid1, "app1", AppStartOccurred::COLD, + "activity_name5", "calling_pkg_name5", true /*is_instant_app*/, + 105 /*activity_start_msec*/)); events.push_back(CreateAppStartOccurredEvent( - appUid1, "app1", AppStartOccurred::HOT, "activity_name6", "calling_pkg_name6", - false /*is_instant_app*/, 106 /*activity_start_msec*/, - bucketStartTimeNs + 2 * bucketSizeNs + 10)); + bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid1, "app1", AppStartOccurred::HOT, + "activity_name6", "calling_pkg_name6", false /*is_instant_app*/, + 106 /*activity_start_msec*/)); - events.push_back(CreateMoveToBackgroundEvent( - appUid2, bucketStartTimeNs + bucketSizeNs + 10)); + events.push_back( + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 10, appUid2)); events.push_back(CreateAppStartOccurredEvent( - appUid2, "app2", AppStartOccurred::COLD, "activity_name7", "calling_pkg_name7", - true /*is_instant_app*/, 201 /*activity_start_msec*/, - bucketStartTimeNs + 2 * bucketSizeNs + 10)); + bucketStartTimeNs + 2 * bucketSizeNs + 10, appUid2, "app2", AppStartOccurred::COLD, + "activity_name7", "calling_pkg_name7", true /*is_instant_app*/, + 201 /*activity_start_msec*/)); sortLogEventsByTimestamp(&events); @@ -149,31 +153,31 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { } ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); - EXPECT_EQ(2, gaugeMetrics.data_size()); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), + &gaugeMetrics); + ASSERT_EQ(2, gaugeMetrics.data_size()); auto data = gaugeMetrics.data(0); - EXPECT_EQ(android::util::APP_START_OCCURRED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::APP_START_OCCURRED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(appUid1, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(3, data.bucket_info_size()); + ASSERT_EQ(3, data.bucket_info_size()); if (sampling_type == GaugeMetric::FIRST_N_SAMPLES) { - EXPECT_EQ(2, data.bucket_info(0).atom_size()); - EXPECT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size()); - EXPECT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); + ASSERT_EQ(2, data.bucket_info(0).atom_size()); + ASSERT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(0, data.bucket_info(0).wall_clock_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); @@ -190,8 +194,8 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(103L, data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis()); - EXPECT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, @@ -203,8 +207,8 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(104L, data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); - EXPECT_EQ(2, data.bucket_info(2).atom_size()); - EXPECT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); + ASSERT_EQ(2, data.bucket_info(2).atom_size()); + ASSERT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, @@ -222,8 +226,8 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(106L, data.bucket_info(2).atom(1).app_start_occurred().activity_start_millis()); } else { - EXPECT_EQ(1, data.bucket_info(0).atom_size()); - EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); @@ -234,8 +238,8 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(102L, data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis()); - EXPECT_EQ(1, data.bucket_info(1).atom_size()); - EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(1).atom_size()); + ASSERT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, @@ -247,8 +251,8 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(104L, data.bucket_info(1).atom(0).app_start_occurred().activity_start_millis()); - EXPECT_EQ(1, data.bucket_info(2).atom_size()); - EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info(2).atom_size()); + ASSERT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, @@ -263,14 +267,14 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { data = gaugeMetrics.data(1); - EXPECT_EQ(data.dimensions_in_what().field(), android::util::APP_START_OCCURRED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().field(), util::APP_START_OCCURRED); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(appUid2, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); - EXPECT_EQ(1, data.bucket_info(0).atom_size()); - EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); + ASSERT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + ASSERT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp index f1b6029f0ab0..e320419a9d44 100644 --- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp @@ -45,7 +45,7 @@ StatsdConfig CreateStatsdConfig() { countMetric->set_what(crashMatcher.id()); countMetric->set_bucket(FIVE_MINUTES); countMetric->mutable_dimensions_in_what()->set_field( - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field auto metric_activation1 = config.add_metric_activation(); @@ -79,7 +79,7 @@ StatsdConfig CreateStatsdConfigWithOneDeactivation() { countMetric->set_what(crashMatcher.id()); countMetric->set_bucket(FIVE_MINUTES); countMetric->mutable_dimensions_in_what()->set_field( - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field auto metric_activation1 = config.add_metric_activation(); @@ -117,7 +117,7 @@ StatsdConfig CreateStatsdConfigWithTwoDeactivations() { countMetric->set_what(crashMatcher.id()); countMetric->set_bucket(FIVE_MINUTES); countMetric->mutable_dimensions_in_what()->set_field( - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field auto metric_activation1 = config.add_metric_activation(); @@ -153,7 +153,7 @@ StatsdConfig CreateStatsdConfigWithSameDeactivations() { countMetric->set_what(crashMatcher.id()); countMetric->set_bucket(FIVE_MINUTES); countMetric->mutable_dimensions_in_what()->set_field( - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field auto metric_activation1 = config.add_metric_activation(); @@ -194,7 +194,7 @@ StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() { countMetric->set_what(crashMatcher.id()); countMetric->set_bucket(FIVE_MINUTES); countMetric->mutable_dimensions_in_what()->set_field( - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + util::PROCESS_LIFE_CYCLE_STATE_CHANGED); countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field int64_t metricId2 = 234567; @@ -203,7 +203,7 @@ StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() { countMetric->set_what(foregroundMatcher.id()); countMetric->set_bucket(FIVE_MINUTES); countMetric->mutable_dimensions_in_what()->set_field( - android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + util::ACTIVITY_FOREGROUND_STATE_CHANGED); countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field auto metric_activation1 = config.add_metric_activation(); @@ -236,7 +236,7 @@ StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() { TEST(MetricActivationE2eTest, TestCountMetric) { auto config = CreateStatsdConfig(); - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; @@ -252,24 +252,25 @@ TEST(MetricActivationE2eTest, TestCountMetric) { long timeBase1 = 1; int broadcastCount = 0; - StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, - bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector<int64_t>& activeConfigs) { + const vector<int64_t>& activeConfigs) { broadcastCount++; EXPECT_EQ(broadcastUid, uid); activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), - activeConfigs.begin(), activeConfigs.end()); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); return true; }); processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; @@ -277,7 +278,7 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_FALSE(metricProducer->mIsActive); // Two activations: one is triggered by battery saver mode (tracker index 0), the other is // triggered by screen on event (tracker index 2). - EXPECT_EQ(eventActivationMap.size(), 2u); + ASSERT_EQ(eventActivationMap.size(), 2u); EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); @@ -289,19 +290,19 @@ TEST(MetricActivationE2eTest, TestCountMetric) { std::unique_ptr<LogEvent> event; - event = CreateAppCrashEvent(111, bucketStartTimeNs + 5); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 0); // Activated by battery save mode. event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); @@ -311,13 +312,12 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); // First processed event. - event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); // Activated by screen on event. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 20); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -329,8 +329,8 @@ TEST(MetricActivationE2eTest, TestCountMetric) { // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -343,17 +343,17 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_EQ(broadcastCount, 1); // 3rd processed event. - event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); // All activations expired. - event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 2); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -362,13 +362,13 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); @@ -378,55 +378,53 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); // 4th processed event. - event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); ConfigMetricsReportList reports; vector<uint8_t> buffer; processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); + ADB_DUMP, FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(4, reports.reports(0).metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).count_metrics(), &countMetrics); - EXPECT_EQ(4, countMetrics.data_size()); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(4, countMetrics.data_size()); auto data = countMetrics.data(0); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(1); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(2); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); // Partial bucket as metric is deactivated. EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -434,23 +432,22 @@ TEST(MetricActivationE2eTest, TestCountMetric) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(3); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); - EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, - data.bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); } TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { auto config = CreateStatsdConfigWithOneDeactivation(); - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; @@ -466,24 +463,25 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { long timeBase1 = 1; int broadcastCount = 0; - StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, - bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector<int64_t>& activeConfigs) { + const vector<int64_t>& activeConfigs) { broadcastCount++; EXPECT_EQ(broadcastUid, uid); activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), - activeConfigs.begin(), activeConfigs.end()); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); return true; }); processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; @@ -492,7 +490,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_FALSE(metricProducer->mIsActive); // Two activations: one is triggered by battery saver mode (tracker index 0), the other is // triggered by screen on event (tracker index 2). - EXPECT_EQ(eventActivationMap.size(), 2u); + ASSERT_EQ(eventActivationMap.size(), 2u); EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); @@ -501,26 +499,26 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap.size(), 1u); + ASSERT_EQ(eventDeactivationMap.size(), 1u); EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); - EXPECT_EQ(eventDeactivationMap[3].size(), 1u); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); std::unique_ptr<LogEvent> event; - event = CreateAppCrashEvent(111, bucketStartTimeNs + 5); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 0); // Activated by battery save mode. event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); @@ -531,13 +529,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // First processed event. - event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); // Activated by screen on event. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 20); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -550,8 +547,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -565,17 +562,17 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(broadcastCount, 1); // 3rd processed event. - event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); // All activations expired. - event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 2); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -585,13 +582,13 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); @@ -602,16 +599,16 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // 4th processed event. - event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); // Re-enable battery saver mode activation. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -622,16 +619,16 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // 5th processed event. - event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); // Cancel battery saver mode activation. - event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -642,13 +639,13 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // Screen-on activation expired. - event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 4); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -657,16 +654,16 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); - event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); // Re-enable battery saver mode activation. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 5); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); @@ -677,12 +674,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // Cancel battery saver mode activation. - event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 6); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -694,49 +691,47 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { ConfigMetricsReportList reports; vector<uint8_t> buffer; processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); + ADB_DUMP, FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).count_metrics(), &countMetrics); - EXPECT_EQ(5, countMetrics.data_size()); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); auto data = countMetrics.data(0); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(1); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(2); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); // Partial bucket as metric is deactivated. EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -744,12 +739,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(3); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -757,12 +752,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(4); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -773,7 +768,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { auto config = CreateStatsdConfigWithTwoDeactivations(); - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; @@ -789,24 +784,25 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { long timeBase1 = 1; int broadcastCount = 0; - StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, - bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector<int64_t>& activeConfigs) { + const vector<int64_t>& activeConfigs) { broadcastCount++; EXPECT_EQ(broadcastUid, uid); activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), - activeConfigs.begin(), activeConfigs.end()); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); return true; }); processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; @@ -815,7 +811,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_FALSE(metricProducer->mIsActive); // Two activations: one is triggered by battery saver mode (tracker index 0), the other is // triggered by screen on event (tracker index 2). - EXPECT_EQ(eventActivationMap.size(), 2u); + ASSERT_EQ(eventActivationMap.size(), 2u); EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); @@ -824,29 +820,29 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap.size(), 2u); + ASSERT_EQ(eventDeactivationMap.size(), 2u); EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); - EXPECT_EQ(eventDeactivationMap[3].size(), 1u); - EXPECT_EQ(eventDeactivationMap[4].size(), 1u); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); + ASSERT_EQ(eventDeactivationMap[4].size(), 1u); EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); std::unique_ptr<LogEvent> event; - event = CreateAppCrashEvent(111, bucketStartTimeNs + 5); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 0); // Activated by battery save mode. event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); @@ -858,13 +854,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // First processed event. - event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); // Activated by screen on event. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 20); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -878,8 +873,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -894,17 +889,17 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(broadcastCount, 1); // 3rd processed event. - event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); // All activations expired. - event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 2); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -915,13 +910,13 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); @@ -933,16 +928,16 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // 4th processed event. - event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); // Re-enable battery saver mode activation. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -954,17 +949,17 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // 5th processed event. - event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 4); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -975,12 +970,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // Screen-on activation expired. - event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 4); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -990,16 +985,16 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); // Re-enable battery saver mode activation. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 5); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); @@ -1011,12 +1006,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 6); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); @@ -1029,49 +1024,47 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { ConfigMetricsReportList reports; vector<uint8_t> buffer; processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); + ADB_DUMP, FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).count_metrics(), &countMetrics); - EXPECT_EQ(5, countMetrics.data_size()); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); auto data = countMetrics.data(0); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(1); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(2); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); // Partial bucket as metric is deactivated. EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -1079,12 +1072,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(3); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -1092,12 +1085,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(4); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -1108,7 +1101,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { auto config = CreateStatsdConfigWithSameDeactivations(); - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; @@ -1124,24 +1117,25 @@ TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { long timeBase1 = 1; int broadcastCount = 0; - StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, - bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector<int64_t>& activeConfigs) { + const vector<int64_t>& activeConfigs) { broadcastCount++; EXPECT_EQ(broadcastUid, uid); activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), - activeConfigs.begin(), activeConfigs.end()); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); return true; }); processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 1); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; @@ -1150,7 +1144,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { EXPECT_FALSE(metricProducer->mIsActive); // Two activations: one is triggered by battery saver mode (tracker index 0), the other is // triggered by screen on event (tracker index 2). - EXPECT_EQ(eventActivationMap.size(), 2u); + ASSERT_EQ(eventActivationMap.size(), 2u); EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); @@ -1159,9 +1153,9 @@ TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap.size(), 1u); + ASSERT_EQ(eventDeactivationMap.size(), 1u); EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); - EXPECT_EQ(eventDeactivationMap[3].size(), 2u); + ASSERT_EQ(eventDeactivationMap[3].size(), 2u); EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); EXPECT_EQ(eventDeactivationMap[3][1], eventActivationMap[2]); EXPECT_EQ(broadcastCount, 0); @@ -1169,28 +1163,28 @@ TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { std::unique_ptr<LogEvent> event; // Event that should be ignored. - event = CreateAppCrashEvent(111, bucketStartTimeNs + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 1, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 1); // Activate metric via screen on for 2 minutes. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); // 1st processed event. - event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); // Enable battery saver mode activation for 5 minutes. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 10); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 1); @@ -1200,112 +1194,111 @@ TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); // 2nd processed event. - event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 + 40); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 40, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 + 40); // Cancel battery saver mode and screen on activation. int64_t firstDeactivation = bucketStartTimeNs + NS_PER_SEC * 61; - event = CreateScreenBrightnessChangedEvent(64, firstDeactivation); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(firstDeactivation, 64); + processor.OnLogEvent(event.get(), firstDeactivation); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 2); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); // Should be ignored - event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 61 + 80); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 61 + 80, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 61 + 80); // Re-enable battery saver mode activation. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); // 3rd processed event. - event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80); // Cancel battery saver mode activation. int64_t secondDeactivation = bucketStartTimeNs + NS_PER_SEC * 60 * 13; - event = CreateScreenBrightnessChangedEvent(140, secondDeactivation); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(secondDeactivation, 140); + processor.OnLogEvent(event.get(), secondDeactivation); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(broadcastCount, 4); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); // Should be ignored. - event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80); ConfigMetricsReportList reports; vector<uint8_t> buffer; processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); + ADB_DUMP, FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(3, reports.reports(0).metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).count_metrics(), &countMetrics); - EXPECT_EQ(3, countMetrics.data_size()); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); auto data = countMetrics.data(0); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(1); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(2); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(555, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); // Partial bucket as metric is deactivated. - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(secondDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); } TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { auto config = CreateStatsdConfigWithTwoMetricsTwoDeactivations(); - int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; @@ -1321,24 +1314,25 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { long timeBase1 = 1; int broadcastCount = 0; - StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, - bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + StatsLogProcessor processor( + m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs, + [](const ConfigKey& key) { return true; }, [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, - const vector<int64_t>& activeConfigs) { + const vector<int64_t>& activeConfigs) { broadcastCount++; EXPECT_EQ(broadcastUid, uid); activeConfigsBroadcast.clear(); - activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), - activeConfigs.begin(), activeConfigs.end()); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(), + activeConfigs.end()); return true; }); processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); - EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + ASSERT_EQ(processor.mMetricsManagers.size(), 1u); sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; EXPECT_TRUE(metricsManager->isConfigValid()); - EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 2); + ASSERT_EQ(metricsManager->mAllMetricProducers.size(), 2); sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; auto& eventActivationMap = metricProducer->mEventActivationMap; auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; @@ -1351,7 +1345,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_FALSE(metricProducer2->mIsActive); // Two activations: one is triggered by battery saver mode (tracker index 0), the other is // triggered by screen on event (tracker index 2). - EXPECT_EQ(eventActivationMap.size(), 2u); + ASSERT_EQ(eventActivationMap.size(), 2u); EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); @@ -1360,15 +1354,15 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap.size(), 2u); + ASSERT_EQ(eventDeactivationMap.size(), 2u); EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); - EXPECT_EQ(eventDeactivationMap[3].size(), 1u); - EXPECT_EQ(eventDeactivationMap[4].size(), 1u); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); + ASSERT_EQ(eventDeactivationMap[4].size(), 1u); EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); - EXPECT_EQ(eventActivationMap2.size(), 2u); + ASSERT_EQ(eventActivationMap2.size(), 2u); EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end()); EXPECT_TRUE(eventActivationMap2.find(2) != eventActivationMap2.end()); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); @@ -1377,20 +1371,20 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, 0); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2.size(), 2u); + ASSERT_EQ(eventDeactivationMap2.size(), 2u); EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end()); EXPECT_TRUE(eventDeactivationMap2.find(4) != eventDeactivationMap2.end()); - EXPECT_EQ(eventDeactivationMap[3].size(), 1u); - EXPECT_EQ(eventDeactivationMap[4].size(), 1u); + ASSERT_EQ(eventDeactivationMap[3].size(), 1u); + ASSERT_EQ(eventDeactivationMap[4].size(), 1u); EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); std::unique_ptr<LogEvent> event; - event = CreateAppCrashEvent(111, bucketStartTimeNs + 5); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(1111, bucketStartTimeNs + 5); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 5, 111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + 5, 1111); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 5); EXPECT_FALSE(metricsManager->isActive()); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_FALSE(metricProducer2->mIsActive); @@ -1398,10 +1392,10 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { // Activated by battery save mode. event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_EQ(broadcastCount, 1); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -1423,15 +1417,14 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // First processed event. - event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(2222, bucketStartTimeNs + 15); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + 15, 222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + 15, 2222); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 15); // Activated by screen on event. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + 20); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + 20, android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + 20); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -1454,10 +1447,10 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. - event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(3333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25, 3333); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -1481,20 +1474,20 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(broadcastCount, 1); // 3rd processed event. - event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(4444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25, 4444); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); // All activations expired. - event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(5555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 8, 5555); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 8); EXPECT_FALSE(metricsManager->isActive()); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 2); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10); @@ -1515,12 +1508,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // Re-activate metric via screen on. - event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - processor.OnLogEvent(event.get()); + event = CreateScreenStateChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10, + android::view::DISPLAY_STATE_ON); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_TRUE(metricsManager->isActive()); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); @@ -1542,17 +1535,17 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // 4th processed event. - event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(6666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1, 6666); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); // Re-enable battery saver mode activation. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); EXPECT_TRUE(metricsManager->isActive()); EXPECT_EQ(broadcastCount, 3); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -1574,18 +1567,18 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // 5th processed event. - event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(7777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40, 7777); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60, 64); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); EXPECT_FALSE(metricsManager->isActive()); // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 4); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -1606,13 +1599,13 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // Screen-on activation expired. - event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(8888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 13, 8888); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 13); EXPECT_FALSE(metricsManager->isActive()); EXPECT_EQ(broadcastCount, 4); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -1632,17 +1625,17 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); - event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - processor.OnLogEvent(event.get()); - event = CreateMoveToForegroundEvent(9999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); - processor.OnLogEvent(event.get()); + event = CreateAppCrashEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + event = CreateMoveToForegroundEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1, 9999); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); // Re-enable battery saver mode activation. event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); - processor.OnLogEvent(event.get()); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); EXPECT_TRUE(metricsManager->isActive()); EXPECT_EQ(broadcastCount, 5); - EXPECT_EQ(activeConfigsBroadcast.size(), 1); + ASSERT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); EXPECT_TRUE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); @@ -1664,11 +1657,11 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // Cancel battery saver mode and screen on activation. - event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); - processor.OnLogEvent(event.get()); + event = CreateScreenBrightnessChangedEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 16, 140); + processor.OnLogEvent(event.get(), bucketStartTimeNs + NS_PER_SEC * 60 * 16); EXPECT_FALSE(metricsManager->isActive()); EXPECT_EQ(broadcastCount, 6); - EXPECT_EQ(activeConfigsBroadcast.size(), 0); + ASSERT_EQ(activeConfigsBroadcast.size(), 0); EXPECT_FALSE(metricProducer->mIsActive); EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); @@ -1691,51 +1684,48 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { ConfigMetricsReportList reports; vector<uint8_t> buffer; processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, - ADB_DUMP, FAST, &buffer); + ADB_DUMP, FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(2, reports.reports(0).metrics_size()); - EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size()); - EXPECT_EQ(5, reports.reports(0).metrics(1).count_metrics().data_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(2, reports.reports(0).metrics_size()); StatsLogReport::CountMetricDataWrapper countMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).count_metrics(), &countMetrics); - EXPECT_EQ(5, countMetrics.data_size()); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); auto data = countMetrics.data(0); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(1); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(2); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); // Partial bucket as metric is deactivated. EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -1743,12 +1733,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(3); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -1756,53 +1746,51 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(4); - EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, data.bucket_info(0).end_bucket_elapsed_nanos()); - - countMetrics.clear_data(); - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(1).count_metrics(), &countMetrics); - EXPECT_EQ(5, countMetrics.data_size()); + countMetrics.clear_data(); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(1).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); data = countMetrics.data(0); - EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(2222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(1); - EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(3333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(2); - EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(4444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); // Partial bucket as metric is deactivated. EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -1810,12 +1798,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(3); - EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(6666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); @@ -1823,12 +1811,12 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { data.bucket_info(0).end_bucket_elapsed_nanos()); data = countMetrics.data(4); - EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* uid field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_EQ(7777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); - EXPECT_EQ(1, data.bucket_info_size()); + ASSERT_EQ(1, data.bucket_info_size()); EXPECT_EQ(1, data.bucket_info(0).count()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp index 78fb391978b3..5e77ee0f0b0a 100644 --- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp @@ -48,12 +48,12 @@ StatsdConfig CreateStatsdConfig() { auto isSyncingPredicate = CreateIsSyncingPredicate(); auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions(); *syncDimension = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + util::SYNC_STATE_CHANGED, {Position::FIRST}); syncDimension->add_child()->set_field(2 /* name field*/); auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = - CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); *config.add_predicate() = screenIsOffPredicate; *config.add_predicate() = isSyncingPredicate; @@ -72,26 +72,26 @@ StatsdConfig CreateStatsdConfig() { countMetric->set_condition(combinationPredicate->id()); // The metric is dimensioning by uid only. *countMetric->mutable_dimensions_in_what() = - CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); + CreateDimensions(util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); countMetric->set_bucket(FIVE_MINUTES); // Links between crash atom and condition of app is in syncing. auto links = countMetric->add_links(); links->set_condition(isSyncingPredicate.id()); auto dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. *links->mutable_fields_in_condition() = CreateAttributionUidDimensions( - android::util::SYNC_STATE_CHANGED, {Position::FIRST}); + util::SYNC_STATE_CHANGED, {Position::FIRST}); // Links between crash atom and condition of app is in background. links = countMetric->add_links(); links->set_condition(isInBackgroundPredicate.id()); dimensionWhat = links->mutable_fields_in_what(); - dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + dimensionWhat->set_field(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); dimensionWhat->add_child()->set_field(1); // uid field. auto dimensionCondition = links->mutable_fields_in_condition(); - dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->set_field(util::ACTIVITY_FOREGROUND_STATE_CHANGED); dimensionCondition->add_child()->set_field(1); // uid field. return config; } @@ -103,56 +103,54 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1) { auto config = CreateStatsdConfig(); uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; ConfigKey cfgKey; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); int appUid = 123; - auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1); - auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201); - auto crashEvent3= CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101); - - auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51); - auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299); - auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001); - - auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16); - auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249); - - auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351); - auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2); - - auto screenTurnedOnEvent = - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + 2); - auto screenTurnedOffEvent = - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - bucketStartTimeNs + 200); + auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid); + auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid); + auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid); + + auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid); + auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid); + auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid); + + auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid); + auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid); + + auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid); + auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid); + + auto screenTurnedOnEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + auto screenTurnedOffEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + 2 * bucketSizeNs - 100); - - std::vector<AttributionNodeInternal> attributions = { - CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")}; - auto syncOnEvent1 = - CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50); - auto syncOffEvent1 = - CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); - auto syncOnEvent2 = - CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); - - auto moveToBackgroundEvent1 = - CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15); + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + std::vector<int> attributionUids = {appUid, appUid + 1}; + std::vector<string> attributionTags = {"App1", "GMSCoreModule1"}; + + auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids, + attributionTags, "ReadEmail"); + auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids, + attributionTags, "ReadEmail"); + auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000, + attributionUids, attributionTags, "ReadDoc"); + + auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid); auto moveToForegroundEvent1 = - CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250); + CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid); auto moveToBackgroundEvent2 = - CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350); + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid); auto moveToForegroundEvent2 = - CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1); + CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid); /* bucket #1 bucket #2 @@ -199,22 +197,22 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1) { } ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 1); EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); auto data = reports.reports(0).metrics(0).count_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); // Uid field. EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid); @@ -227,50 +225,51 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2) { TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); int appUid = 123; - auto crashEvent1 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 1); - auto crashEvent2 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 201); - auto crashEvent3 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 101); + auto crashEvent1 = CreateAppCrashEvent(bucketStartTimeNs + 1, appUid); + auto crashEvent2 = CreateAppCrashEvent(bucketStartTimeNs + 201, appUid); + auto crashEvent3 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 101, appUid); - auto crashEvent4 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 51); - auto crashEvent5 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 299); - auto crashEvent6 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 2001); + auto crashEvent4 = CreateAppCrashEvent(bucketStartTimeNs + 51, appUid); + auto crashEvent5 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 299, appUid); + auto crashEvent6 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 2001, appUid); - auto crashEvent7 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 16); - auto crashEvent8 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 249); + auto crashEvent7 = CreateAppCrashEvent(bucketStartTimeNs + 16, appUid); + auto crashEvent8 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 249, appUid); - auto crashEvent9 = CreateAppCrashEvent(appUid, bucketStartTimeNs + bucketSizeNs + 351); - auto crashEvent10 = CreateAppCrashEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 2); + auto crashEvent9 = CreateAppCrashEvent(bucketStartTimeNs + bucketSizeNs + 351, appUid); + auto crashEvent10 = CreateAppCrashEvent(bucketStartTimeNs + 2 * bucketSizeNs - 2, appUid); auto screenTurnedOnEvent = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 2); + bucketStartTimeNs + 2, android::view::DisplayStateEnum::DISPLAY_STATE_ON); auto screenTurnedOffEvent = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 200); + bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + 2 * bucketSizeNs - 100); - - std::vector<AttributionNodeInternal> attributions = { - CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")}; - auto syncOnEvent1 = CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50); - auto syncOffEvent1 = - CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300); - auto syncOnEvent2 = - CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000); - - auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15); + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs - 100, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + std::vector<int> attributionUids = {appUid, appUid + 1}; + std::vector<string> attributionTags = {"App1", "GMSCoreModule1"}; + + auto syncOnEvent1 = CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids, + attributionTags, "ReadEmail"); + auto syncOffEvent1 = CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids, + attributionTags, "ReadEmail"); + auto syncOnEvent2 = CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs + 2000, + attributionUids, attributionTags, "ReadDoc"); + + auto moveToBackgroundEvent1 = CreateMoveToBackgroundEvent(bucketStartTimeNs + 15, appUid); auto moveToForegroundEvent1 = - CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 250); + CreateMoveToForegroundEvent(bucketStartTimeNs + bucketSizeNs + 250, appUid); auto moveToBackgroundEvent2 = - CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + bucketSizeNs + 350); + CreateMoveToBackgroundEvent(bucketStartTimeNs + bucketSizeNs + 350, appUid); auto moveToForegroundEvent2 = - CreateMoveToForegroundEvent(appUid, bucketStartTimeNs + 2 * bucketSizeNs - 1); + CreateMoveToForegroundEvent(bucketStartTimeNs + 2 * bucketSizeNs - 1, appUid); /* bucket #1 bucket #2 @@ -318,24 +317,23 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2) { ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info_size(), 2); EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1); EXPECT_EQ(reports.reports(0).metrics(0).count_metrics().data(0).bucket_info(1).count(), 3); auto data = reports.reports(0).metrics(0).count_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimensions_in_what().field(), - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); - EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().field(), util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); // Uid field. EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid); diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp index 0bc3ebb81ce6..c03b9254f70d 100644 --- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp @@ -12,45 +12,47 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <android/binder_ibinder.h> +#include <android/binder_interface_utils.h> #include <gtest/gtest.h> -#include <binder/IPCThreadState.h> +#include <vector> + #include "src/StatsLogProcessor.h" #include "src/StatsService.h" #include "src/stats_log_util.h" #include "tests/statsd_test_util.h" -#include <vector> +using::ndk::SharedRefBase; +using std::shared_ptr; namespace android { namespace os { namespace statsd { #ifdef __ANDROID__ - -const string kAndroid = "android"; +namespace { const string kApp1 = "app1.sharing.1"; const int kConfigKey = 789130123; // Randomly chosen to avoid collisions with existing configs. +const int kCallingUid = 0; // Randomly chosen -void SendConfig(StatsService& service, const StatsdConfig& config) { +void SendConfig(shared_ptr<StatsService>& service, const StatsdConfig& config) { string str; config.SerializeToString(&str); std::vector<uint8_t> configAsVec(str.begin(), str.end()); - bool success; - service.addConfiguration(kConfigKey, configAsVec, String16(kAndroid.c_str())); + service->addConfiguration(kConfigKey, configAsVec, kCallingUid); } ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp, bool include_current = false) { vector<uint8_t> output; - IPCThreadState* ipc = IPCThreadState::self(); - ConfigKey configKey(ipc->getCallingUid(), kConfigKey); + ConfigKey configKey(AIBinder_getCallingUid(), kConfigKey); processor->onDumpReport(configKey, timestamp, include_current /* include_current_bucket*/, true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &output); ConfigMetricsReportList reports; reports.ParseFromArray(output.data(), output.size()); EXPECT_EQ(1, reports.reports_size()); - return reports.reports(0); + return reports.reports(kCallingUid); } StatsdConfig MakeConfig() { @@ -69,9 +71,10 @@ StatsdConfig MakeConfig() { StatsdConfig MakeValueMetricConfig(int64_t minTime) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. auto pulledAtomMatcher = - CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE); + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); *config.add_atom_matcher() = pulledAtomMatcher; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); @@ -80,21 +83,23 @@ StatsdConfig MakeValueMetricConfig(int64_t minTime) { valueMetric->set_id(123456); valueMetric->set_what(pulledAtomMatcher.id()); *valueMetric->mutable_value_field() = - CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); *valueMetric->mutable_dimensions_in_what() = - CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); valueMetric->set_bucket(FIVE_MINUTES); valueMetric->set_min_bucket_size_nanos(minTime); valueMetric->set_use_absolute_value_on_reset(true); + valueMetric->set_skip_zero_diff_output(false); return config; } StatsdConfig MakeGaugeMetricConfig(int64_t minTime) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. auto pulledAtomMatcher = - CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE); + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); *config.add_atom_matcher() = pulledAtomMatcher; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); @@ -104,135 +109,192 @@ StatsdConfig MakeGaugeMetricConfig(int64_t minTime) { gaugeMetric->set_what(pulledAtomMatcher.id()); gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true); *gaugeMetric->mutable_dimensions_in_what() = - CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); gaugeMetric->set_bucket(FIVE_MINUTES); gaugeMetric->set_min_bucket_size_nanos(minTime); return config; } +} // anonymous namespace TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get()); - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 2).get()); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 2, 100).get()); - ConfigMetricsReport report = GetReports(service.mProcessor, start + 3); + ConfigMetricsReport report = GetReports(service->mProcessor, start + 3); // Expect no metrics since the bucket has not finished yet. - EXPECT_EQ(1, report.metrics_size()); - EXPECT_EQ(0, report.metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); } TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. // Force the uidmap to update at timestamp 2. - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get()); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); // This is a new installation, so there shouldn't be a split (should be same as the without // split case). - service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); + service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); // Goes into the second bucket. - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get()); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); - ConfigMetricsReport report = GetReports(service.mProcessor, start + 4); - EXPECT_EQ(1, report.metrics_size()); - EXPECT_EQ(0, report.metrics(0).count_metrics().data_size()); + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); } TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. - service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, - {String16("")}); + service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, + {String16("")}); // Force the uidmap to update at timestamp 2. - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get()); - service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); + service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); // Goes into the second bucket. - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get()); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); - ConfigMetricsReport report = GetReports(service.mProcessor, start + 4); + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); - EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0). - has_start_bucket_elapsed_nanos()); - EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0). - has_end_bucket_elapsed_nanos()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_start_bucket_elapsed_nanos()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_end_bucket_elapsed_nanos()); EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); } TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. - service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, - {String16("")}); + service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, + {String16("")}); // Force the uidmap to update at timestamp 2. - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get()); - service.mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); + service->mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1); + // Goes into the second bucket. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); + + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_start_bucket_elapsed_nanos()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_end_bucket_elapsed_nanos()); + EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); +} + +TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) { + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + SendConfig(service, MakeConfig()); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + // Goes into the first bucket + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get()); + int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); // Goes into the second bucket. - service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get()); + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get()); - ConfigMetricsReport report = GetReports(service.mProcessor, start + 4); + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); - EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0). - has_start_bucket_elapsed_nanos()); - EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0). - has_end_bucket_elapsed_nanos()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_start_bucket_elapsed_nanos()); + EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos()); EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); } TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); // Partial buckets don't occur when app is first installed. - service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeValueMetricConfig(0)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. - service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, - String16("v2"), String16("")); + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; + service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); ConfigMetricsReport report = - GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true); - EXPECT_EQ(1, report.metrics_size()); - EXPECT_EQ(0, report.metrics(0).value_metrics().skipped_size()); + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).value_metrics().skipped_size()); + + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size()); + EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)), + report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos()); } TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); // Partial buckets don't occur when app is first installed. - service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. - const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2; - service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), + const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); ConfigMetricsReport report = - GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true); + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); @@ -241,41 +303,86 @@ TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { // Can't test the start time since it will be based on the actual time when the pulling occurs. EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); + + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); +} + +TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) { + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + // Initial pull will fail since puller is not registered. + SendConfig(service, MakeValueMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); + + int64_t bootCompleteTimeNs = start + NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + + // First bucket is dropped due to the initial pull failing + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size()); + EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); + + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); + EXPECT_EQ( + MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); } TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); // Partial buckets don't occur when app is first installed. - service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeGaugeMetricConfig(0)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. - service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); - ConfigMetricsReport report = - GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true); - EXPECT_EQ(1, report.metrics_size()); - EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); } TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { - StatsService service(nullptr, nullptr); + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); // Partial buckets don't occur when app is first installed. - service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2; - service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), - String16("")); + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); ConfigMetricsReport report = - GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true); + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size()); @@ -283,6 +390,38 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos()); EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos()); + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); +} + +TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) { + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + // Initial pull will fail since puller hasn't been registered. + SendConfig(service, MakeGaugeMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); + + int64_t bootCompleteTimeNs = start + NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + // No data in the first bucket, so nothing is reported + ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); + EXPECT_EQ( + MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); } #else diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp index fb878dc7efed..4d3928277527 100644 --- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <android/binder_interface_utils.h> #include <gtest/gtest.h> #include "src/StatsLogProcessor.h" @@ -20,6 +21,8 @@ #include <vector> +using ::ndk::SharedRefBase; + namespace android { namespace os { namespace statsd { @@ -33,8 +36,9 @@ const int64_t metricId = 123456; StatsdConfig CreateStatsdConfig(bool useCondition = true) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. auto pulledAtomMatcher = - CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE); + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); *config.add_atom_matcher() = pulledAtomMatcher; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); @@ -49,9 +53,9 @@ StatsdConfig CreateStatsdConfig(bool useCondition = true) { valueMetric->set_condition(screenIsOffPredicate.id()); } *valueMetric->mutable_value_field() = - CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); *valueMetric->mutable_dimensions_in_what() = - CreateDimensions(android::util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); valueMetric->set_bucket(FIVE_MINUTES); valueMetric->set_use_absolute_value_on_reset(true); valueMetric->set_skip_zero_diff_output(false); @@ -59,45 +63,181 @@ StatsdConfig CreateStatsdConfig(bool useCondition = true) { return config; } +StatsdConfig CreateStatsdConfigWithStates() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. + + auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); + *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); + *config.add_atom_matcher() = CreateBatteryStateNoneMatcher(); + *config.add_atom_matcher() = CreateBatteryStateUsbMatcher(); + + auto screenOnPredicate = CreateScreenIsOnPredicate(); + *config.add_predicate() = screenOnPredicate; + + auto screenOffPredicate = CreateScreenIsOffPredicate(); + *config.add_predicate() = screenOffPredicate; + + auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate(); + *config.add_predicate() = deviceUnpluggedPredicate; + + auto screenOnOnBatteryPredicate = config.add_predicate(); + screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate")); + screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate); + + auto screenOffOnBatteryPredicate = config.add_predicate(); + screenOffOnBatteryPredicate->set_id(StringToId("ScreenOffOnBattery")); + screenOffOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND); + addPredicateToPredicateCombination(screenOffPredicate, screenOffOnBatteryPredicate); + addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOffOnBatteryPredicate); + + const State screenState = + CreateScreenStateWithSimpleOnOffMap(/*screen on id=*/321, /*screen off id=*/123); + *config.add_state() = screenState; + + // ValueMetricSubsystemSleepWhileScreenOnOnBattery + auto valueMetric1 = config.add_value_metric(); + valueMetric1->set_id(metricId); + valueMetric1->set_what(pulledAtomMatcher.id()); + valueMetric1->set_condition(screenOnOnBatteryPredicate->id()); + *valueMetric1->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric1->set_bucket(FIVE_MINUTES); + valueMetric1->set_use_absolute_value_on_reset(true); + valueMetric1->set_skip_zero_diff_output(false); + valueMetric1->set_max_pull_delay_sec(INT_MAX); + + // ValueMetricSubsystemSleepWhileScreenOffOnBattery + ValueMetric* valueMetric2 = config.add_value_metric(); + valueMetric2->set_id(StringToId("ValueMetricSubsystemSleepWhileScreenOffOnBattery")); + valueMetric2->set_what(pulledAtomMatcher.id()); + valueMetric2->set_condition(screenOffOnBatteryPredicate->id()); + *valueMetric2->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric2->set_bucket(FIVE_MINUTES); + valueMetric2->set_use_absolute_value_on_reset(true); + valueMetric2->set_skip_zero_diff_output(false); + valueMetric2->set_max_pull_delay_sec(INT_MAX); + + // ValueMetricSubsystemSleepWhileOnBatterySliceScreen + ValueMetric* valueMetric3 = config.add_value_metric(); + valueMetric3->set_id(StringToId("ValueMetricSubsystemSleepWhileOnBatterySliceScreen")); + valueMetric3->set_what(pulledAtomMatcher.id()); + valueMetric3->set_condition(deviceUnpluggedPredicate.id()); + *valueMetric3->mutable_value_field() = + CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); + valueMetric3->add_slice_by_state(screenState.id()); + valueMetric3->set_bucket(FIVE_MINUTES); + valueMetric3->set_use_absolute_value_on_reset(true); + valueMetric3->set_skip_zero_diff_output(false); + valueMetric3->set_max_pull_delay_sec(INT_MAX); + return config; +} + } // namespace +/** + * Tests the initial condition and condition after the first log events for + * value metrics with either a combination condition or simple condition. + * + * Metrics should be initialized with condition kUnknown (given that the + * predicate is using the default InitialValue of UNKNOWN). The condition should + * be updated to either kFalse or kTrue if a condition event is logged for all + * children conditions. + */ +TEST(ValueMetricE2eTest, TestInitialConditionChanges) { + StatsdConfig config = CreateStatsdConfigWithStates(); + int64_t baseTimeNs = getElapsedRealtimeNs(); + int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + int32_t tagId = util::SUBSYSTEM_SLEEP_STATE; + auto processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), tagId); + + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(3, metricsManager->mAllMetricProducers.size()); + + // Combination condition metric - screen on and device unplugged + sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0]; + // Simple condition metric - device unplugged + sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[2]; + + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOnEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 30, android::view::DISPLAY_STATE_ON); + processor->OnLogEvent(screenOnEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 40, android::view::DISPLAY_STATE_OFF); + processor->OnLogEvent(screenOffEvent.get()); + EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition); + + auto pluggedUsbEvent = CreateBatteryStateChangedEvent( + configAddedTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); + processor->OnLogEvent(pluggedUsbEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition); + + auto pluggedNoneEvent = CreateBatteryStateChangedEvent( + configAddedTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); + processor->OnLogEvent(pluggedNoneEvent.get()); + EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition); + EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition); +} + TEST(ValueMetricE2eTest, TestPulledEvents) { auto config = CreateStatsdConfig(); int64_t baseTimeNs = getElapsedRealtimeNs(); int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), + util::SUBSYSTEM_SLEEP_STATE); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); EXPECT_GT(startBucketNum, (int64_t)0); // When creating the config, the value metric producer should register the alarm at the // end of the current bucket. - EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); EXPECT_EQ(bucketSizeNs, processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); int64_t& expectedPullTimeNs = processor->mPullerManager->mReceivers.begin()->second.front().nextPullTimeNs; EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); - auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 55); + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); - auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + 65); + auto screenOnEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 75); + screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); // Pulling alarm arrives on time and reset the sequential pulling alarm. @@ -106,16 +246,16 @@ TEST(ValueMetricE2eTest, TestPulledEvents) { processor->informPullAlarmFired(expectedPullTimeNs + 1); - screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + 2 * bucketSizeNs + 15); + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 2 * bucketSizeNs + 15, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); processor->informPullAlarmFired(expectedPullTimeNs + 1); processor->informPullAlarmFired(expectedPullTimeNs + 1); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 4 * bucketSizeNs + 11); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 11, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); processor->informPullAlarmFired(expectedPullTimeNs + 1); @@ -131,37 +271,36 @@ TEST(ValueMetricE2eTest, TestPulledEvents) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::ValueMetricDataWrapper valueMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).value_metrics(), &valueMetrics); - EXPECT_GT((int)valueMetrics.data_size(), 1); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); + ASSERT_GT((int)valueMetrics.data_size(), 1); auto data = valueMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); // We have 4 buckets, the first one was incomplete since the condition was unknown. - EXPECT_EQ(4, data.bucket_info_size()); + ASSERT_EQ(4, data.bucket_info_size()); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(0).values_size()); + ASSERT_EQ(1, data.bucket_info(0).values_size()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(1).values_size()); + ASSERT_EQ(1, data.bucket_info(1).values_size()); EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(2).values_size()); + ASSERT_EQ(1, data.bucket_info(2).values_size()); EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(3).values_size()); + ASSERT_EQ(1, data.bucket_info(3).values_size()); } TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { @@ -169,23 +308,24 @@ TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { int64_t baseTimeNs = getElapsedRealtimeNs(); // 10 mins == 2 bucket durations. int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), + util::SUBSYSTEM_SLEEP_STATE); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); EXPECT_GT(startBucketNum, (int64_t)0); // When creating the config, the value metric producer should register the alarm at the // end of the current bucket. - EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); EXPECT_EQ(bucketSizeNs, processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); int64_t& expectedPullTimeNs = @@ -193,16 +333,16 @@ TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); // Screen off/on/off events. - auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 55); + auto screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); - auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + 65); + auto screenOnEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 65, android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 75); + screenOffEvent = + CreateScreenStateChangedEvent(configAddedTimeNs + 75, android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); // Pulling alarm arrives late by 2 buckets and 1 ns. 2 buckets late is too far away in the @@ -211,8 +351,8 @@ TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs); // This screen state change will start a new bucket. - screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, - configAddedTimeNs + 4 * bucketSizeNs + 65); + screenOnEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 4 * bucketSizeNs + 65, + android::view::DISPLAY_STATE_ON); processor->OnLogEvent(screenOnEvent.get()); // The alarm is delayed but we already created a bucket thanks to the screen state condition. @@ -220,8 +360,8 @@ TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21); EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs); - screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF, - configAddedTimeNs + 6 * bucketSizeNs + 31); + screenOffEvent = CreateScreenStateChangedEvent(configAddedTimeNs + 6 * bucketSizeNs + 31, + android::view::DISPLAY_STATE_OFF); processor->OnLogEvent(screenOffEvent.get()); processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21); @@ -239,44 +379,42 @@ TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::ValueMetricDataWrapper valueMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).value_metrics(), &valueMetrics); - EXPECT_GT((int)valueMetrics.data_size(), 1); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); + ASSERT_GT((int)valueMetrics.data_size(), 1); auto data = valueMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); - EXPECT_EQ(3, data.bucket_info_size()); + ASSERT_EQ(3, data.bucket_info_size()); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(0).values_size()); + ASSERT_EQ(1, data.bucket_info(0).values_size()); EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(1).values_size()); + ASSERT_EQ(1, data.bucket_info(1).values_size()); EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 10 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos()); - EXPECT_EQ(1, data.bucket_info(2).values_size()); + ASSERT_EQ(1, data.bucket_info(2).values_size()); } TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) { auto config = CreateStatsdConfig(false); int64_t baseTimeNs = getElapsedRealtimeNs(); int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs; - int64_t bucketSizeNs = - TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; + int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000; auto batterySaverStartMatcher = CreateBatterySaverModeStartAtomMatcher(); *config.add_atom_matcher() = batterySaverStartMatcher; - const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. + const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets. auto metric_activation = config.add_metric_activation(); metric_activation->set_metric_id(metricId); metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY); @@ -285,20 +423,22 @@ TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) { event_activation->set_ttl_seconds(ttlNs / 1000000000); ConfigKey cfgKey; - auto processor = CreateStatsLogProcessor( - baseTimeNs, configAddedTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey, + SharedRefBase::make<FakeSubsystemSleepCallback>(), + util::SUBSYSTEM_SLEEP_STATE); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); processor->mPullerManager->ForceClearPullerCache(); - int startBucketNum = processor->mMetricsManagers.begin()->second-> - mAllMetricProducers[0]->getCurrentBucketNum(); + int startBucketNum = processor->mMetricsManagers.begin() + ->second->mAllMetricProducers[0] + ->getCurrentBucketNum(); EXPECT_GT(startBucketNum, (int64_t)0); EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); // When creating the config, the value metric producer should register the alarm at the // end of the current bucket. - EXPECT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); + ASSERT_EQ((size_t)1, processor->mPullerManager->mReceivers.size()); EXPECT_EQ(bucketSizeNs, processor->mPullerManager->mReceivers.begin()->second.front().intervalNs); int64_t& expectedPullTimeNs = @@ -306,24 +446,24 @@ TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) { EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs); // Pulling alarm arrives on time and reset the sequential pulling alarm. - processor->informPullAlarmFired(expectedPullTimeNs + 1); // 15 mins + 1 ns. + processor->informPullAlarmFired(expectedPullTimeNs + 1); // 15 mins + 1 ns. EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, expectedPullTimeNs); EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); // Activate the metric. A pull occurs here - const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. + const int64_t activationNs = configAddedTimeNs + bucketSizeNs + (2 * 1000 * 1000); // 2 millis. auto batterySaverOnEvent = CreateBatterySaverOnEvent(activationNs); - processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. + processor->OnLogEvent(batterySaverOnEvent.get()); // 15 mins + 2 ms. EXPECT_TRUE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); - processor->informPullAlarmFired(expectedPullTimeNs + 1); // 20 mins + 1 ns. + processor->informPullAlarmFired(expectedPullTimeNs + 1); // 20 mins + 1 ns. EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, expectedPullTimeNs); - processor->informPullAlarmFired(expectedPullTimeNs + 2); // 25 mins + 2 ns. + processor->informPullAlarmFired(expectedPullTimeNs + 2); // 25 mins + 2 ns. EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs); // Create random event to deactivate metric. - auto deactivationEvent = CreateScreenBrightnessChangedEvent(50, activationNs + ttlNs + 1); + auto deactivationEvent = CreateScreenBrightnessChangedEvent(activationNs + ttlNs + 1, 50); processor->OnLogEvent(deactivationEvent.get()); EXPECT_FALSE(processor->mMetricsManagers.begin()->second->mAllMetricProducers[0]->isActive()); @@ -342,31 +482,192 @@ TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) { backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(1, reports.reports_size()); - EXPECT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); StatsLogReport::ValueMetricDataWrapper valueMetrics; - sortMetricDataByDimensionsValue( - reports.reports(0).metrics(0).value_metrics(), &valueMetrics); - EXPECT_GT((int)valueMetrics.data_size(), 0); + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics); + ASSERT_GT((int)valueMetrics.data_size(), 0); auto data = valueMetrics.data(0); - EXPECT_EQ(android::util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); - EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field()); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); EXPECT_EQ(1 /* subsystem name field */, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty()); // We have 2 full buckets, the two surrounding the activation are dropped. - EXPECT_EQ(2, data.bucket_info_size()); + ASSERT_EQ(2, data.bucket_info_size()); auto bucketInfo = data.bucket_info(0); EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_EQ(1, bucketInfo.values_size()); + ASSERT_EQ(1, bucketInfo.values_size()); bucketInfo = data.bucket_info(1); EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, bucketInfo.start_bucket_elapsed_nanos()); EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos()); - EXPECT_EQ(1, bucketInfo.values_size()); + ASSERT_EQ(1, bucketInfo.values_size()); +} + +/** + * Test initialization of a simple value metric that is sliced by a state. + * + * ValueCpuUserTimePerScreenState + */ +TEST(ValueMetricE2eTest, TestInitWithSlicedState) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto pulledAtomMatcher = + CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); + *config.add_atom_matcher() = pulledAtomMatcher; + + auto screenState = CreateScreenState(); + *config.add_state() = screenState; + + // Create value metric that slices by screen state without a map. + int64_t metricId = 123456; + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); + valueMetric->set_what(pulledAtomMatcher.id()); + *valueMetric->mutable_value_field() = + CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); + valueMetric->add_slice_by_state(screenState.id()); + valueMetric->set_max_pull_delay_sec(INT_MAX); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000LL; + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Check that ValueMetricProducer was initialized correctly. + ASSERT_EQ(1U, processor->mMetricsManagers.size()); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(1, metricsManager->mAllMetricProducers.size()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0)); + ASSERT_EQ(0, metricProducer->mStateGroupMap.size()); +} + +/** + * Test initialization of a value metric that is sliced by state and has + * dimensions_in_what. + * + * ValueCpuUserTimePerUidPerUidProcessState + */ +TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto cpuTimePerUidMatcher = + CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID); + *config.add_atom_matcher() = cpuTimePerUidMatcher; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create value metric that slices by screen state with a complete map. + int64_t metricId = 123456; + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); + valueMetric->set_what(cpuTimePerUidMatcher.id()); + *valueMetric->mutable_value_field() = + CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); + *valueMetric->mutable_dimensions_in_what() = + CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); + valueMetric->add_slice_by_state(uidProcessState.id()); + MetricStateLink* stateLink = valueMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + valueMetric->set_max_pull_delay_sec(INT_MAX); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Check that StateTrackers were initialized correctly. + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Check that ValueMetricProducer was initialized correctly. + ASSERT_EQ(1U, processor->mMetricsManagers.size()); + sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + ASSERT_EQ(1, metricsManager->mAllMetricProducers.size()); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + ASSERT_EQ(1, metricProducer->mSlicedStateAtoms.size()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0)); + ASSERT_EQ(0, metricProducer->mStateGroupMap.size()); +} + +/** + * Test initialization of a value metric that is sliced by state and has + * dimensions_in_what. + * + * ValueCpuUserTimePerUidPerUidProcessState + */ +TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + auto cpuTimePerUidMatcher = + CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID); + *config.add_atom_matcher() = cpuTimePerUidMatcher; + + auto uidProcessState = CreateUidProcessState(); + *config.add_state() = uidProcessState; + + // Create value metric that slices by screen state with a complete map. + int64_t metricId = 123456; + auto valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); + valueMetric->set_what(cpuTimePerUidMatcher.id()); + *valueMetric->mutable_value_field() = + CreateDimensions(util::CPU_TIME_PER_UID, {2 /* user_time_micros */}); + valueMetric->add_slice_by_state(uidProcessState.id()); + MetricStateLink* stateLink = valueMetric->add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(util::CPU_TIME_PER_UID, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + valueMetric->set_max_pull_delay_sec(INT_MAX); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // No StateTrackers are initialized. + EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount()); + + // Config initialization fails. + ASSERT_EQ(0, processor->mMetricsManagers.size()); } #else diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index e13bf1409989..52bc222e5fe2 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -42,7 +42,7 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) auto holdingWakelockPredicate = CreateHoldingWakelockPredicate(); // The predicate is dimensioning by any attribution node and both by uid and tag. FieldMatcher dimensions = CreateAttributionUidAndTagDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST}); + util::WAKELOCK_STATE_CHANGED, {Position::FIRST, Position::LAST}); // Also slice by the wakelock tag dimensions.add_child()->set_field(3); // The wakelock tag is set in field 3 of the wakelock. *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() = dimensions; @@ -56,18 +56,16 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) // The metric is dimensioning by first attribution node and only by uid. *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( - android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); + util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); durationMetric->set_bucket(FIVE_MINUTES); return config; } -std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; +std::vector<int> attributionUids1 = {111, 222, 222}; +std::vector<string> attributionTags1 = {"App1", "GMSCoreModule1", "GMSCoreModule2"}; -std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App2"), - CreateAttribution(222, "GMSCoreModule1"), - CreateAttribution(222, "GMSCoreModule2")}; +std::vector<int> attributionUids2 = {111, 222, 222}; +std::vector<string> attributionTags2 = {"App2", "GMSCoreModule1", "GMSCoreModule2"}; /* Events: @@ -81,20 +79,21 @@ void FeedEvents(StatsdConfig config, sp<StatsLogProcessor> processor) { TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto screenTurnedOnEvent = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_ON, bucketStartTimeNs + 1); + bucketStartTimeNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON); auto screenTurnedOffEvent = CreateScreenStateChangedEvent( - android::view::DisplayStateEnum::DISPLAY_STATE_OFF, bucketStartTimeNs + 200); + bucketStartTimeNs + 200, android::view::DisplayStateEnum::DISPLAY_STATE_OFF); auto screenTurnedOnEvent2 = - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON, - bucketStartTimeNs + bucketSizeNs + 500); - - auto acquireEvent1 = CreateAcquireWakelockEvent(attributions1, "wl1", bucketStartTimeNs + 2); - auto releaseEvent1 = - CreateReleaseWakelockEvent(attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 2); - auto acquireEvent2 = - CreateAcquireWakelockEvent(attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 10); - auto releaseEvent2 = CreateReleaseWakelockEvent(attributions2, "wl2", - bucketStartTimeNs + 2 * bucketSizeNs - 15); + CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 500, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + + auto acquireEvent1 = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1, + attributionTags1, "wl1"); + auto releaseEvent1 = CreateReleaseWakelockEvent(bucketStartTimeNs + bucketSizeNs + 2, + attributionUids1, attributionTags1, "wl1"); + auto acquireEvent2 = CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs - 10, + attributionUids2, attributionTags2, "wl2"); + auto releaseEvent2 = CreateReleaseWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs - 15, + attributionUids2, attributionTags2, "wl2"); std::vector<std::unique_ptr<LogEvent>> events; @@ -122,30 +121,30 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1) uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); FeedEvents(config, processor); vector<uint8_t> buffer; ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); // Only 1 dimension output. The tag dimension in the predicate has been aggregated. - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); auto data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, 111); + util::WAKELOCK_STATE_CHANGED, 111); // Validate bucket info. - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); data = reports.reports(0).metrics(0).duration_metrics().data(0); // The wakelock holding interval starts from the screen off event and to the end of the 1st // bucket. @@ -159,27 +158,27 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2) uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); FeedEvents(config, processor); vector<uint8_t> buffer; ConfigMetricsReportList reports; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); // Dump the report after the end of 2nd bucket. - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); auto data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, 111); + util::WAKELOCK_STATE_CHANGED, 111); // Two output buckets. // The wakelock holding interval in the 1st bucket starts from the screen off event and to // the end of the 1st bucket. @@ -189,6 +188,7 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2) // ends at the second screen on event. EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); } + TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3) { ConfigKey cfgKey; auto config = CreateStatsdConfig(DurationMetric::SUM); @@ -196,7 +196,7 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3) uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); FeedEvents(config, processor); vector<uint8_t> buffer; @@ -204,31 +204,31 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3) std::vector<std::unique_ptr<LogEvent>> events; events.push_back( - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - bucketStartTimeNs + 2 * bucketSizeNs + 90)); - events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3", - bucketStartTimeNs + 2 * bucketSizeNs + 100)); - events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3", - bucketStartTimeNs + 5 * bucketSizeNs + 100)); + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); sortLogEventsByTimestamp(&events); for (const auto& event : events) { processor->OnLogEvent(event.get()); } - processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); auto data = reports.reports(0).metrics(0).duration_metrics().data(0); ValidateAttributionUidDimension(data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, 111); + util::WAKELOCK_STATE_CHANGED, 111); // The last wakelock holding spans 4 buckets. EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); @@ -243,13 +243,13 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1) uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); FeedEvents(config, processor); ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs - 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); @@ -257,13 +257,13 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1) backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports_size(), 1); // When using ProtoOutputStream, if nothing written to a sub msg, it won't be treated as // one. It was previsouly 1 because we had a fake onDumpReport which calls add_metric() by // itself. - EXPECT_EQ(1, reports.reports(0).metrics_size()); - EXPECT_EQ(0, reports.reports(0).metrics(0).duration_metrics().data_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + ASSERT_EQ(0, reports.reports(0).metrics(0).duration_metrics().data_size()); } TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2) { @@ -273,27 +273,27 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2) uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); FeedEvents(config, processor); ConfigMetricsReportList reports; vector<uint8_t> buffer; - processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); // Dump the report after the end of 2nd bucket. One dimension with one bucket. - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); auto data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. ValidateAttributionUidDimension(data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, 111); + util::WAKELOCK_STATE_CHANGED, 111); // The max is acquire event for wl1 to screen off start. EXPECT_EQ((unsigned long long)data.bucket_info(0).duration_nanos(), bucketSizeNs + 2 - 200); } @@ -305,7 +305,7 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3) uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL; auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); - EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + ASSERT_EQ(processor->mMetricsManagers.size(), 1u); EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); FeedEvents(config, processor); ConfigMetricsReportList reports; @@ -313,31 +313,31 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3) std::vector<std::unique_ptr<LogEvent>> events; events.push_back( - CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, - bucketStartTimeNs + 2 * bucketSizeNs + 90)); - events.push_back(CreateAcquireWakelockEvent(attributions1, "wl3", - bucketStartTimeNs + 2 * bucketSizeNs + 100)); - events.push_back(CreateReleaseWakelockEvent(attributions1, "wl3", - bucketStartTimeNs + 5 * bucketSizeNs + 100)); + CreateScreenStateChangedEvent(bucketStartTimeNs + 2 * bucketSizeNs + 90, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); + events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 2 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); + events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 5 * bucketSizeNs + 100, + attributionUids1, attributionTags1, "wl3")); sortLogEventsByTimestamp(&events); for (const auto& event : events) { processor->OnLogEvent(event.get()); } - processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, - ADB_DUMP, FAST, &buffer); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); EXPECT_TRUE(buffer.size() > 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); - EXPECT_EQ(reports.reports_size(), 1); - EXPECT_EQ(reports.reports(0).metrics_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); - EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); + ASSERT_EQ(reports.reports_size(), 1); + ASSERT_EQ(reports.reports(0).metrics_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + ASSERT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); auto data = reports.reports(0).metrics(0).duration_metrics().data(0); ValidateAttributionUidDimension(data.dimensions_in_what(), - android::util::WAKELOCK_STATE_CHANGED, 111); + util::WAKELOCK_STATE_CHANGED, 111); // The last wakelock holding spans 4 buckets. EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 3 * bucketSizeNs); EXPECT_EQ((unsigned long long)data.bucket_info(1).start_bucket_elapsed_nanos(), diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp deleted file mode 100644 index 32409184b02f..000000000000 --- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2019 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. - */ - -#undef LOG_TAG -#define LOG_TAG "GpuStatsPuller_test" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> - -#include <graphicsenv/GpuStatsInfo.h> -#include <log/log.h> - -#include "src/external/GpuStatsPuller.h" -#include "statslog.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -// clang-format off -static const std::string DRIVER_PACKAGE_NAME = "TEST_DRIVER"; -static const std::string DRIVER_VERSION_NAME = "TEST_DRIVER_VERSION"; -static const std::string APP_PACKAGE_NAME = "TEST_APP"; -static const int64_t TIMESTAMP_WALLCLOCK = 111; -static const int64_t TIMESTAMP_ELAPSED = 222; -static const int64_t DRIVER_VERSION_CODE = 333; -static const int64_t DRIVER_BUILD_TIME = 444; -static const int64_t GL_LOADING_COUNT = 3; -static const int64_t GL_LOADING_FAILURE_COUNT = 1; -static const int64_t VK_LOADING_COUNT = 4; -static const int64_t VK_LOADING_FAILURE_COUNT = 0; -static const int64_t ANGLE_LOADING_COUNT = 2; -static const int64_t ANGLE_LOADING_FAILURE_COUNT = 1; -static const int64_t GL_DRIVER_LOADING_TIME_0 = 555; -static const int64_t GL_DRIVER_LOADING_TIME_1 = 666; -static const int64_t VK_DRIVER_LOADING_TIME_0 = 777; -static const int64_t VK_DRIVER_LOADING_TIME_1 = 888; -static const int64_t VK_DRIVER_LOADING_TIME_2 = 999; -static const int64_t ANGLE_DRIVER_LOADING_TIME_0 = 1010; -static const int64_t ANGLE_DRIVER_LOADING_TIME_1 = 1111; -static const int32_t VULKAN_VERSION = 1; -static const int32_t CPU_VULKAN_VERSION = 2; -static const int32_t GLES_VERSION = 3; -static const bool CPU_VULKAN_IN_USE = true; -static const size_t NUMBER_OF_VALUES_GLOBAL = 13; -static const size_t NUMBER_OF_VALUES_APP = 6; -// clang-format on - -class MockGpuStatsPuller : public GpuStatsPuller { -public: - MockGpuStatsPuller(const int tagId, vector<std::shared_ptr<LogEvent>>* data) - : GpuStatsPuller(tagId), mData(data){}; - -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override { - *data = *mData; - return true; - } - - vector<std::shared_ptr<LogEvent>>* mData; -}; - -class GpuStatsPuller_test : public ::testing::Test { -public: - GpuStatsPuller_test() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - } - - ~GpuStatsPuller_test() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); - } -}; - -TEST_F(GpuStatsPuller_test, PullGpuStatsGlobalInfo) { - vector<std::shared_ptr<LogEvent>> inData, outData; - std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_GLOBAL_INFO, - TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED); - EXPECT_TRUE(event->write(DRIVER_PACKAGE_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_CODE)); - EXPECT_TRUE(event->write(DRIVER_BUILD_TIME)); - EXPECT_TRUE(event->write(GL_LOADING_COUNT)); - EXPECT_TRUE(event->write(GL_LOADING_FAILURE_COUNT)); - EXPECT_TRUE(event->write(VK_LOADING_COUNT)); - EXPECT_TRUE(event->write(VK_LOADING_FAILURE_COUNT)); - EXPECT_TRUE(event->write(VULKAN_VERSION)); - EXPECT_TRUE(event->write(CPU_VULKAN_VERSION)); - EXPECT_TRUE(event->write(GLES_VERSION)); - EXPECT_TRUE(event->write(ANGLE_LOADING_COUNT)); - EXPECT_TRUE(event->write(ANGLE_LOADING_FAILURE_COUNT)); - event->init(); - inData.emplace_back(event); - MockGpuStatsPuller mockPuller(android::util::GPU_STATS_GLOBAL_INFO, &inData); - mockPuller.ForceClearCache(); - mockPuller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::GPU_STATS_GLOBAL_INFO, outData[0]->GetTagId()); - ASSERT_EQ(NUMBER_OF_VALUES_GLOBAL, outData[0]->size()); - EXPECT_EQ(DRIVER_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_NAME, outData[0]->getValues()[1].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[2].mValue.long_value); - EXPECT_EQ(DRIVER_BUILD_TIME, outData[0]->getValues()[3].mValue.long_value); - EXPECT_EQ(GL_LOADING_COUNT, outData[0]->getValues()[4].mValue.long_value); - EXPECT_EQ(GL_LOADING_FAILURE_COUNT, outData[0]->getValues()[5].mValue.long_value); - EXPECT_EQ(VK_LOADING_COUNT, outData[0]->getValues()[6].mValue.long_value); - EXPECT_EQ(VK_LOADING_FAILURE_COUNT, outData[0]->getValues()[7].mValue.long_value); - EXPECT_EQ(VULKAN_VERSION, outData[0]->getValues()[8].mValue.int_value); - EXPECT_EQ(CPU_VULKAN_VERSION, outData[0]->getValues()[9].mValue.int_value); - EXPECT_EQ(GLES_VERSION, outData[0]->getValues()[10].mValue.int_value); - EXPECT_EQ(ANGLE_LOADING_COUNT, outData[0]->getValues()[11].mValue.long_value); - EXPECT_EQ(ANGLE_LOADING_FAILURE_COUNT, outData[0]->getValues()[12].mValue.long_value); -} - -TEST_F(GpuStatsPuller_test, PullGpuStatsAppInfo) { - vector<std::shared_ptr<LogEvent>> inData, outData; - std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_APP_INFO, - TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED); - EXPECT_TRUE(event->write(APP_PACKAGE_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_CODE)); - std::vector<int64_t> glDriverLoadingTime; - glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_0); - glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_1); - std::vector<int64_t> vkDriverLoadingTime; - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_0); - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_1); - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_2); - std::vector<int64_t> angleDriverLoadingTime; - angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_0); - angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_1); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(glDriverLoadingTime))); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(vkDriverLoadingTime))); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(angleDriverLoadingTime))); - EXPECT_TRUE(event->write(CPU_VULKAN_IN_USE)); - event->init(); - inData.emplace_back(event); - MockGpuStatsPuller mockPuller(android::util::GPU_STATS_APP_INFO, &inData); - mockPuller.ForceClearCache(); - mockPuller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::GPU_STATS_APP_INFO, outData[0]->GetTagId()); - ASSERT_EQ(NUMBER_OF_VALUES_APP, outData[0]->size()); - EXPECT_EQ(APP_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[1].mValue.long_value); - EXPECT_EQ(int64VectorToProtoByteString(glDriverLoadingTime), - outData[0]->getValues()[2].mValue.str_value); - EXPECT_EQ(int64VectorToProtoByteString(vkDriverLoadingTime), - outData[0]->getValues()[3].mValue.str_value); - EXPECT_EQ(int64VectorToProtoByteString(angleDriverLoadingTime), - outData[0]->getValues()[4].mValue.str_value); - EXPECT_EQ(CPU_VULKAN_IN_USE, outData[0]->getValues()[5].mValue.int_value); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp deleted file mode 100644 index 38bc19452afa..000000000000 --- a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <android/os/IncidentReportArgs.h> - -#include <gtest/gtest.h> - -namespace android { -namespace os { -namespace statsd { - -TEST(IncidentReportArgsTest, testSerialization) { - IncidentReportArgs args; - args.setAll(0); - args.addSection(1000); - args.addSection(1001); - - vector<uint8_t> header1; - header1.push_back(0x1); - header1.push_back(0x2); - vector<uint8_t> header2; - header1.push_back(0x22); - header1.push_back(0x33); - - args.addHeader(header1); - args.addHeader(header2); - - args.setPrivacyPolicy(1); - - args.setReceiverPkg("com.android.os"); - args.setReceiverCls("com.android.os.Receiver"); - - Parcel out; - status_t err = args.writeToParcel(&out); - EXPECT_EQ(NO_ERROR, err); - - out.setDataPosition(0); - - IncidentReportArgs args2; - err = args2.readFromParcel(&out); - EXPECT_EQ(NO_ERROR, err); - - EXPECT_EQ(0, args2.all()); - set<int> sections; - sections.insert(1000); - sections.insert(1001); - EXPECT_EQ(sections, args2.sections()); - EXPECT_EQ(1, args2.getPrivacyPolicy()); - - EXPECT_EQ(string("com.android.os"), args2.receiverPkg()); - EXPECT_EQ(string("com.android.os.Receiver"), args2.receiverCls()); - - vector<vector<uint8_t>> headers; - headers.push_back(header1); - headers.push_back(header2); - EXPECT_EQ(headers, args2.headers()); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp new file mode 100644 index 000000000000..85a60886349e --- /dev/null +++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp @@ -0,0 +1,219 @@ +// Copyright (C) 2019 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 "src/external/StatsCallbackPuller.h" + +#include <aidl/android/os/BnPullAtomCallback.h> +#include <aidl/android/os/IPullAtomResultReceiver.h> +#include <aidl/android/util/StatsEventParcel.h> +#include <android/binder_interface_utils.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <stdio.h> + +#include <chrono> +#include <thread> +#include <vector> + +#include "../metrics/metrics_test_helper.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +using namespace testing; +using Status = ::ndk::ScopedAStatus; +using aidl::android::os::BnPullAtomCallback; +using aidl::android::os::IPullAtomResultReceiver; +using aidl::android::util::StatsEventParcel; +using ::ndk::SharedRefBase; +using std::make_shared; +using std::shared_ptr; +using std::vector; +using std::this_thread::sleep_for; +using testing::Contains; + +namespace { +int pullTagId = -12; +bool pullSuccess; +vector<int64_t> values; +int64_t pullDelayNs; +int64_t pullTimeoutNs; +int64_t pullCoolDownNs; +std::thread pullThread; + +AStatsEvent* createSimpleEvent(int64_t value) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, pullTagId); + AStatsEvent_writeInt64(event, value); + AStatsEvent_build(event); + return event; +} + +void executePull(const shared_ptr<IPullAtomResultReceiver>& resultReceiver) { + // Convert stats_events into StatsEventParcels. + vector<StatsEventParcel> parcels; + for (int i = 0; i < values.size(); i++) { + AStatsEvent* event = createSimpleEvent(values[i]); + size_t size; + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); + + StatsEventParcel p; + // vector.assign() creates a copy, but this is inevitable unless + // stats_event.h/c uses a vector as opposed to a buffer. + p.buffer.assign(buffer, buffer + size); + parcels.push_back(std::move(p)); + AStatsEvent_release(event); + } + + sleep_for(std::chrono::nanoseconds(pullDelayNs)); + resultReceiver->pullFinished(pullTagId, pullSuccess, parcels); +} + +class FakePullAtomCallback : public BnPullAtomCallback { +public: + Status onPullAtom(int atomTag, + const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override { + // Force pull to happen in separate thread to simulate binder. + pullThread = std::thread(executePull, resultReceiver); + return Status::ok(); + } +}; + +class StatsCallbackPullerTest : public ::testing::Test { +public: + StatsCallbackPullerTest() { + } + + void SetUp() override { + pullSuccess = false; + pullDelayNs = 0; + values.clear(); + pullTimeoutNs = 10000000000LL; // 10 seconds. + pullCoolDownNs = 1000000000; // 1 second. + } + + void TearDown() override { + if (pullThread.joinable()) { + pullThread.join(); + } + values.clear(); + } +}; +} // Anonymous namespace. + +TEST_F(StatsCallbackPullerTest, PullSuccess) { + shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>(); + int64_t value = 43; + pullSuccess = true; + values.push_back(value); + + StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); + + vector<std::shared_ptr<LogEvent>> dataHolder; + int64_t startTimeNs = getElapsedRealtimeNs(); + EXPECT_TRUE(puller.PullInternal(&dataHolder)); + int64_t endTimeNs = getElapsedRealtimeNs(); + + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_LT(startTimeNs, dataHolder[0]->GetElapsedTimestampNs()); + EXPECT_GT(endTimeNs, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(value, dataHolder[0]->getValues()[0].mValue.int_value); +} + +TEST_F(StatsCallbackPullerTest, PullFail) { + shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>(); + pullSuccess = false; + int64_t value = 1234; + values.push_back(value); + + StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); + + vector<shared_ptr<LogEvent>> dataHolder; + EXPECT_FALSE(puller.PullInternal(&dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsCallbackPullerTest, PullTimeout) { + shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>(); + pullSuccess = true; + pullDelayNs = MillisToNano(5); // 5ms. + pullTimeoutNs = 10000; // 10 microseconds. + int64_t value = 4321; + values.push_back(value); + + StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {}); + + vector<shared_ptr<LogEvent>> dataHolder; + int64_t startTimeNs = getElapsedRealtimeNs(); + // Returns true to let StatsPuller code evaluate the timeout. + EXPECT_TRUE(puller.PullInternal(&dataHolder)); + int64_t endTimeNs = getElapsedRealtimeNs(); + int64_t actualPullDurationNs = endTimeNs - startTimeNs; + + // Pull should take at least the timeout amount of time, but should stop early because the delay + // is bigger. + EXPECT_LT(pullTimeoutNs, actualPullDurationNs); + EXPECT_GT(pullDelayNs, actualPullDurationNs); + ASSERT_EQ(0, dataHolder.size()); + + // Let the pull return and make sure that the dataHolder is not modified. + pullThread.join(); + ASSERT_EQ(0, dataHolder.size()); +} + +// Register a puller and ensure that the timeout logic works. +TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) { + shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>(); + pullSuccess = true; + pullDelayNs = MillisToNano(5); // 5 ms. + pullTimeoutNs = 10000; // 10 microsseconds. + int64_t value = 4321; + int32_t uid = 123; + values.push_back(value); + + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs, + vector<int32_t>(), cb); + vector<shared_ptr<LogEvent>> dataHolder; + int64_t startTimeNs = getElapsedRealtimeNs(); + // Returns false, since StatsPuller code will evaluate the timeout. + EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, startTimeNs, &dataHolder)); + int64_t endTimeNs = getElapsedRealtimeNs(); + int64_t actualPullDurationNs = endTimeNs - startTimeNs; + + // Pull should take at least the timeout amount of time, but should stop early because the delay + // is bigger. Make sure that the time is closer to the timeout, than to the intended delay. + EXPECT_LT(pullTimeoutNs, actualPullDurationNs); + EXPECT_GT(pullDelayNs / 5, actualPullDurationNs); + ASSERT_EQ(0, dataHolder.size()); + + // Let the pull return and make sure that the dataHolder is not modified. + pullThread.join(); + ASSERT_EQ(0, dataHolder.size()); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/external/StatsPullerManager_test.cpp b/cmds/statsd/tests/external/StatsPullerManager_test.cpp new file mode 100644 index 000000000000..c76e85ec75e6 --- /dev/null +++ b/cmds/statsd/tests/external/StatsPullerManager_test.cpp @@ -0,0 +1,150 @@ +// Copyright (C) 2020 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 "src/external/StatsPullerManager.h" + +#include <aidl/android/os/IPullAtomResultReceiver.h> +#include <aidl/android/util/StatsEventParcel.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +using aidl::android::util::StatsEventParcel; +using ::ndk::SharedRefBase; +using std::make_shared; +using std::shared_ptr; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +namespace { + +int pullTagId1 = 10101; +int pullTagId2 = 10102; +int uid1 = 9999; +int uid2 = 8888; +ConfigKey configKey(50, 12345); +ConfigKey badConfigKey(60, 54321); +int unregisteredUid = 98765; +int64_t coolDownNs = NS_PER_SEC; +int64_t timeoutNs = NS_PER_SEC / 2; + +AStatsEvent* createSimpleEvent(int32_t atomId, int32_t value) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomId); + AStatsEvent_writeInt32(event, value); + AStatsEvent_build(event); + return event; +} + +class FakePullAtomCallback : public BnPullAtomCallback { +public: + FakePullAtomCallback(int32_t uid) : mUid(uid){}; + Status onPullAtom(int atomTag, + const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override { + vector<StatsEventParcel> parcels; + AStatsEvent* event = createSimpleEvent(atomTag, mUid); + size_t size; + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); + + StatsEventParcel p; + // vector.assign() creates a copy, but this is inevitable unless + // stats_event.h/c uses a vector as opposed to a buffer. + p.buffer.assign(buffer, buffer + size); + parcels.push_back(std::move(p)); + AStatsEvent_release(event); + resultReceiver->pullFinished(atomTag, /*success*/ true, parcels); + return Status::ok(); + } + int32_t mUid; +}; + +class FakePullUidProvider : public PullUidProvider { +public: + vector<int32_t> getPullAtomUids(int atomId) override { + if (atomId == pullTagId1) { + return {uid2, uid1}; + } else if (atomId == pullTagId2) { + return {uid2}; + } + return {}; + } +}; + +sp<StatsPullerManager> createPullerManagerAndRegister() { + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + shared_ptr<FakePullAtomCallback> cb1 = SharedRefBase::make<FakePullAtomCallback>(uid1); + pullerManager->RegisterPullAtomCallback(uid1, pullTagId1, coolDownNs, timeoutNs, {}, cb1, true); + shared_ptr<FakePullAtomCallback> cb2 = SharedRefBase::make<FakePullAtomCallback>(uid2); + pullerManager->RegisterPullAtomCallback(uid2, pullTagId1, coolDownNs, timeoutNs, {}, cb2, true); + pullerManager->RegisterPullAtomCallback(uid1, pullTagId2, coolDownNs, timeoutNs, {}, cb1, true); + return pullerManager; +} +} // anonymous namespace + +TEST(StatsPullerManagerTest, TestPullInvalidUid) { + sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister(); + + vector<shared_ptr<LogEvent>> data; + EXPECT_FALSE(pullerManager->Pull(pullTagId1, {unregisteredUid}, /*timestamp =*/1, &data, true)); +} + +TEST(StatsPullerManagerTest, TestPullChoosesCorrectUid) { + sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister(); + + vector<shared_ptr<LogEvent>> data; + EXPECT_TRUE(pullerManager->Pull(pullTagId1, {uid1}, /*timestamp =*/1, &data, true)); + ASSERT_EQ(data.size(), 1); + EXPECT_EQ(data[0]->GetTagId(), pullTagId1); + ASSERT_EQ(data[0]->getValues().size(), 1); + EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid1); +} + +TEST(StatsPullerManagerTest, TestPullInvalidConfigKey) { + sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister(); + sp<FakePullUidProvider> uidProvider = new FakePullUidProvider(); + pullerManager->RegisterPullUidProvider(configKey, uidProvider); + + vector<shared_ptr<LogEvent>> data; + EXPECT_FALSE(pullerManager->Pull(pullTagId1, badConfigKey, /*timestamp =*/1, &data, true)); +} + +TEST(StatsPullerManagerTest, TestPullConfigKeyGood) { + sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister(); + sp<FakePullUidProvider> uidProvider = new FakePullUidProvider(); + pullerManager->RegisterPullUidProvider(configKey, uidProvider); + + vector<shared_ptr<LogEvent>> data; + EXPECT_TRUE(pullerManager->Pull(pullTagId1, configKey, /*timestamp =*/1, &data, true)); + EXPECT_EQ(data[0]->GetTagId(), pullTagId1); + ASSERT_EQ(data[0]->getValues().size(), 1); + EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid2); +} + +TEST(StatsPullerManagerTest, TestPullConfigKeyNoPullerWithUid) { + sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister(); + sp<FakePullUidProvider> uidProvider = new FakePullUidProvider(); + pullerManager->RegisterPullUidProvider(configKey, uidProvider); + + vector<shared_ptr<LogEvent>> data; + EXPECT_FALSE(pullerManager->Pull(pullTagId2, configKey, /*timestamp =*/1, &data, true)); +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/external/StatsPuller_test.cpp b/cmds/statsd/tests/external/StatsPuller_test.cpp index 76e2097a90b8..55a90365e151 100644 --- a/cmds/statsd/tests/external/StatsPuller_test.cpp +++ b/cmds/statsd/tests/external/StatsPuller_test.cpp @@ -15,11 +15,14 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> #include <stdio.h> + #include <chrono> #include <thread> #include <vector> + #include "../metrics/metrics_test_helper.h" #include "src/stats_log_util.h" +#include "stats_event.h" #include "tests/statsd_test_util.h" #ifdef __ANDROID__ @@ -35,7 +38,7 @@ using std::vector; using std::this_thread::sleep_for; using testing::Contains; -// cooldown time 1sec. +namespace { int pullTagId = 10014; bool pullSuccess; @@ -44,7 +47,8 @@ long pullDelayNs; class FakePuller : public StatsPuller { public: - FakePuller() : StatsPuller(pullTagId){}; + FakePuller() + : StatsPuller(pullTagId, /*coolDownNs=*/MillisToNano(10), /*timeoutNs=*/MillisToNano(5)){}; private: bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override { @@ -56,11 +60,15 @@ private: FakePuller puller; -shared_ptr<LogEvent> createSimpleEvent(int64_t eventTimeNs, int64_t value) { - shared_ptr<LogEvent> event = make_shared<LogEvent>(pullTagId, eventTimeNs); - event->write(value); - event->init(); - return event; +std::unique_ptr<LogEvent> createSimpleEvent(int64_t eventTimeNs, int64_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, pullTagId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + AStatsEvent_writeInt64(statsEvent, value); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } class StatsPullerTest : public ::testing::Test { @@ -76,31 +84,33 @@ public: } }; -TEST_F(StatsPullerTest, PullSucces) { +} // Anonymous namespace. + +TEST_F(StatsPullerTest, PullSuccess) { pullData.push_back(createSimpleEvent(1111L, 33)); pullSuccess = true; vector<std::shared_ptr<LogEvent>> dataHolder; - EXPECT_TRUE(puller.Pull(&dataHolder)); - EXPECT_EQ(1, dataHolder.size()); + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - EXPECT_EQ(1, dataHolder[0]->size()); + ASSERT_EQ(1, dataHolder[0]->size()); EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); - sleep_for(std::chrono::seconds(1)); + sleep_for(std::chrono::milliseconds(11)); pullData.clear(); pullData.push_back(createSimpleEvent(2222L, 44)); pullSuccess = true; - EXPECT_TRUE(puller.Pull(&dataHolder)); - EXPECT_EQ(1, dataHolder.size()); + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); EXPECT_EQ(2222L, dataHolder[0]->GetElapsedTimestampNs()); - EXPECT_EQ(1, dataHolder[0]->size()); + ASSERT_EQ(1, dataHolder[0]->size()); EXPECT_EQ(44, dataHolder[0]->getValues()[0].mValue.int_value); } @@ -110,47 +120,49 @@ TEST_F(StatsPullerTest, PullFailAfterSuccess) { pullSuccess = true; vector<std::shared_ptr<LogEvent>> dataHolder; - EXPECT_TRUE(puller.Pull(&dataHolder)); - EXPECT_EQ(1, dataHolder.size()); + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - EXPECT_EQ(1, dataHolder[0]->size()); + ASSERT_EQ(1, dataHolder[0]->size()); EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); - sleep_for(std::chrono::seconds(1)); + sleep_for(std::chrono::milliseconds(11)); pullData.clear(); pullData.push_back(createSimpleEvent(2222L, 44)); pullSuccess = false; dataHolder.clear(); - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + // Fails due to hitting the cool down. pullSuccess = true; dataHolder.clear(); - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); } // Test pull takes longer than timeout, 2nd pull happens shorter than cooldown TEST_F(StatsPullerTest, PullTakeTooLongAndPullFast) { pullData.push_back(createSimpleEvent(1111L, 33)); pullSuccess = true; - // timeout is 0.5 - pullDelayNs = (long)(0.8 * NS_PER_SEC); + // timeout is 5ms + pullDelayNs = MillisToNano(6); vector<std::shared_ptr<LogEvent>> dataHolder; - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); pullData.clear(); pullData.push_back(createSimpleEvent(2222L, 44)); + pullDelayNs = 0; pullSuccess = true; dataHolder.clear(); - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); } TEST_F(StatsPullerTest, PullFail) { @@ -159,19 +171,19 @@ TEST_F(StatsPullerTest, PullFail) { pullSuccess = false; vector<std::shared_ptr<LogEvent>> dataHolder; - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); } TEST_F(StatsPullerTest, PullTakeTooLong) { pullData.push_back(createSimpleEvent(1111L, 33)); pullSuccess = true; - pullDelayNs = NS_PER_SEC; + pullDelayNs = MillisToNano(6); vector<std::shared_ptr<LogEvent>> dataHolder; - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); } TEST_F(StatsPullerTest, PullTooFast) { @@ -180,11 +192,11 @@ TEST_F(StatsPullerTest, PullTooFast) { pullSuccess = true; vector<std::shared_ptr<LogEvent>> dataHolder; - EXPECT_TRUE(puller.Pull(&dataHolder)); - EXPECT_EQ(1, dataHolder.size()); + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - EXPECT_EQ(1, dataHolder[0]->size()); + ASSERT_EQ(1, dataHolder[0]->size()); EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); pullData.clear(); @@ -193,11 +205,11 @@ TEST_F(StatsPullerTest, PullTooFast) { pullSuccess = true; dataHolder.clear(); - EXPECT_TRUE(puller.Pull(&dataHolder)); - EXPECT_EQ(1, dataHolder.size()); + EXPECT_TRUE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); - EXPECT_EQ(1, dataHolder[0]->size()); + ASSERT_EQ(1, dataHolder[0]->size()); EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); } @@ -207,16 +219,93 @@ TEST_F(StatsPullerTest, PullFailsAndTooFast) { pullSuccess = false; vector<std::shared_ptr<LogEvent>> dataHolder; - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + pullSuccess = true; + + EXPECT_FALSE(puller.Pull(getElapsedRealtimeNs(), &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsPullerTest, PullSameEventTime) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = true; + int64_t eventTimeNs = getElapsedRealtimeNs(); + + vector<std::shared_ptr<LogEvent>> dataHolder; + EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + + // Sleep to ensure the cool down expires. + sleep_for(std::chrono::milliseconds(11)); + pullSuccess = true; + + dataHolder.clear(); + EXPECT_TRUE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(1, dataHolder.size()); + EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId()); + EXPECT_EQ(1111L, dataHolder[0]->GetElapsedTimestampNs()); + ASSERT_EQ(1, dataHolder[0]->size()); + EXPECT_EQ(33, dataHolder[0]->getValues()[0].mValue.int_value); +} + +// Test pull takes longer than timeout, 2nd pull happens at same event time +TEST_F(StatsPullerTest, PullTakeTooLongAndPullSameEventTime) { + pullData.push_back(createSimpleEvent(1111L, 33)); + pullSuccess = true; + int64_t eventTimeNs = getElapsedRealtimeNs(); + // timeout is 5ms + pullDelayNs = MillisToNano(6); + + vector<std::shared_ptr<LogEvent>> dataHolder; + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + // Sleep to ensure the cool down expires. 6ms is taken by the delay, so only 5 is needed here. + sleep_for(std::chrono::milliseconds(5)); + + pullData.clear(); + pullData.push_back(createSimpleEvent(2222L, 44)); + pullDelayNs = 0; + + pullSuccess = true; + dataHolder.clear(); + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); +} + +TEST_F(StatsPullerTest, PullFailsAndPullSameEventTime) { + pullData.push_back(createSimpleEvent(1111L, 33)); + + pullSuccess = false; + int64_t eventTimeNs = getElapsedRealtimeNs(); + + vector<std::shared_ptr<LogEvent>> dataHolder; + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); + + // Sleep to ensure the cool down expires. + sleep_for(std::chrono::milliseconds(11)); pullData.clear(); pullData.push_back(createSimpleEvent(2222L, 44)); pullSuccess = true; - EXPECT_FALSE(puller.Pull(&dataHolder)); - EXPECT_EQ(0, dataHolder.size()); + EXPECT_FALSE(puller.Pull(eventTimeNs, &dataHolder)); + ASSERT_EQ(0, dataHolder.size()); } } // namespace statsd diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp index 673082834e18..a21dc8717776 100644 --- a/cmds/statsd/tests/external/puller_util_test.cpp +++ b/cmds/statsd/tests/external/puller_util_test.cpp @@ -13,12 +13,18 @@ // limitations under the License. #include "external/puller_util.h" + #include <gmock/gmock.h> #include <gtest/gtest.h> #include <stdio.h> + #include <vector> -#include "statslog.h" + #include "../metrics/metrics_test_helper.h" +#include "FieldValue.h" +#include "annotations.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" #ifdef __ANDROID__ @@ -27,239 +33,371 @@ namespace os { namespace statsd { using namespace testing; -using std::make_shared; using std::shared_ptr; using std::vector; -using testing::Contains; /* * Test merge isolated and host uid */ +namespace { +const int uidAtomTagId = 100; +const vector<int> additiveFields = {3}; +const int nonUidAtomTagId = 200; +const int timestamp = 1234; +const int isolatedUid1 = 30; +const int isolatedUid2 = 40; +const int isolatedNonAdditiveData = 32; +const int isolatedAdditiveData = 31; +const int hostUid = 20; +const int hostNonAdditiveData = 22; +const int hostAdditiveData = 21; +const int attributionAtomTagId = 300; + +sp<MockUidMap> makeMockUidMap() { + return makeMockUidMapForOneHost(hostUid, {isolatedUid1, isolatedUid2}); +} + +} // anonymous namespace + +TEST(PullerUtilTest, MergeNoDimension) { + vector<shared_ptr<LogEvent>> data = { + // 30->22->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, + isolatedAdditiveData), + + // 20->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, + hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, MergeWithDimension) { + vector<shared_ptr<LogEvent>> data = { + // 30->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 20->32->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, + hostAdditiveData), + + // 20->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, + hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, NoMergeHostUidOnly) { + vector<shared_ptr<LogEvent>> data = { + // 20->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 20->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, + hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); +} -int uidAtomTagId = android::util::CPU_CLUSTER_TIME; -int nonUidAtomTagId = android::util::SYSTEM_UPTIME; -int timestamp = 1234; -int isolatedUid = 30; -int isolatedAdditiveData = 31; -int isolatedNonAdditiveData = 32; -int hostUid = 20; -int hostAdditiveData = 21; -int hostNonAdditiveData = 22; - -void extractIntoVector(vector<shared_ptr<LogEvent>> events, - vector<vector<int>>& ret) { - ret.clear(); - status_t err; - for (const auto& event : events) { - vector<int> vec; - vec.push_back(event->GetInt(1, &err)); - vec.push_back(event->GetInt(2, &err)); - vec.push_back(event->GetInt(3, &err)); - ret.push_back(vec); - } +TEST(PullerUtilTest, IsolatedUidOnly) { + vector<shared_ptr<LogEvent>> data = { + // 30->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 30->22->21 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, + hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + // 20->32->31 + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + + // 20->22->21 + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(2).mValue.int_value); } -TEST(puller_util, MergeNoDimension) { - vector<shared_ptr<LogEvent>> inputData; - shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp); - // 30->22->31 - event->write(isolatedUid); - event->write(hostNonAdditiveData); - event->write(isolatedAdditiveData); - event->init(); - inputData.push_back(event); - - // 20->22->21 - event = make_shared<LogEvent>(uidAtomTagId, timestamp); - event->write(hostUid); - event->write(hostNonAdditiveData); - event->write(hostAdditiveData); - event->init(); - inputData.push_back(event); - - sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); - EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)) - .WillRepeatedly(Return(hostUid)); - EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid))) - .WillRepeatedly(ReturnArg<0>()); - mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId); - - vector<vector<int>> actual; - extractIntoVector(inputData, actual); - vector<int> expectedV1 = {20, 22, 52}; - EXPECT_EQ(1, (int)actual.size()); - EXPECT_THAT(actual, Contains(expectedV1)); +TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUid) { + vector<shared_ptr<LogEvent>> data = { + // 30->32->31 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 31->32->21 + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid2, isolatedNonAdditiveData, + hostAdditiveData), + + // 20->32->21 + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, isolatedNonAdditiveData, + hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(3, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData, + actualFieldValues->at(2).mValue.int_value); +} + +TEST(PullerUtilTest, NoNeedToMerge) { + vector<shared_ptr<LogEvent>> data = { + // 32->31 + CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 22->21 + CreateTwoValueLogEvent(nonUidAtomTagId, timestamp, hostNonAdditiveData, + hostAdditiveData), + + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, nonUidAtomTagId, {} /*no additive fields*/); + + ASSERT_EQ(2, (int)data.size()); + + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(2, actualFieldValues->size()); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(1).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(2, actualFieldValues->size()); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(1).mValue.int_value); } -TEST(puller_util, MergeWithDimension) { - vector<shared_ptr<LogEvent>> inputData; - shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp); - // 30->32->31 - event->write(isolatedUid); - event->write(isolatedNonAdditiveData); - event->write(isolatedAdditiveData); - event->init(); - inputData.push_back(event); - - // 20->32->21 - event = make_shared<LogEvent>(uidAtomTagId, timestamp); - event->write(hostUid); - event->write(isolatedNonAdditiveData); - event->write(hostAdditiveData); - event->init(); - inputData.push_back(event); - - // 20->22->21 - event = make_shared<LogEvent>(uidAtomTagId, timestamp); - event->write(hostUid); - event->write(hostNonAdditiveData); - event->write(hostAdditiveData); - event->init(); - inputData.push_back(event); - - sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); - EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)) - .WillRepeatedly(Return(hostUid)); - EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid))) - .WillRepeatedly(ReturnArg<0>()); - mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId); - - vector<vector<int>> actual; - extractIntoVector(inputData, actual); - vector<int> expectedV1 = {20, 22, 21}; - vector<int> expectedV2 = {20, 32, 52}; - EXPECT_EQ(2, (int)actual.size()); - EXPECT_THAT(actual, Contains(expectedV1)); - EXPECT_THAT(actual, Contains(expectedV2)); +TEST(PullerUtilTest, MergeNoDimensionAttributionChain) { + vector<shared_ptr<LogEvent>> data = { + // 30->tag1->400->tag2->22->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, isolatedAdditiveData), + + // 20->tag1->400->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData, actualFieldValues->at(5).mValue.int_value); } -TEST(puller_util, NoMergeHostUidOnly) { - vector<shared_ptr<LogEvent>> inputData; - shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp); - // 20->32->31 - event->write(hostUid); - event->write(isolatedNonAdditiveData); - event->write(isolatedAdditiveData); - event->init(); - inputData.push_back(event); - - // 20->22->21 - event = make_shared<LogEvent>(uidAtomTagId, timestamp); - event->write(hostUid); - event->write(hostNonAdditiveData); - event->write(hostAdditiveData); - event->init(); - inputData.push_back(event); - - sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); - EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)) - .WillRepeatedly(Return(hostUid)); - EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid))) - .WillRepeatedly(ReturnArg<0>()); - mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId); - - // 20->32->31 - // 20->22->21 - vector<vector<int>> actual; - extractIntoVector(inputData, actual); - vector<int> expectedV1 = {20, 32, 31}; - vector<int> expectedV2 = {20, 22, 21}; - EXPECT_EQ(2, (int)actual.size()); - EXPECT_THAT(actual, Contains(expectedV1)); - EXPECT_THAT(actual, Contains(expectedV2)); +TEST(PullerUtilTest, MergeWithDimensionAttributionChain) { + vector<shared_ptr<LogEvent>> data = { + // 200->tag1->30->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, isolatedUid1}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 200->tag1->20->tag2->32->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid}, + {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), + + // 200->tag1->20->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {200, hostUid}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(200, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); } -TEST(puller_util, IsolatedUidOnly) { - vector<shared_ptr<LogEvent>> inputData; - shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp); - // 30->32->31 - event->write(hostUid); - event->write(isolatedNonAdditiveData); - event->write(isolatedAdditiveData); - event->init(); - inputData.push_back(event); - - // 30->22->21 - event = make_shared<LogEvent>(uidAtomTagId, timestamp); - event->write(hostUid); - event->write(hostNonAdditiveData); - event->write(hostAdditiveData); - event->init(); - inputData.push_back(event); - - sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); - EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)) - .WillRepeatedly(Return(hostUid)); - EXPECT_CALL(*uidMap, getHostUidOrSelf(Ne(isolatedUid))) - .WillRepeatedly(ReturnArg<0>()); - mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId); - - // 20->32->31 - // 20->22->21 - vector<vector<int>> actual; - extractIntoVector(inputData, actual); - vector<int> expectedV1 = {20, 32, 31}; - vector<int> expectedV2 = {20, 22, 21}; - EXPECT_EQ(2, (int)actual.size()); - EXPECT_THAT(actual, Contains(expectedV1)); - EXPECT_THAT(actual, Contains(expectedV2)); +TEST(PullerUtilTest, NoMergeHostUidOnlyAttributionChain) { + vector<shared_ptr<LogEvent>> data = { + // 20->tag1->400->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 20->tag1->400->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); + + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); } -TEST(puller_util, MultipleIsolatedUidToOneHostUid) { - vector<shared_ptr<LogEvent>> inputData; - shared_ptr<LogEvent> event = make_shared<LogEvent>(uidAtomTagId, timestamp); - // 30->32->31 - event->write(isolatedUid); - event->write(isolatedNonAdditiveData); - event->write(isolatedAdditiveData); - event->init(); - inputData.push_back(event); - - // 31->32->21 - event = make_shared<LogEvent>(uidAtomTagId, timestamp); - event->write(isolatedUid + 1); - event->write(isolatedNonAdditiveData); - event->write(hostAdditiveData); - event->init(); - inputData.push_back(event); - - // 20->32->21 - event = make_shared<LogEvent>(uidAtomTagId, timestamp); - event->write(hostUid); - event->write(isolatedNonAdditiveData); - event->write(hostAdditiveData); - event->init(); - inputData.push_back(event); - - sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); - EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(Return(hostUid)); - mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, uidAtomTagId); - - vector<vector<int>> actual; - extractIntoVector(inputData, actual); - vector<int> expectedV1 = {20, 32, 73}; - EXPECT_EQ(1, (int)actual.size()); - EXPECT_THAT(actual, Contains(expectedV1)); +TEST(PullerUtilTest, IsolatedUidOnlyAttributionChain) { + vector<shared_ptr<LogEvent>> data = { + // 30->tag1->400->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 30->tag1->400->tag2->22->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, hostNonAdditiveData, hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + + // 20->tag1->400->tag2->32->31 + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(5).mValue.int_value); + + // 20->tag1->400->tag2->22->21 + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData, actualFieldValues->at(5).mValue.int_value); } -TEST(puller_util, NoNeedToMerge) { - vector<shared_ptr<LogEvent>> inputData; - shared_ptr<LogEvent> event = - make_shared<LogEvent>(nonUidAtomTagId, timestamp); - // 32 - event->write(isolatedNonAdditiveData); - event->init(); - inputData.push_back(event); - - event = make_shared<LogEvent>(nonUidAtomTagId, timestamp); - // 22 - event->write(hostNonAdditiveData); - event->init(); - inputData.push_back(event); - - sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); - mapAndMergeIsolatedUidsToHostUid(inputData, uidMap, nonUidAtomTagId); - - EXPECT_EQ(2, (int)inputData.size()); +TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUidAttributionChain) { + vector<shared_ptr<LogEvent>> data = { + // 30->tag1->400->tag2->32->31 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid1, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, + isolatedAdditiveData), + + // 31->tag1->400->tag2->32->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {isolatedUid2, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), + + // 20->tag1->400->tag2->32->21 + makeAttributionLogEvent(attributionAtomTagId, timestamp, {hostUid, 400}, + {"tag1", "tag2"}, isolatedNonAdditiveData, hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, attributionAtomTagId, additiveFields); + + ASSERT_EQ(1, (int)data.size()); + + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ("tag1", actualFieldValues->at(1).mValue.str_value); + EXPECT_EQ(400, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ("tag2", actualFieldValues->at(3).mValue.str_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(isolatedAdditiveData + hostAdditiveData + hostAdditiveData, + actualFieldValues->at(5).mValue.int_value); } } // namespace statsd diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 2a43d9ba3984..428c46f8a0d2 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -13,7 +13,7 @@ // limitations under the License. #include "src/guardrail/StatsdStats.h" -#include "statslog.h" +#include "statslog_statsdtest.h" #include "tests/statsd_test_util.h" #include <gtest/gtest.h> @@ -42,7 +42,7 @@ TEST(StatsdStatsTest, TestValidConfigAdd) { StatsdStatsReport report; bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(1, report.config_stats_size()); + ASSERT_EQ(1, report.config_stats_size()); const auto& configReport = report.config_stats(0); EXPECT_EQ(0, configReport.uid()); EXPECT_EQ(12345, configReport.id()); @@ -69,7 +69,7 @@ TEST(StatsdStatsTest, TestInvalidConfigAdd) { StatsdStatsReport report; bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(1, report.config_stats_size()); + ASSERT_EQ(1, report.config_stats_size()); const auto& configReport = report.config_stats(0); // The invalid config should be put into icebox with a deletion time. EXPECT_TRUE(configReport.has_deletion_time_sec()); @@ -89,7 +89,7 @@ TEST(StatsdStatsTest, TestConfigRemove) { StatsdStatsReport report; bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(1, report.config_stats_size()); + ASSERT_EQ(1, report.config_stats_size()); const auto& configReport = report.config_stats(0); EXPECT_FALSE(configReport.has_deletion_time_sec()); @@ -97,7 +97,7 @@ TEST(StatsdStatsTest, TestConfigRemove) { stats.dumpStats(&output, false); good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(1, report.config_stats_size()); + ASSERT_EQ(1, report.config_stats_size()); const auto& configReport2 = report.config_stats(0); EXPECT_TRUE(configReport2.has_deletion_time_sec()); } @@ -145,21 +145,21 @@ TEST(StatsdStatsTest, TestSubStats) { StatsdStatsReport report; bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(1, report.config_stats_size()); + ASSERT_EQ(1, report.config_stats_size()); const auto& configReport = report.config_stats(0); - EXPECT_EQ(2, configReport.broadcast_sent_time_sec_size()); - EXPECT_EQ(1, configReport.data_drop_time_sec_size()); - EXPECT_EQ(1, configReport.data_drop_bytes_size()); + ASSERT_EQ(2, configReport.broadcast_sent_time_sec_size()); + ASSERT_EQ(1, configReport.data_drop_time_sec_size()); + ASSERT_EQ(1, configReport.data_drop_bytes_size()); EXPECT_EQ(123, configReport.data_drop_bytes(0)); - EXPECT_EQ(3, configReport.dump_report_time_sec_size()); - EXPECT_EQ(3, configReport.dump_report_data_size_size()); - EXPECT_EQ(2, configReport.activation_time_sec_size()); - EXPECT_EQ(1, configReport.deactivation_time_sec_size()); - EXPECT_EQ(1, configReport.annotation_size()); + ASSERT_EQ(3, configReport.dump_report_time_sec_size()); + ASSERT_EQ(3, configReport.dump_report_data_size_size()); + ASSERT_EQ(2, configReport.activation_time_sec_size()); + ASSERT_EQ(1, configReport.deactivation_time_sec_size()); + ASSERT_EQ(1, configReport.annotation_size()); EXPECT_EQ(123, configReport.annotation(0).field_int64()); EXPECT_EQ(456, configReport.annotation(0).field_int32()); - EXPECT_EQ(2, configReport.matcher_stats_size()); + ASSERT_EQ(2, configReport.matcher_stats_size()); // matcher1 is the first in the list if (configReport.matcher_stats(0).id() == StringToId("matcher1")) { EXPECT_EQ(2, configReport.matcher_stats(0).matched_times()); @@ -174,18 +174,18 @@ TEST(StatsdStatsTest, TestSubStats) { EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id()); } - EXPECT_EQ(2, configReport.alert_stats_size()); + ASSERT_EQ(2, configReport.alert_stats_size()); bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1"); EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id()); EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times()); EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id()); EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times()); - EXPECT_EQ(1, configReport.condition_stats_size()); + ASSERT_EQ(1, configReport.condition_stats_size()); EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id()); EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts()); - EXPECT_EQ(1, configReport.metric_stats_size()); + ASSERT_EQ(1, configReport.metric_stats_size()); EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id()); EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts()); @@ -199,21 +199,21 @@ TEST(StatsdStatsTest, TestSubStats) { stats.dumpStats(&output, false); good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(1, report.config_stats_size()); + ASSERT_EQ(1, report.config_stats_size()); const auto& configReport2 = report.config_stats(0); - EXPECT_EQ(1, configReport2.matcher_stats_size()); + ASSERT_EQ(1, configReport2.matcher_stats_size()); EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id()); EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times()); - EXPECT_EQ(1, configReport2.condition_stats_size()); + ASSERT_EQ(1, configReport2.condition_stats_size()); EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id()); EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts()); - EXPECT_EQ(1, configReport2.metric_stats_size()); + ASSERT_EQ(1, configReport2.metric_stats_size()); EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id()); EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts()); - EXPECT_EQ(1, configReport2.alert_stats_size()); + ASSERT_EQ(1, configReport2.alert_stats_size()); EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id()); EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times()); } @@ -222,11 +222,11 @@ TEST(StatsdStatsTest, TestAtomLog) { StatsdStats stats; time_t now = time(nullptr); // old event, we get it from the stats buffer. should be ignored. - stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, 1000); + stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000); - stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 1); - stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2); - stats.noteAtomLogged(android::util::APP_CRASH_OCCURRED, now + 3); + stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1); + stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2); + stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3); vector<uint8_t> output; stats.dumpStats(&output, false); @@ -234,15 +234,15 @@ TEST(StatsdStatsTest, TestAtomLog) { bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(2, report.atom_stats_size()); + ASSERT_EQ(2, report.atom_stats_size()); bool sensorAtomGood = false; bool dropboxAtomGood = false; for (const auto& atomStats : report.atom_stats()) { - if (atomStats.tag() == android::util::SENSOR_STATE_CHANGED && atomStats.count() == 3) { + if (atomStats.tag() == util::SENSOR_STATE_CHANGED && atomStats.count() == 3) { sensorAtomGood = true; } - if (atomStats.tag() == android::util::APP_CRASH_OCCURRED && atomStats.count() == 1) { + if (atomStats.tag() == util::APP_CRASH_OCCURRED && atomStats.count() == 1) { dropboxAtomGood = true; } } @@ -254,8 +254,8 @@ TEST(StatsdStatsTest, TestAtomLog) { TEST(StatsdStatsTest, TestNonPlatformAtomLog) { StatsdStats stats; time_t now = time(nullptr); - int newAtom1 = android::util::kMaxPushedAtomId + 1; - int newAtom2 = android::util::kMaxPushedAtomId + 2; + int newAtom1 = StatsdStats::kMaxPushedAtomId + 1; + int newAtom2 = StatsdStats::kMaxPushedAtomId + 2; stats.noteAtomLogged(newAtom1, now + 1); stats.noteAtomLogged(newAtom1, now + 2); @@ -267,7 +267,7 @@ TEST(StatsdStatsTest, TestNonPlatformAtomLog) { bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(2, report.atom_stats_size()); + ASSERT_EQ(2, report.atom_stats_size()); bool newAtom1Good = false; bool newAtom2Good = false; @@ -287,22 +287,27 @@ TEST(StatsdStatsTest, TestNonPlatformAtomLog) { TEST(StatsdStatsTest, TestPullAtomStats) { StatsdStats stats; - stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 3333L); - stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 2222L); - stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 4444L); - - stats.notePull(android::util::DISK_SPACE); - stats.notePullTime(android::util::DISK_SPACE, 1111L); - stats.notePullDelay(android::util::DISK_SPACE, 1111L); - stats.notePull(android::util::DISK_SPACE); - stats.notePullTime(android::util::DISK_SPACE, 3333L); - stats.notePullDelay(android::util::DISK_SPACE, 3335L); - stats.notePull(android::util::DISK_SPACE); - stats.notePullFromCache(android::util::DISK_SPACE); - stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, true); - stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, false); - stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, true); - + stats.updateMinPullIntervalSec(util::DISK_SPACE, 3333L); + stats.updateMinPullIntervalSec(util::DISK_SPACE, 2222L); + stats.updateMinPullIntervalSec(util::DISK_SPACE, 4444L); + + stats.notePull(util::DISK_SPACE); + stats.notePullTime(util::DISK_SPACE, 1111L); + stats.notePullDelay(util::DISK_SPACE, 1111L); + stats.notePull(util::DISK_SPACE); + stats.notePullTime(util::DISK_SPACE, 3333L); + stats.notePullDelay(util::DISK_SPACE, 3335L); + stats.notePull(util::DISK_SPACE); + stats.notePullFromCache(util::DISK_SPACE); + stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); + stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, false); + stats.notePullerCallbackRegistrationChanged(util::DISK_SPACE, true); + stats.notePullBinderCallFailed(util::DISK_SPACE); + stats.notePullUidProviderNotFound(util::DISK_SPACE); + stats.notePullerNotFound(util::DISK_SPACE); + stats.notePullerNotFound(util::DISK_SPACE); + stats.notePullTimeout(util::DISK_SPACE, 3000L, 6000L); + stats.notePullTimeout(util::DISK_SPACE, 4000L, 7000L); vector<uint8_t> output; stats.dumpStats(&output, false); @@ -310,9 +315,9 @@ TEST(StatsdStatsTest, TestPullAtomStats) { bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(1, report.pulled_atom_stats_size()); + ASSERT_EQ(1, report.pulled_atom_stats_size()); - EXPECT_EQ(android::util::DISK_SPACE, report.pulled_atom_stats(0).atom_id()); + EXPECT_EQ(util::DISK_SPACE, report.pulled_atom_stats(0).atom_id()); EXPECT_EQ(3, report.pulled_atom_stats(0).total_pull()); EXPECT_EQ(1, report.pulled_atom_stats(0).total_pull_from_cache()); EXPECT_EQ(2222L, report.pulled_atom_stats(0).min_pull_interval_sec()); @@ -322,6 +327,16 @@ TEST(StatsdStatsTest, TestPullAtomStats) { EXPECT_EQ(3335L, report.pulled_atom_stats(0).max_pull_delay_nanos()); EXPECT_EQ(2L, report.pulled_atom_stats(0).registered_count()); EXPECT_EQ(1L, report.pulled_atom_stats(0).unregistered_count()); + EXPECT_EQ(1L, report.pulled_atom_stats(0).binder_call_failed()); + EXPECT_EQ(1L, report.pulled_atom_stats(0).failed_uid_provider_not_found()); + EXPECT_EQ(2L, report.pulled_atom_stats(0).puller_not_found()); + ASSERT_EQ(2, report.pulled_atom_stats(0).pull_atom_metadata_size()); + EXPECT_EQ(3000L, report.pulled_atom_stats(0).pull_atom_metadata(0).pull_timeout_uptime_millis()); + EXPECT_EQ(4000L, report.pulled_atom_stats(0).pull_atom_metadata(1).pull_timeout_uptime_millis()); + EXPECT_EQ(6000L, report.pulled_atom_stats(0).pull_atom_metadata(0) + .pull_timeout_elapsed_millis()); + EXPECT_EQ(7000L, report.pulled_atom_stats(0).pull_atom_metadata(1) + .pull_timeout_elapsed_millis()); } TEST(StatsdStatsTest, TestAtomMetricsStats) { @@ -342,7 +357,7 @@ TEST(StatsdStatsTest, TestAtomMetricsStats) { bool good = report.ParseFromArray(&output[0], output.size()); EXPECT_TRUE(good); - EXPECT_EQ(2, report.atom_metric_stats().size()); + ASSERT_EQ(2, report.atom_metric_stats().size()); auto atomStats = report.atom_metric_stats(0); EXPECT_EQ(1000L, atomStats.metric_id()); @@ -401,11 +416,11 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { const auto& configStats = stats.mConfigStats[key]; size_t maxCount = StatsdStats::kMaxTimestampCount; - EXPECT_EQ(maxCount, configStats->broadcast_sent_time_sec.size()); - EXPECT_EQ(maxCount, configStats->data_drop_time_sec.size()); - EXPECT_EQ(maxCount, configStats->dump_report_stats.size()); - EXPECT_EQ(maxCount, configStats->activation_time_sec.size()); - EXPECT_EQ(maxCount, configStats->deactivation_time_sec.size()); + ASSERT_EQ(maxCount, configStats->broadcast_sent_time_sec.size()); + ASSERT_EQ(maxCount, configStats->data_drop_time_sec.size()); + ASSERT_EQ(maxCount, configStats->dump_report_stats.size()); + ASSERT_EQ(maxCount, configStats->activation_time_sec.size()); + ASSERT_EQ(maxCount, configStats->deactivation_time_sec.size()); // the oldest timestamp is the second timestamp in history EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front()); @@ -435,13 +450,13 @@ TEST(StatsdStatsTest, TestSystemServerCrash) { StatsdStatsReport report; EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); const int maxCount = StatsdStats::kMaxSystemServerRestarts; - EXPECT_EQ(maxCount, (int)report.system_restart_sec_size()); + ASSERT_EQ(maxCount, (int)report.system_restart_sec_size()); stats.noteSystemServerRestart(StatsdStats::kMaxSystemServerRestarts + 1); output.clear(); stats.dumpStats(&output, false); EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); - EXPECT_EQ(maxCount, (int)report.system_restart_sec_size()); + ASSERT_EQ(maxCount, (int)report.system_restart_sec_size()); EXPECT_EQ(StatsdStats::kMaxSystemServerRestarts + 1, report.system_restart_sec(maxCount - 1)); } @@ -462,19 +477,19 @@ TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) { StatsdStatsReport report; EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); - EXPECT_EQ(2, report.activation_guardrail_stats_size()); + ASSERT_EQ(2, report.activation_guardrail_stats_size()); bool uid1Good = false; bool uid2Good = false; for (const auto& guardrailTimes : report.activation_guardrail_stats()) { if (uid1 == guardrailTimes.uid()) { uid1Good = true; - EXPECT_EQ(2, guardrailTimes.guardrail_met_sec_size()); + ASSERT_EQ(2, guardrailTimes.guardrail_met_sec_size()); EXPECT_EQ(10, guardrailTimes.guardrail_met_sec(0)); EXPECT_EQ(20, guardrailTimes.guardrail_met_sec(1)); } else if (uid2 == guardrailTimes.uid()) { int maxCount = StatsdStats::kMaxTimestampCount; uid2Good = true; - EXPECT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size()); + ASSERT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size()); for (int i = 0; i < maxCount; i++) { EXPECT_EQ(100 - maxCount + i, guardrailTimes.guardrail_met_sec(i)); } @@ -486,6 +501,41 @@ TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) { EXPECT_TRUE(uid2Good); } +TEST(StatsdStatsTest, TestAtomErrorStats) { + StatsdStats stats; + + int pushAtomTag = 100; + int pullAtomTag = 1000; + int numErrors = 10; + + for (int i = 0; i < numErrors; i++) { + // We must call noteAtomLogged as well because only those pushed atoms + // that have been logged will have stats printed about them in the + // proto. + stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0); + stats.noteAtomError(pushAtomTag, /*pull=*/false); + + stats.noteAtomError(pullAtomTag, /*pull=*/true); + } + + vector<uint8_t> output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); + + // Check error count = numErrors for push atom + ASSERT_EQ(1, report.atom_stats_size()); + const auto& pushedAtomStats = report.atom_stats(0); + EXPECT_EQ(pushAtomTag, pushedAtomStats.tag()); + EXPECT_EQ(numErrors, pushedAtomStats.error_count()); + + // Check error count = numErrors for pull atom + ASSERT_EQ(1, report.pulled_atom_stats_size()); + const auto& pulledAtomStats = report.pulled_atom_stats(0); + EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id()); + EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp index d6cd876e5ad5..3a654565b4aa 100644 --- a/cmds/statsd/tests/indexed_priority_queue_test.cpp +++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp @@ -44,23 +44,23 @@ TEST(indexed_priority_queue, empty_and_size) { sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId}; sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId}; - EXPECT_EQ(0u, ipq.size()); + ASSERT_EQ(0u, ipq.size()); EXPECT_TRUE(ipq.empty()); ipq.push(aa4); - EXPECT_EQ(1u, ipq.size()); + ASSERT_EQ(1u, ipq.size()); EXPECT_FALSE(ipq.empty()); ipq.push(aa8); - EXPECT_EQ(2u, ipq.size()); + ASSERT_EQ(2u, ipq.size()); EXPECT_FALSE(ipq.empty()); ipq.remove(aa4); - EXPECT_EQ(1u, ipq.size()); + ASSERT_EQ(1u, ipq.size()); EXPECT_FALSE(ipq.empty()); ipq.remove(aa8); - EXPECT_EQ(0u, ipq.size()); + ASSERT_EQ(0u, ipq.size()); EXPECT_TRUE(ipq.empty()); } @@ -126,17 +126,17 @@ TEST(indexed_priority_queue, push_same_aa) { sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId}; ipq.push(aa4_a); - EXPECT_EQ(1u, ipq.size()); + ASSERT_EQ(1u, ipq.size()); EXPECT_TRUE(ipq.contains(aa4_a)); EXPECT_FALSE(ipq.contains(aa4_b)); ipq.push(aa4_a); - EXPECT_EQ(1u, ipq.size()); + ASSERT_EQ(1u, ipq.size()); EXPECT_TRUE(ipq.contains(aa4_a)); EXPECT_FALSE(ipq.contains(aa4_b)); ipq.push(aa4_b); - EXPECT_EQ(2u, ipq.size()); + ASSERT_EQ(2u, ipq.size()); EXPECT_TRUE(ipq.contains(aa4_a)); EXPECT_TRUE(ipq.contains(aa4_b)); } @@ -150,7 +150,7 @@ TEST(indexed_priority_queue, remove_nonexistant) { ipq.push(aa4); ipq.remove(aa5); - EXPECT_EQ(1u, ipq.size()); + ASSERT_EQ(1u, ipq.size()); EXPECT_TRUE(ipq.contains(aa4)); EXPECT_FALSE(ipq.contains(aa5)); } @@ -164,17 +164,17 @@ TEST(indexed_priority_queue, remove_same_aa) { ipq.push(aa4_a); ipq.push(aa4_b); - EXPECT_EQ(2u, ipq.size()); + ASSERT_EQ(2u, ipq.size()); EXPECT_TRUE(ipq.contains(aa4_a)); EXPECT_TRUE(ipq.contains(aa4_b)); ipq.remove(aa4_b); - EXPECT_EQ(1u, ipq.size()); + ASSERT_EQ(1u, ipq.size()); EXPECT_TRUE(ipq.contains(aa4_a)); EXPECT_FALSE(ipq.contains(aa4_b)); ipq.remove(aa4_a); - EXPECT_EQ(0u, ipq.size()); + ASSERT_EQ(0u, ipq.size()); EXPECT_FALSE(ipq.contains(aa4_a)); EXPECT_FALSE(ipq.contains(aa4_b)); } @@ -205,22 +205,22 @@ TEST(indexed_priority_queue, pop) { ipq.push(c); ipq.push(b); ipq.push(a); - EXPECT_EQ(3u, ipq.size()); + ASSERT_EQ(3u, ipq.size()); ipq.pop(); - EXPECT_EQ(2u, ipq.size()); + ASSERT_EQ(2u, ipq.size()); EXPECT_FALSE(ipq.contains(a)); EXPECT_TRUE(ipq.contains(b)); EXPECT_TRUE(ipq.contains(c)); ipq.pop(); - EXPECT_EQ(1u, ipq.size()); + ASSERT_EQ(1u, ipq.size()); EXPECT_FALSE(ipq.contains(a)); EXPECT_FALSE(ipq.contains(b)); EXPECT_TRUE(ipq.contains(c)); ipq.pop(); - EXPECT_EQ(0u, ipq.size()); + ASSERT_EQ(0u, ipq.size()); EXPECT_FALSE(ipq.contains(a)); EXPECT_FALSE(ipq.contains(b)); EXPECT_FALSE(ipq.contains(c)); diff --git a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp index f27d12957f11..a15f95bef358 100644 --- a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp +++ b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp @@ -16,9 +16,12 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <stdio.h> + #include <thread> -#include <stdio.h> +#include "stats_event.h" +#include "tests/statsd_test_util.h" namespace android { namespace os { @@ -29,6 +32,20 @@ using namespace testing; using std::unique_ptr; +namespace { + +std::unique_ptr<LogEvent> makeLogEvent(uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 10); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +} // anonymous namespace + #ifdef __ANDROID__ TEST(LogEventQueue_test, TestGoodConsumer) { LogEventQueue queue(50); @@ -36,8 +53,7 @@ TEST(LogEventQueue_test, TestGoodConsumer) { std::thread writer([&queue, timeBaseNs] { for (int i = 0; i < 100; i++) { int64_t oldestEventNs; - bool success = queue.push(std::make_unique<LogEvent>(10, timeBaseNs + i * 1000), - &oldestEventNs); + bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs); EXPECT_TRUE(success); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -63,8 +79,7 @@ TEST(LogEventQueue_test, TestSlowConsumer) { int failure_count = 0; int64_t oldestEventNs; for (int i = 0; i < 100; i++) { - bool success = queue.push(std::make_unique<LogEvent>(10, timeBaseNs + i * 1000), - &oldestEventNs); + bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs); if (!success) failure_count++; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } diff --git a/cmds/statsd/tests/metadata_util_test.cpp b/cmds/statsd/tests/metadata_util_test.cpp new file mode 100644 index 000000000000..7707890cbd0c --- /dev/null +++ b/cmds/statsd/tests/metadata_util_test.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 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 "metadata_util.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +TEST(MetadataUtilTest, TestWriteAndReadMetricDimensionKey) { + HashableDimensionKey dim; + HashableDimensionKey dim2; + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 1, 3}; + int pos4[] = {2, 0, 0}; + Field field1(10, pos1, 2); + Field field2(10, pos2, 2); + Field field3(10, pos3, 2); + Field field4(10, pos4, 0); + + Value value1((int32_t)10025); + Value value2("tag"); + Value value3((int32_t)987654); + Value value4((int32_t)99999); + + dim.addValue(FieldValue(field1, value1)); + dim.addValue(FieldValue(field2, value2)); + dim.addValue(FieldValue(field3, value3)); + dim.addValue(FieldValue(field4, value4)); + + dim2.addValue(FieldValue(field1, value1)); + dim2.addValue(FieldValue(field2, value2)); + + MetricDimensionKey dimKey(dim, dim2); + + metadata::MetricDimensionKey metadataDimKey; + writeMetricDimensionKeyToMetadataDimensionKey(dimKey, &metadataDimKey); + + MetricDimensionKey loadedDimKey = loadMetricDimensionKeyFromProto(metadataDimKey); + + ASSERT_EQ(loadedDimKey, dimKey); + ASSERT_EQ(std::hash<MetricDimensionKey>{}(loadedDimKey), + std::hash<MetricDimensionKey>{}(dimKey)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 67c704eb87fd..bb8e7bfd90f4 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -13,16 +13,19 @@ // limitations under the License. #include "src/metrics/CountMetricProducer.h" -#include "src/stats_log_util.h" -#include "metrics_test_helper.h" -#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <math.h> #include <stdio.h> + #include <vector> +#include "metrics_test_helper.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + using namespace testing; using android::sp; using std::set; @@ -35,16 +38,44 @@ namespace android { namespace os { namespace statsd { + +namespace { const ConfigKey kConfigKey(0, 12345); +void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeString(statsEvent, uid.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +} // namespace + +// Setup for parameterized tests. +class CountMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(CountMetricProducerTest_PartialBucket, + CountMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + TEST(CountMetricProducerTest, TestFirstBucket) { CountMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - 5, 600 * NS_PER_SEC + NS_PER_SEC/2); + CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2); EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs); EXPECT_EQ(10, countProducer.mCurrentBucketNum); EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs()); @@ -61,45 +92,46 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { metric.set_id(1); metric.set_bucket(ONE_MINUTE); - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.init(); - LogEvent event2(tagId, bucketStartTimeNs + 2); - event2.init(); - sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - bucketStartTimeNs, bucketStartTimeNs); + CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, bucketStartTimeNs, bucketStartTimeNs); // 2 events in bucket 1. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // Flushes at event #2. countProducer.flushIfNeededLocked(bucketStartTimeNs + 2); - EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); // Flushes. countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(1UL, buckets.size()); + ASSERT_EQ(1UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(2LL, buckets[0].mCount); // 1 matched event happens in bucket 2. - LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2); - event3.init(); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 2, tagId); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1]; EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs); EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs); @@ -107,11 +139,11 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { // nothing happens in bucket 3. we should not record anything for bucket 3. countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); - EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(2UL, buckets3.size()); + ASSERT_EQ(2UL, buckets3.size()); } TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { @@ -123,37 +155,38 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { metric.set_bucket(ONE_MINUTE); metric.set_condition(StringToId("SCREEN_ON")); - LogEvent event1(1, bucketStartTimeNs + 1); - event1.init(); - - LogEvent event2(1, bucketStartTimeNs + 10); - event2.init(); - sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs, bucketStartTimeNs); + CountMetricProducer countProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + bucketStartTimeNs, bucketStartTimeNs); countProducer.onConditionChanged(true, bucketStartTimeNs); + + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, /*atomId=*/1); countProducer.onMatchedLogEvent(1 /*matcher index*/, event1); - EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); + + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2); + // Upon this match event, the matched event1 is flushed. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 10, /*atomId=*/1); countProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); - { - const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(1UL, buckets.size()); - const auto& bucketInfo = buckets[0]; - EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); - EXPECT_EQ(1LL, bucketInfo.mCount); - } + + const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; + ASSERT_EQ(1UL, buckets.size()); + const auto& bucketInfo = buckets[0]; + EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); + EXPECT_EQ(1LL, bucketInfo.mCount); } TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { @@ -172,50 +205,52 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.write("111"); // uid - event1.init(); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 10, tagId, /*uid=*/"222"); + ConditionKey key1; - key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = - {getMockedDimensionKey(conditionTagId, 2, "111")}; + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = { + getMockedDimensionKey(conditionTagId, 2, "111")}; - LogEvent event2(tagId, bucketStartTimeNs + 10); - event2.write("222"); // uid - event2.init(); ConditionKey key2; - key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = - {getMockedDimensionKey(conditionTagId, 2, "222")}; + key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = { + getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue)); + EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse)); - CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard, - bucketStartTimeNs, bucketStartTimeNs); + EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); + + CountMetricProducer countProducer(kConfigKey, metric, 0 /*condition tracker index*/, + {ConditionState::kUnknown}, wizard, bucketStartTimeNs, + bucketStartTimeNs); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); countProducer.flushIfNeededLocked(bucketStartTimeNs + 1); - EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, countProducer.mPastBuckets.size()); EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != countProducer.mPastBuckets.end()); const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(1UL, buckets.size()); + ASSERT_EQ(1UL, buckets.size()); const auto& bucketInfo = buckets[0]; EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); EXPECT_EQ(1LL, bucketInfo.mCount); } -TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { +TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) { sp<AlarmMonitor> alarmMonitor; int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; int tagId = 1; int conditionTagId = 2; @@ -226,57 +261,66 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { Alert alert; alert.set_num_buckets(3); alert.set_trigger_if_sum_gt(2); - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.write("111"); // uid - event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, + + CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard, bucketStartTimeNs, bucketStartTimeNs); sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); EXPECT_TRUE(anomalyTracker != nullptr); - // Bucket is flushed yet. + // Bucket is not flushed yet. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - // App upgrade forces bucket flush. + // App upgrade or boot complete forces bucket flush. // Check that there's a past bucket and the bucket end is not adjusted. - countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((long long)bucketStartTimeNs, + switch (GetParam()) { + case APP_UPGRADE: + countProducer.notifyAppUpgrade(eventTimeNs); + break; + case BOOT_COMPLETE: + countProducer.onStatsdInitCompleted(eventTimeNs); + break; + } + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); - EXPECT_EQ((long long)eventUpgradeTimeNs, + EXPECT_EQ(eventTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, countProducer.getCurrentBucketNum()); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); // Anomaly tracker only contains full buckets. EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); int64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs(); // Next event occurs in same bucket as partial bucket created. - LogEvent event2(tagId, bucketStartTimeNs + 59 * NS_PER_SEC + 10); - event2.write("222"); // uid - event2.init(); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 59 * NS_PER_SEC + 10, tagId, /*uid=*/"222"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, countProducer.getCurrentBucketNum()); EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); // Third event in following bucket. - LogEvent event3(tagId, bucketStartTimeNs + 62 * NS_PER_SEC + 10); - event3.write("333"); // uid - event3.init(); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + 62 * NS_PER_SEC + 10, tagId, /*uid=*/"333"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, countProducer.getCurrentBucketNum()); EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); } -TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { +TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + int64_t eventTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; int tagId = 1; int conditionTagId = 2; @@ -284,41 +328,48 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { CountMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.write("111"); // uid - event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, + + CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard, bucketStartTimeNs, bucketStartTimeNs); // Bucket is flushed yet. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); - EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); - - // App upgrade forces bucket flush. - // Check that there's a past bucket and the bucket end is not adjusted. - countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)bucketStartTimeNs, + ASSERT_EQ(0UL, countProducer.mPastBuckets.size()); + + // App upgrade or boot complete forces bucket flush. + // Check that there's a past bucket and the bucket end is not adjusted since the upgrade + // occurred after the bucket end time. + switch (GetParam()) { + case APP_UPGRADE: + countProducer.notifyAppUpgrade(eventTimeNs); + break; + case BOOT_COMPLETE: + countProducer.onStatsdInitCompleted(eventTimeNs); + break; + } + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); // Next event occurs in same bucket as partial bucket created. - LogEvent event2(tagId, bucketStartTimeNs + 70 * NS_PER_SEC + 10); - event2.write("222"); // uid - event2.init(); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 70 * NS_PER_SEC + 10, tagId, /*uid=*/"222"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); // Third event in following bucket. - LogEvent event3(tagId, bucketStartTimeNs + 121 * NS_PER_SEC + 10); - event3.write("333"); // uid - event3.init(); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + 121 * NS_PER_SEC + 10, tagId, /*uid=*/"333"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)eventUpgradeTimeNs, + ASSERT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ((int64_t)eventTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs); @@ -344,38 +395,39 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { metric.set_bucket(ONE_MINUTE); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - bucketStartTimeNs, bucketStartTimeNs); + + CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, bucketStartTimeNs, bucketStartTimeNs); sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); int tagId = 1; - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.init(); - LogEvent event2(tagId, bucketStartTimeNs + 2); - event2.init(); - LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1); - event3.init(); - LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1); - event4.init(); - LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2); - event5.init(); - LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3); - event6.init(); - LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC); - event7.init(); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + 2 * bucketSizeNs + 1, tagId); + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event4, bucketStartTimeNs + 3 * bucketSizeNs + 1, tagId); + LogEvent event5(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event5, bucketStartTimeNs + 3 * bucketSizeNs + 2, tagId); + LogEvent event6(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event6, bucketStartTimeNs + 3 * bucketSizeNs + 3, tagId); + LogEvent event7(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event7, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, tagId); // Two events in bucket #0. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); + ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // One event in bucket #2. No alarm as bucket #0 is trashed out. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); + ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); @@ -383,17 +435,37 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6); - EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); + ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second); // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); - EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); + ASSERT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); +} + +TEST(CountMetricProducerTest, TestOneWeekTimeUnit) { + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_WEEK); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + + int64_t oneDayNs = 24 * 60 * 60 * 1e9; + int64_t fiveWeeksNs = 5 * 7 * oneDayNs; + + CountMetricProducer countProducer(kConfigKey, metric, -1 /* meaning no condition */, {}, wizard, + oneDayNs, fiveWeeksNs); + + int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs; + + EXPECT_EQ(fiveWeeksNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(4, countProducer.mCurrentBucketNum); + EXPECT_EQ(fiveWeeksOneDayNs, countProducer.getCurrentBucketEndTimeNs()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index b294cad1802c..05cfa37b0ee1 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -13,17 +13,21 @@ // limitations under the License. #include "src/metrics/DurationMetricProducer.h" -#include "src/stats_log_util.h" -#include "metrics_test_helper.h" -#include "src/condition/ConditionWizard.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <stdio.h> + #include <set> #include <unordered_map> #include <vector> +#include "metrics_test_helper.h" +#include "src/condition/ConditionWizard.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + using namespace android::os::statsd; using namespace testing; using android::sp; @@ -37,7 +41,26 @@ namespace android { namespace os { namespace statsd { + +namespace { + const ConfigKey kConfigKey(0, 12345); +void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +} // namespace + +// Setup for parameterized tests. +class DurationMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket, + DurationMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); TEST(DurationMetricTrackerTest, TestFirstBucket) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -47,9 +70,11 @@ TEST(DurationMetricTrackerTest, TestFirstBucket) { metric.set_aggregation_type(DurationMetric_AggregationType_SUM); FieldMatcher dimensions; - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC/2); + + DurationMetricProducer durationProducer(kConfigKey, metric, -1 /*no condition*/, {}, + 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, + dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2); EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs); EXPECT_EQ(10, durationProducer.mCurrentBucketNum); @@ -67,24 +92,26 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { metric.set_aggregation_type(DurationMetric_AggregationType_SUM); int tagId = 1; - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.init(); - LogEvent event2(tagId, bucketStartTimeNs + bucketSizeNs + 2); - event2.init(); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + bucketSizeNs + 2, tagId); FieldMatcher dimensions; - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + DurationMetricProducer durationProducer(kConfigKey, metric, -1 /*no condition*/, {}, + 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, + dimensions, bucketStartTimeNs, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(2UL, buckets.size()); + ASSERT_EQ(2UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(bucketSizeNs - 1LL, buckets[0].mDuration); @@ -104,19 +131,21 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { metric.set_aggregation_type(DurationMetric_AggregationType_SUM); int tagId = 1; - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.init(); - LogEvent event2(tagId, bucketStartTimeNs + 2); - event2.init(); - LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1); - event3.init(); - LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3); - event4.init(); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId); + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId); FieldMatcher dimensions; + DurationMetricProducer durationProducer( - kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown}, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); durationProducer.mCondition = ConditionState::kFalse; EXPECT_FALSE(durationProducer.mCondition); @@ -125,17 +154,17 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); durationProducer.onMatchedLogEvent(1 /* start index*/, event3); durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(1UL, buckets2.size()); + ASSERT_EQ(1UL, buckets2.size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); EXPECT_EQ(1LL, buckets2[0].mDuration); @@ -152,19 +181,21 @@ TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) { metric.set_aggregation_type(DurationMetric_AggregationType_SUM); int tagId = 1; - LogEvent event1(tagId, bucketStartTimeNs + 1); - event1.init(); - LogEvent event2(tagId, bucketStartTimeNs + 2); - event2.init(); - LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1); - event3.init(); - LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3); - event4.init(); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, bucketStartTimeNs + 1, tagId); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, bucketStartTimeNs + 2, tagId); + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId); + LogEvent event4(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId); FieldMatcher dimensions; + DurationMetricProducer durationProducer( - kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown}, + 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, + wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition); EXPECT_FALSE(durationProducer.isConditionSliced()); @@ -172,21 +203,21 @@ TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) { durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); - EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); durationProducer.onMatchedLogEvent(1 /* start index*/, event3); durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); - EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, durationProducer.mPastBuckets.size()); const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(1UL, buckets2.size()); + ASSERT_EQ(1UL, buckets2.size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); EXPECT_EQ(1LL, buckets2[0].mDuration); } -TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) { /** * The duration starts from the first bucket, through the two partial buckets (10-70sec), * another bucket, and ends at the beginning of the next full bucket. @@ -198,10 +229,6 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { */ int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - int tagId = 1; DurationMetric metric; @@ -210,40 +237,53 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { metric.set_aggregation_type(DurationMetric_AggregationType_SUM); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); FieldMatcher dimensions; - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); - LogEvent start_event(tagId, startTimeNs); - start_event.init(); - durationProducer.onMatchedLogEvent(1 /* start index*/, start_event); - EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {}, + 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, + dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); std::vector<DurationBucket> buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - LogEvent end_event(tagId, endTimeNs); - end_event.init(); - durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event); + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(3UL, buckets.size()); - EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs); + ASSERT_EQ(3UL, buckets.size()); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); EXPECT_EQ(bucketSizeNs, buckets[2].mDuration); } -TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) { /** * Expected buckets (start at 11s, upgrade at 75s, end at 135s): * - [10,70]: 59 secs @@ -252,10 +292,6 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { */ int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - int tagId = 1; DurationMetric metric; @@ -264,47 +300,57 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { metric.set_aggregation_type(DurationMetric_AggregationType_SUM); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); FieldMatcher dimensions; - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); - LogEvent start_event(tagId, startTimeNs); - start_event.init(); - durationProducer.onMatchedLogEvent(1 /* start index*/, start_event); - EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {}, + 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, + dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); std::vector<DurationBucket> buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); - EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs); + EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - LogEvent end_event(tagId, endTimeNs); - end_event.init(); - durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event); + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(3UL, buckets.size()); - EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs); + ASSERT_EQ(3UL, buckets.size()); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs, + buckets[2].mDuration); } -TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) { sp<AlarmMonitor> alarmMonitor; int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1; - int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; - int tagId = 1; // Setup metric with alert. @@ -318,117 +364,145 @@ TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); FieldMatcher dimensions; - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + + DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {}, + 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, + dimensions, bucketStartTimeNs, bucketStartTimeNs); sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor); EXPECT_TRUE(anomalyTracker != nullptr); - LogEvent start_event(tagId, startTimeNs); - start_event.init(); - durationProducer.onMatchedLogEvent(1 /* start index*/, start_event); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - LogEvent end_event(tagId, endTimeNs); - end_event.init(); - durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event); + int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); } -TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1; - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - int tagId = 1; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - LogEvent event1(tagId, startTimeNs); - event1.write("111"); // uid - event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); FieldMatcher dimensions; - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); - LogEvent start_event(tagId, startTimeNs); - start_event.init(); - durationProducer.onMatchedLogEvent(1 /* start index*/, start_event); - EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {}, + 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, + dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. - LogEvent end_event(tagId, endTimeNs); - end_event.init(); - durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event); - EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); std::vector<DurationBucket> buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(1UL, buckets.size()); + ASSERT_EQ(1UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); } -TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1; - int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; - int tagId = 1; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); - LogEvent event1(tagId, startTimeNs); - event1.write("111"); // uid - event1.init(); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); FieldMatcher dimensions; - DurationMetricProducer durationProducer( - kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); - LogEvent start_event(tagId, startTimeNs); - start_event.init(); - durationProducer.onMatchedLogEvent(1 /* start index*/, start_event); - EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); + DurationMetricProducer durationProducer(kConfigKey, metric, -1 /* no condition */, {}, + 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, + dimensions, bucketStartTimeNs, bucketStartTimeNs); + + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); + durationProducer.onMatchedLogEvent(1 /* start index*/, event1); + ASSERT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); // Stop occurs in the same partial bucket as created for the app upgrade. - LogEvent end_event(tagId, endTimeNs); - end_event.init(); - durationProducer.onMatchedLogEvent(2 /* stop index*/, end_event); - EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); + durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); std::vector<DurationBucket> buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; - EXPECT_EQ(1UL, buckets.size()); - EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs); + ASSERT_EQ(1UL, buckets.size()); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); } diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index d2fd95c818cf..dfbb9da568b0 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -13,14 +13,17 @@ // limitations under the License. #include "src/metrics/EventMetricProducer.h" -#include "metrics_test_helper.h" -#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <stdio.h> + #include <vector> +#include "metrics_test_helper.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + using namespace testing; using android::sp; using std::set; @@ -35,6 +38,17 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); +namespace { +void makeLogEvent(LogEvent* logEvent, int32_t atomId, int64_t timestampNs, string str) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeString(statsEvent, str.c_str()); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} +} // anonymous namespace + TEST(EventMetricProducerTest, TestNoCondition) { int64_t bucketStartTimeNs = 10000000000; int64_t eventStartTimeNs = bucketStartTimeNs + 1; @@ -43,19 +57,31 @@ TEST(EventMetricProducerTest, TestNoCondition) { EventMetric metric; metric.set_id(1); - LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1); - LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 2); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - bucketStartTimeNs); + EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, bucketStartTimeNs); eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - // TODO(b/110561136): get the report and check the content after the ProtoOutputStream change - // is done eventProducer.onDumpReport(); + // Check dump report content. + ProtoOutputStream output; + std::set<string> strSet; + eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, + true /*erase data*/, FAST, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_event_metrics()); + ASSERT_EQ(2, report.event_metrics().data_size()); + EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos()); + EXPECT_EQ(bucketStartTimeNs + 2, report.event_metrics().data(1).elapsed_timestamp_nanos()); } TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) { @@ -67,12 +93,16 @@ TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) { metric.set_id(1); metric.set_condition(StringToId("SCREEN_ON")); - LogEvent event1(1, bucketStartTimeNs + 1); - LogEvent event2(1, bucketStartTimeNs + 10); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs); + EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, bucketStartTimeNs); eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs); eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); @@ -81,8 +111,16 @@ TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) { eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - // TODO: get the report and check the content after the ProtoOutputStream change is done. - // eventProducer.onDumpReport(); + // Check dump report content. + ProtoOutputStream output; + std::set<string> strSet; + eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, + true /*erase data*/, FAST, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_event_metrics()); + ASSERT_EQ(1, report.event_metrics().data_size()); + EXPECT_EQ(bucketStartTimeNs + 1, report.event_metrics().data(0).elapsed_timestamp_nanos()); } TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { @@ -100,30 +138,40 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); - LogEvent event1(tagId, bucketStartTimeNs + 1); - EXPECT_TRUE(event1.write("111")); - event1.init(); + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, 1 /*tagId*/, bucketStartTimeNs + 1, "111"); ConditionKey key1; - key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "111")}; + key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = { + getMockedDimensionKey(conditionTagId, 2, "111")}; - LogEvent event2(tagId, bucketStartTimeNs + 10); - EXPECT_TRUE(event2.write("222")); - event2.init(); + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10, "222"); ConditionKey key2; - key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")}; + key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = { + getMockedDimensionKey(conditionTagId, 2, "222")}; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse)); - - EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue)); + // Condition is false for first event. + EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse)); + // Condition is true for second event. + EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue)); - EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs); + EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, bucketStartTimeNs); eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1); eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2); - // TODO: get the report and check the content after the ProtoOutputStream change is done. - // eventProducer.onDumpReport(); + // Check dump report content. + ProtoOutputStream output; + std::set<string> strSet; + eventProducer.onDumpReport(bucketStartTimeNs + 20, true /*include current partial bucket*/, + true /*erase data*/, FAST, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_event_metrics()); + ASSERT_EQ(1, report.event_metrics().data_size()); + EXPECT_EQ(bucketStartTimeNs + 10, report.event_metrics().data(0).elapsed_timestamp_nanos()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index b9553a8fded8..5997bedcdf2d 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -12,19 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "src/matchers/SimpleLogMatchingTracker.h" #include "src/metrics/GaugeMetricProducer.h" -#include "src/stats_log_util.h" -#include "logd/LogEvent.h" -#include "metrics_test_helper.h" -#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <math.h> #include <stdio.h> + #include <vector> +#include "logd/LogEvent.h" +#include "metrics_test_helper.h" +#include "src/matchers/SimpleLogMatchingTracker.h" +#include "src/metrics/MetricProducer.h" +#include "src/stats_log_util.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + using namespace testing; using android::sp; using std::set; @@ -38,6 +42,8 @@ namespace android { namespace os { namespace statsd { +namespace { + const ConfigKey kConfigKey(0, 12345); const int tagId = 1; const int64_t metricId = 123; @@ -48,7 +54,30 @@ const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000L const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; -const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; +const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + +shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t value1, string str1, + int32_t value2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, value1); + AStatsEvent_writeString(statsEvent, str1.c_str()); + AStatsEvent_writeInt32(statsEvent, value2); + + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} +} // anonymous namespace + +// Setup for parameterized tests. +class GaugeMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(GaugeMetricProducerTest_PartialBucket, + GaugeMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); /* * Tests that the first bucket works correctly @@ -75,13 +104,11 @@ TEST(GaugeMetricProducerTest, TestFirstBucket) { // statsd started long ago. // The metric starts in the middle of the bucket - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, - -1, -1, tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, - pullerManager); + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, -1, -1, + tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager); gaugeProducer.prepareFirstBucket(); - EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs); EXPECT_EQ(10, gaugeProducer.mCurrentBucketNum); EXPECT_EQ(660000000005, gaugeProducer.getCurrentBucketEndTimeNs()); @@ -103,60 +130,49 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(3); - event->write("some value"); - event->write(11); - event->init(); - data->push_back(event); + data->push_back(makeLogEvent(tagId, eventTimeNs + 10, 3, "some value", 11)); return true; })); - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, - tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); - + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1, + tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(10); - event->write("some value"); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(makeLogEvent(tagId, bucket2StartTimeNs + 1, 10, "some value", 11)); gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); EXPECT_EQ(10, it->mValue.int_value); it++; EXPECT_EQ(11, it->mValue.int_value); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms - .front().mFields->begin()->mValue.int_value); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin() + ->second.back() + .mGaugeAtoms.front() + .mFields->begin() + ->mValue.int_value); allData.clear(); - std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10); - event2->write(24); - event2->write("some value"); - event2->write(25); - event2->init(); - allData.push_back(event2); + allData.push_back(makeLogEvent(tagId, bucket3StartTimeNs + 10, 24, "some value", 25)); gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); EXPECT_EQ(24, it->mValue.int_value); @@ -164,8 +180,8 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(INT, it->mValue.getType()); EXPECT_EQ(25, it->mValue.int_value); // One dimension. - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); EXPECT_EQ(10L, it->mValue.int_value); @@ -174,10 +190,10 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(11L, it->mValue.int_value); gaugeProducer.flushIfNeededLocked(bucket4StartTimeNs); - EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); // One dimension. - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.size()); it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); EXPECT_EQ(24L, it->mValue.int_value); @@ -186,7 +202,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(25L, it->mValue.int_value); } -TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { +TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) { sp<AlarmMonitor> alarmMonitor; GaugeMetric metric; metric.set_id(metricId); @@ -204,11 +220,12 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, -1 /* -1 means no pulling */, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); @@ -216,58 +233,64 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor); EXPECT_TRUE(anomalyTracker != nullptr); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); - gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + switch (GetParam()) { + case APP_UPGRADE: + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); // Partial buckets are not sent to anomaly tracker. EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); // Create an event in the same partial bucket. - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC); - event2->write(1); - event2->write(10); - event2->init(); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ((int64_t)partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); // Partial buckets are not sent to anomaly tracker. EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); // Next event should trigger creation of new bucket and send previous full bucket to anomaly // tracker. - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC); - event3->write(1); - event3->write(10); - event3->init(); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(1L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ((int64_t)bucketStartTimeNs + bucketSizeNs, gaugeProducer.mCurrentBucketStartTimeNs); EXPECT_EQ(1, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); // Next event should trigger creation of new bucket. - shared_ptr<LogEvent> event4 = - make_shared<LogEvent>(tagId, bucketStartTimeNs + 125 * NS_PER_SEC); - event4->write(1); - event4->write(10); - event4->init(); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 125 * NS_PER_SEC, 1, 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); EXPECT_EQ(2L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(3UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); } -TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { +TEST_P(GaugeMetricProducerTest_PartialBucket, TestPulled) { GaugeMetric metric; metric.set_id(metricId); metric.set_bucket(ONE_MINUTE); @@ -284,59 +307,59 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({new SimpleLogMatchingTracker( atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); - sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) .WillOnce(Return(false)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventUpgradeTimeNs); - event->write("some value"); - event->write(2); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 2)); return true; })); - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1, + tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write("some value"); - event->write(1); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() ->mValue.int_value); - gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + switch (GetParam()) { + case APP_UPGRADE: + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() ->mValue.int_value); allData.clear(); - event = make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 1); - event->write("some value"); - event->write(3); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 1, 3)); gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() @@ -358,38 +381,35 @@ TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false)); - - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, - tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(false)); + + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1, + tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write("some value"); - event->write(1); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() ->mValue.int_value); - gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); - EXPECT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + ASSERT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); EXPECT_EQ(bucketStartTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() @@ -411,51 +431,48 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + + int64_t conditionChangeNs = bucketStartTimeNs + 8; sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, conditionChangeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write("some value"); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs + 10, 100)); return true; })); - GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, - logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, -1, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); - gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + gaugeProducer.onConditionChanged(true, conditionChangeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(100, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() ->mValue.int_value); - EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write("some value"); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() ->mValue.int_value); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin() ->second.back() .mGaugeAtoms.front() @@ -464,8 +481,8 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition) { gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10); gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin() ->second.back() .mGaugeAtoms.front() @@ -485,75 +502,61 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { dim->set_field(tagId); dim->add_child()->set_field(1); - dim = metric.mutable_dimensions_in_condition(); - dim->set_field(conditionTag); - dim->add_child()->set_field(1); - UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*wizard, query(_, _, _, _, _, _)) + EXPECT_CALL(*wizard, query(_, _, _)) .WillRepeatedly( Invoke([](const int conditionIndex, const ConditionKey& conditionParameters, - const vector<Matcher>& dimensionFields, const bool isSubsetDim, - const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet) { - dimensionKeySet->clear(); + const bool isPartialLink) { int pos[] = {1, 0, 0}; Field f(conditionTag, pos, 0); HashableDimensionKey key; key.mutableValues()->emplace_back(f, Value((int32_t)1000000)); - dimensionKeySet->insert(key); return ConditionState::kTrue; })); + int64_t sliceConditionChangeNs = bucketStartTimeNs + 8; + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, sliceConditionChangeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(1000); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs + 10, 1000, 100)); return true; })); - GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, - logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/, + {ConditionState::kUnknown}, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, -1, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); - gaugeProducer.onSlicedConditionMayChange(true, bucketStartTimeNs + 8); + gaugeProducer.onSlicedConditionMayChange(true, sliceConditionChangeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); const auto& key = gaugeProducer.mCurrentSlicedBucket->begin()->first; - EXPECT_EQ(1UL, key.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1UL, key.getDimensionKeyInWhat().getValues().size()); EXPECT_EQ(1000, key.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(1UL, key.getDimensionKeyInCondition().getValues().size()); - EXPECT_EQ(1000000, key.getDimensionKeyInCondition().getValues()[0].mValue.int_value); - - EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(0UL, gaugeProducer.mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(1000); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1000, 110)); gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); } TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { @@ -561,9 +564,10 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false)); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(false)); GaugeMetric metric; metric.set_id(metricId); @@ -576,13 +580,13 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, - tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, -1, + tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); Alert alert; @@ -595,13 +599,11 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor); int tagId = 1; - std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event1->write("some value"); - event1->write(13); - event1->init(); - - gaugeProducer.onDataPulled({event1}, /** succeed */ true, bucketStartTimeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 13)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() @@ -609,13 +611,12 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); std::shared_ptr<LogEvent> event2 = - std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 20); - event2->write("some value"); - event2->write(15); - event2->init(); + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + bucketSizeNs + 20, 15); - gaugeProducer.onDataPulled({event2}, /** succeed */ true, bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + allData.clear(); + allData.push_back(event2); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + bucketSizeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() @@ -623,14 +624,11 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC) + refPeriodSec); - std::shared_ptr<LogEvent> event3 = - std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10); - event3->write("some value"); - event3->write(26); - event3->init(); - - gaugeProducer.onDataPulled({event3}, /** succeed */ true, bucket2StartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + allData.clear(); + allData.push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 10, 26)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 2 * bucketSizeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(26L, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() .mFields->begin() @@ -638,13 +636,11 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), std::ceil(1.0 * event2->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); - // The event4 does not have the gauge field. Thus the current bucket value is 0. - std::shared_ptr<LogEvent> event4 = - std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10); - event4->write("some value"); - event4->init(); - gaugeProducer.onDataPulled({event4}, /** succeed */ true, bucketStartTimeNs + 3 * bucketSizeNs); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + // This event does not have the gauge field. Thus the current bucket value is 0. + allData.clear(); + allData.push_back(CreateNoValuesLogEvent(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10)); + gaugeProducer.onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 3 * bucketSizeNs); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->empty()); } @@ -664,51 +660,49 @@ TEST(GaugeMetricProducerTest, TestPullOnTrigger) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(4); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 4)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event->write(5); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 5)); return true; })) .WillOnce(Return(true)); int triggerId = 5; - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, - tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, + triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); - vector<shared_ptr<LogEvent>> allData; + ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); - LogEvent trigger(triggerId, bucketStartTimeNs + 10); - trigger.init(); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - trigger.setElapsedTimestampNs(bucketStartTimeNs + 20); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size()); + LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + + ASSERT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.size()); EXPECT_EQ(4, gaugeProducer.mPastBuckets.begin() ->second.back() .mGaugeAtoms[0] @@ -738,75 +732,131 @@ TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); - sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({ - new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3); - event->write(3); - event->write(4); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 3, 4)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(4); - event->write(5); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 5)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event->write(4); - event->write(6); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, eventTimeNs, 4, 6)); return true; })) .WillOnce(Return(true)); int triggerId = 5; - GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, - tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, + triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); gaugeProducer.prepareFirstBucket(); - vector<shared_ptr<LogEvent>> allData; - - LogEvent trigger(triggerId, bucketStartTimeNs + 3); - trigger.init(); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - trigger.setElapsedTimestampNs(bucketStartTimeNs + 10); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - trigger.setElapsedTimestampNs(bucketStartTimeNs + 20); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - EXPECT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); - trigger.setElapsedTimestampNs(bucket2StartTimeNs + 1); - gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger); - - EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.size()); + LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); + triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 10); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->size()); + ASSERT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + ASSERT_EQ(2UL, gaugeProducer.mCurrentSlicedBucket->begin()->second.size()); + triggerEvent.setElapsedTimestampNs(bucket2StartTimeNs + 1); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + + ASSERT_EQ(2UL, gaugeProducer.mPastBuckets.size()); auto bucketIt = gaugeProducer.mPastBuckets.begin(); - EXPECT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size()); + ASSERT_EQ(1UL, bucketIt->second.back().mGaugeAtoms.size()); EXPECT_EQ(3, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value); EXPECT_EQ(4, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value); bucketIt++; - EXPECT_EQ(2UL, bucketIt->second.back().mGaugeAtoms.size()); + ASSERT_EQ(2UL, bucketIt->second.back().mGaugeAtoms.size()); EXPECT_EQ(4, bucketIt->first.getDimensionKeyInWhat().getValues().begin()->mValue.int_value); EXPECT_EQ(5, bucketIt->second.back().mGaugeAtoms[0].mFields->begin()->mValue.int_value); EXPECT_EQ(6, bucketIt->second.back().mGaugeAtoms[1].mFields->begin()->mValue.int_value); } +/* + * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size + * is smaller than the "min_bucket_size_nanos" specified in the metric config. + */ +TEST(GaugeMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(FIVE_MINUTES); + metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES); + metric.set_min_bucket_size_nanos(10000000000); // 10 seconds + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 3, _, _)) + // Bucket start. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, eventTimeNs, 10)); + return true; + })); + + int triggerId = 5; + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, + triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); + gaugeProducer.prepareFirstBucket(); + + LogEvent triggerEvent(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 3); + gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + gaugeProducer.onDumpReport(bucketStartTimeNs + 9000000, true /* include recent buckets */, true, + FAST /* dump_latency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_gauge_metrics()); + ASSERT_EQ(0, report.gauge_metrics().data_size()); + ASSERT_EQ(1, report.gauge_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.gauge_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), + report.gauge_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.gauge_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.gauge_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), dropEvent.drop_time_millis()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp index bf047520cc77..fda3daaa56aa 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -52,7 +52,6 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); @@ -63,9 +62,8 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey()); // Event starts again. This would not change anything as it already starts. @@ -79,7 +77,7 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(20LL, buckets[eventKey][0].mDuration); } @@ -88,7 +86,6 @@ TEST(MaxDurationTrackerTest, TestStopAll) { const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -99,9 +96,8 @@ TEST(MaxDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey()); @@ -114,7 +110,7 @@ TEST(MaxDurationTrackerTest, TestStopAll) { tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); @@ -124,7 +120,6 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -135,9 +130,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); // The event starts. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -155,7 +149,7 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { EXPECT_TRUE(buckets.find(eventKey) == buckets.end()); tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, &buckets); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs); @@ -165,7 +159,6 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -176,9 +169,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { int64_t bucketNum = 0; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - false, false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); // 2 starts tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey()); @@ -195,14 +187,13 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { bucketStartTimeNs + (2 * bucketSizeNs) + 5, false); tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration); } TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { const HashableDimensionKey conditionDimKey = key1; - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -223,9 +214,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC; int64_t metricId = 1; - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true, - false, {}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + 0, bucketStartTimeNs, bucketSizeNs, true, false, {}); EXPECT_TRUE(tracker.mAnomalyTrackers.empty()); tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); @@ -233,20 +223,19 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { tracker.noteConditionChanged(key1, false, conditionStops1); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); - EXPECT_EQ(0U, buckets.size()); + ASSERT_EQ(0U, buckets.size()); tracker.noteConditionChanged(key1, true, conditionStarts2); tracker.noteConditionChanged(key1, false, conditionStops2); tracker.noteStop(key1, eventStopTimeNs, false); tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1, &buckets); - EXPECT_EQ(1U, buckets.size()); + ASSERT_EQ(1U, buckets.size()); vector<DurationBucket> item = buckets.begin()->second; - EXPECT_EQ(1UL, item.size()); + ASSERT_EQ(1UL, item.size()); EXPECT_EQ((int64_t)(13LL * NS_PER_SEC), item[0].mDuration); } TEST(MaxDurationTrackerTest, TestAnomalyDetection) { - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -273,9 +262,9 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1); sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second; @@ -283,11 +272,11 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { // Remove the anomaly alarm when the duration is no longer fully met. tracker.noteConditionChanged(key1, false, eventStartTimeNs + 15 * NS_PER_SEC); - EXPECT_EQ(0U, anomalyTracker->mAlarms.size()); + ASSERT_EQ(0U, anomalyTracker->mAlarms.size()); // Since the condition was off for 10 seconds, the anomaly should trigger 10 sec later. tracker.noteConditionChanged(key1, true, eventStartTimeNs + 25 * NS_PER_SEC); - EXPECT_EQ(1U, anomalyTracker->mAlarms.size()); + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(63ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); } @@ -295,7 +284,6 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { // This tests that we correctly compute the predicted time of an anomaly assuming that the current // state continues forward as-is. TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -333,16 +321,16 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1); tracker.noteConditionChanged(key1, true, conditionStarts1); tracker.noteConditionChanged(key1, false, conditionStops1); tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); // Condition is on already. tracker.noteConditionChanged(key1, true, conditionStarts2); - EXPECT_EQ(1U, anomalyTracker->mAlarms.size()); + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); auto alarm = anomalyTracker->mAlarms.begin()->second; int64_t anomalyFireTimeSec = alarm->timestampSec; EXPECT_EQ(conditionStarts2 + 36 * NS_PER_SEC, @@ -353,7 +341,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { // gets correctly taken into account in future predictAnomalyTimestampNs calculations. std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm}); anomalyTracker->informAlarmsFired(anomalyFireTimeSec * NS_PER_SEC, firedAlarms); - EXPECT_EQ(0u, anomalyTracker->mAlarms.size()); + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); int64_t refractoryPeriodEndsSec = anomalyFireTimeSec + refPeriodSec; EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), refractoryPeriodEndsSec); @@ -364,7 +352,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { tracker.noteStop(key2, eventStopTimeNs, false); tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1); // Anomaly is ongoing, but we're still in the refractory period. - EXPECT_EQ(1U, anomalyTracker->mAlarms.size()); + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ(refractoryPeriodEndsSec, (long long)(alarm->timestampSec)); @@ -381,7 +369,6 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { // Suppose A starts, then B starts, and then A stops. We still need to set an anomaly based on the // elapsed duration of B. TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey conditionKey1; @@ -416,14 +403,14 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, - true, false, {anomalyTracker}); + MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1); tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2); tracker.noteStop(key1, eventStopTimeNs1, false); - EXPECT_EQ(1U, anomalyTracker->mAlarms.size()); + ASSERT_EQ(1U, anomalyTracker->mAlarms.size()); auto alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ(eventStopTimeNs1 + 35 * NS_PER_SEC, (unsigned long long)(alarm->timestampSec * NS_PER_SEC)); @@ -434,4 +421,4 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { } // namespace android #else GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif
\ No newline at end of file +#endif diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp index 7c2b4236add8..1d6f7de5e7e3 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -51,7 +51,6 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -62,9 +61,9 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -75,7 +74,7 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(durationTimeNs, buckets[eventKey][0].mDuration); } @@ -84,7 +83,6 @@ TEST(OringDurationTrackerTest, TestDurationNested) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -94,9 +92,8 @@ TEST(OringDurationTrackerTest, TestDurationNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -106,7 +103,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); } @@ -117,7 +114,6 @@ TEST(OringDurationTrackerTest, TestStopAll) { {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -127,9 +123,8 @@ TEST(OringDurationTrackerTest, TestStopAll) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl @@ -138,7 +133,7 @@ TEST(OringDurationTrackerTest, TestStopAll) { tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration); } @@ -147,7 +142,6 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -158,9 +152,8 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime); @@ -168,7 +161,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey()); EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime); - EXPECT_EQ(2u, buckets[eventKey].size()); + ASSERT_EQ(2u, buckets[eventKey].size()); EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); @@ -176,7 +169,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false); tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(2u, buckets[eventKey].size()); + ASSERT_EQ(2u, buckets[eventKey].size()); EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration); EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration); } @@ -186,13 +179,12 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -203,9 +195,9 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); @@ -215,7 +207,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(5LL, buckets[eventKey][0].mDuration); } @@ -224,13 +216,12 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) + EXPECT_CALL(*wizard, query(_, key1, _)) .Times(2) .WillOnce(Return(ConditionState::kFalse)) .WillOnce(Return(ConditionState::kTrue)); @@ -243,9 +234,9 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { int64_t eventStartTimeNs = bucketStartTimeNs + 1; int64_t durationTimeNs = 2 * 1000; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); // condition to false; record duration 5n @@ -257,7 +248,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(1005LL, buckets[eventKey][0].mDuration); } @@ -266,13 +257,12 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ConditionKey key1; key1[StringToId("APP_BACKGROUND")] = kConditionKey1; - EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) // #4 + EXPECT_CALL(*wizard, query(_, key1, _)) // #4 .WillOnce(Return(ConditionState::kFalse)); unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets; @@ -282,9 +272,8 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { int64_t bucketNum = 0; int64_t eventStartTimeNs = bucketStartTimeNs + 1; - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1); @@ -297,7 +286,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(15LL, buckets[eventKey][0].mDuration); } @@ -306,7 +295,6 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -324,9 +312,9 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, true, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs, + bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, + {anomalyTracker}); // Nothing in the past bucket. tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey()); @@ -334,7 +322,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs)); tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false); - EXPECT_EQ(0u, buckets[eventKey].size()); + ASSERT_EQ(0u, buckets[eventKey].size()); int64_t event1StartTimeNs = eventStartTimeNs + 10; tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey()); @@ -347,7 +335,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { tracker.noteStop(kEventKey1, event1StopTimeNs, false); EXPECT_TRUE(buckets.find(eventKey) != buckets.end()); - EXPECT_EQ(1u, buckets[eventKey].size()); + ASSERT_EQ(1u, buckets[eventKey].size()); EXPECT_EQ(3LL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10, buckets[eventKey][0].mDuration); @@ -371,7 +359,6 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { } TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) { - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -387,7 +374,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) { sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1, - dimensionInCondition, + true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); @@ -415,7 +402,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { for (int j = 0; j < 3; j++) { int64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC; for (int i = 0; i <= 7; ++i) { - vector<Matcher> dimensionInCondition; + Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -431,9 +418,8 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, - wizard, 1, dimensionInCondition, - true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, + OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, + 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {anomalyTracker}); int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC; @@ -472,7 +458,6 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -491,20 +476,20 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs, - bucketSizeNs, false, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs, + false, false, {anomalyTracker}); tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey()); tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); EXPECT_TRUE(tracker.mStarted.empty()); - EXPECT_EQ(10LL, tracker.mDuration); // 10ns + EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration); // 10ns - EXPECT_EQ(0u, tracker.mStarted.size()); + ASSERT_EQ(0u, tracker.mStarted.size()); tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey()); - EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); EXPECT_EQ((long long)(52ULL * NS_PER_SEC), // (10s + 1s + 1ns + 20ns) - 10ns + 40s, rounded up (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC)); // The alarm is set to fire at 52s, and when it does, an anomaly would be declared. However, @@ -522,7 +507,6 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); - vector<Matcher> dimensionInCondition; Alert alert; alert.set_id(101); alert.set_metric_id(1); @@ -541,34 +525,34 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { sp<AlarmMonitor> alarmMonitor; sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor); - OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition, - true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs, - bucketSizeNs, false, false, {anomalyTracker}); + OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/, + bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false, + false, {anomalyTracker}); - tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 - EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); + tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1 + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later) - EXPECT_EQ(0u, anomalyTracker->mAlarms.size()); + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again - EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); + tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); - tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 - EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); + tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2 + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1 - EXPECT_EQ(1u, anomalyTracker->mAlarms.size()); + ASSERT_EQ(1u, anomalyTracker->mAlarms.size()); alarm = anomalyTracker->mAlarms.begin()->second; EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC)); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U); @@ -576,11 +560,11 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time. std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm}); anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms); - EXPECT_EQ(0u, anomalyTracker->mAlarms.size()); + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec); tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2 - EXPECT_EQ(0u, anomalyTracker->mAlarms.size()); + ASSERT_EQ(0u, anomalyTracker->mAlarms.size()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec); } @@ -589,4 +573,4 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { } // namespace android #else GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif
\ No newline at end of file +#endif diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 2262c76e64f9..5666501d7d51 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -12,21 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "src/matchers/SimpleLogMatchingTracker.h" #include "src/metrics/ValueMetricProducer.h" -#include "src/stats_log_util.h" -#include "metrics_test_helper.h" -#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <math.h> #include <stdio.h> + #include <vector> +#include "metrics_test_helper.h" +#include "src/matchers/SimpleLogMatchingTracker.h" +#include "src/metrics/MetricProducer.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + using namespace testing; using android::sp; -using android::util::ProtoReader; using std::make_shared; using std::set; using std::shared_ptr; @@ -39,6 +41,8 @@ namespace android { namespace os { namespace statsd { +namespace { + const ConfigKey kConfigKey(0, 12345); const int tagId = 1; const int64_t metricId = 123; @@ -56,10 +60,18 @@ double epsilon = 0.001; static void assertPastBucketValuesSingleKey( const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets, const std::initializer_list<int>& expectedValuesList, - const std::initializer_list<int64_t>& expectedDurationNsList) { - std::vector<int> expectedValues(expectedValuesList); - std::vector<int64_t> expectedDurationNs(expectedDurationNsList); + const std::initializer_list<int64_t>& expectedDurationNsList, + const std::initializer_list<int64_t>& expectedStartTimeNsList, + const std::initializer_list<int64_t>& expectedEndTimeNsList) { + vector<int> expectedValues(expectedValuesList); + vector<int64_t> expectedDurationNs(expectedDurationNsList); + vector<int64_t> expectedStartTimeNs(expectedStartTimeNsList); + vector<int64_t> expectedEndTimeNs(expectedEndTimeNsList); + ASSERT_EQ(expectedValues.size(), expectedDurationNs.size()); + ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size()); + ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size()); + if (expectedValues.size() == 0) { ASSERT_EQ(0, mPastBuckets.size()); return; @@ -68,29 +80,48 @@ static void assertPastBucketValuesSingleKey( ASSERT_EQ(1, mPastBuckets.size()); ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size()); - auto buckets = mPastBuckets.begin()->second; + const vector<ValueBucket>& buckets = mPastBuckets.begin()->second; for (int i = 0; i < expectedValues.size(); i++) { EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value) << "Values differ at index " << i; EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs) << "Condition duration value differ at index " << i; + EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs) + << "Start time differs at index " << i; + EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs) + << "End time differs at index " << i; } } +} // anonymous namespace + class ValueMetricProducerTestHelper { +public: + static sp<ValueMetricProducer> createValueProducerNoConditions( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) { + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); - public: - static shared_ptr<LogEvent> createEvent(int64_t eventTimeNs, int64_t value) { - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventTimeNs); - event->write(tagId); - event->write(value); - event->write(value); - event->init(); - return event; + sp<ValueMetricProducer> valueProducer = + new ValueMetricProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer->prepareFirstBucket(); + return valueProducer; } - static sp<ValueMetricProducer> createValueProducerNoConditions( - sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) { + static sp<ValueMetricProducer> createValueProducerWithCondition( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric, + ConditionState conditionAfterFirstBucketPrepared) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); @@ -98,19 +129,24 @@ class ValueMetricProducerTestHelper { new EventMatcherWizard({new SimpleLogMatchingTracker( atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( - kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard, + logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); valueProducer->prepareFirstBucket(); + valueProducer->mCondition = conditionAfterFirstBucketPrepared; return valueProducer; } - static sp<ValueMetricProducer> createValueProducerWithCondition( - sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) { + static sp<ValueMetricProducer> createValueProducerWithState( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric, + vector<int32_t> slicedStateAtoms, + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); @@ -118,15 +154,42 @@ class ValueMetricProducerTestHelper { new EventMatcherWizard({new SimpleLogMatchingTracker( atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); - sp<ValueMetricProducer> valueProducer = - new ValueMetricProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, - eventMatcherWizard, tagId, bucketStartTimeNs, - bucketStartTimeNs, pullerManager); + sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( + kConfigKey, metric, -1 /* no condition */, {}, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager, {}, + {}, slicedStateAtoms, stateGroupMap); valueProducer->prepareFirstBucket(); - valueProducer->mCondition = ConditionState::kFalse; + return valueProducer; + } + + static sp<ValueMetricProducer> createValueProducerWithConditionAndState( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric, + vector<int32_t> slicedStateAtoms, + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap, + ConditionState conditionAfterFirstBucketPrepared) { + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); + + sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( + kConfigKey, metric, 0 /* condition tracker index */, {ConditionState::kUnknown}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, stateGroupMap); + valueProducer->prepareFirstBucket(); + valueProducer->mCondition = conditionAfterFirstBucketPrepared; return valueProducer; } @@ -145,8 +208,27 @@ class ValueMetricProducerTestHelper { metric.set_condition(StringToId("SCREEN_ON")); return metric; } + + static ValueMetric createMetricWithState(string state) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.add_slice_by_state(StringToId(state)); + return metric; + } + + static ValueMetric createMetricWithConditionAndState(string state) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_condition(StringToId("SCREEN_ON")); + metric.add_slice_by_state(StringToId(state)); + return metric; + } }; +// Setup for parameterized tests. +class ValueMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket, + ValueMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); /* * Tests that the first bucket works correctly @@ -166,9 +248,9 @@ TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime) { // statsd started long ago. // The metric starts in the middle of the bucket - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, -1, startTimeBase, - 22, pullerManager); + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, -1, + startTimeBase, 22, pullerManager); valueProducer.prepareFirstBucket(); EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10)); @@ -196,8 +278,8 @@ TEST(ValueMetricProducerTest, TestFirstBucket) { // statsd started long ago. // The metric starts in the middle of the bucket - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, -1, 5, + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, -1, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager); valueProducer.prepareFirstBucket(); @@ -210,16 +292,13 @@ TEST(ValueMetricProducerTest, TestFirstBucket) { * Tests pulled atoms with no conditions */ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })); @@ -228,63 +307,55 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(8, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(23); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(23, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(23, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(12, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(13, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); @@ -293,28 +364,27 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs); } -TEST(ValueMetricProducerTest, TestPartialBucketCreated) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); +TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initialize bucket. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); return true; })) // Partial bucket. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event->write(tagId); - event->write(5); - event->init(); - data->push_back(event); + data->push_back( + CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5)); return true; })); @@ -324,34 +394,32 @@ TEST(ValueMetricProducerTest, TestPartialBucketCreated) { // First bucket ends. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event->write(tagId); - event->write(2); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 2)); valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs); // Partial buckets created in 2nd bucket. - valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer->getCurrentBucketNum()); - // One full bucket and one partial bucket. - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - vector<ValueBucket> buckets = valueProducer->mPastBuckets.begin()->second; - EXPECT_EQ(2UL, buckets.size()); - // Full bucket (2 - 1) - EXPECT_EQ(1, buckets[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, buckets[0].mConditionTrueNs); - // Full bucket (5 - 3) - EXPECT_EQ(3, buckets[1].values[0].long_value); - // partial bucket [bucket2StartTimeNs, bucket2StartTimeNs + 2] - EXPECT_EQ(2, buckets[1].mConditionTrueNs); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3}, + {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}); } /* * Tests pulled atoms with filtering */ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -364,81 +432,67 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(3); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 3, 3)); return true; })); sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( - kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer->prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(3); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 3, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(8, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(4); - event->write(23); - event->init(); - allData.push_back(event); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket3StartTimeNs + 1, 4, 23)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // No new data seen, so data has been cleared. - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(8, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(3); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // the base was reset - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); } @@ -451,61 +505,54 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { metric.set_use_absolute_value_on_reset(true); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true)); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(true)); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(10); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(10, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(26, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); @@ -518,57 +565,50 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false)); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(false)); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(10); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(10, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(26, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); } @@ -581,82 +621,81 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 130)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 1); // Third condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(180); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 180)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:0 start:100 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(110, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(110, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); - EXPECT_EQ(false, curInterval.hasBase); + EXPECT_EQ(false, curBaseInfo.hasBase); valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, bucket3StartTimeNs}); } -TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; @@ -667,42 +706,58 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - - valueProducer.notifyAppUpgrade(bucketStartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); - - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC); - event2->write(1); - event2->write(10); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); + + // Event arrives after the bucket split. + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); // Next value should create a new bucket. - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC); - event3->write(1); - event3->write(10); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20}, + {partialBucketSplitTimeNs - bucketStartTimeNs, + bucket2StartTimeNs - partialBucketSplitTimeNs}, + {bucketStartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket2StartTimeNs}); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); } -TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -712,53 +767,53 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150; + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) .WillOnce(Return(true)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 149); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs}, + {partialBucketSplitTimeNs}); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(150); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(20L, - valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, - {150, bucketSizeNs - 150}); + EXPECT_EQ(2, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { @@ -773,69 +828,72 @@ TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true)); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(true)); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150); + ASSERT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); } -TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to true time. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, + bucket2StartTimeNs - 100); // Condition change to false time. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs - 100); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 120)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - valueProducer->onConditionChanged(false, bucket2StartTimeNs-100); + valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); EXPECT_FALSE(valueProducer->mCondition); - valueProducer->notifyAppUpgrade(bucket2StartTimeNs-50, "ANY.APP", 1, 1); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } // Expect one full buckets already done and starting a partial bucket. - EXPECT_EQ(bucket2StartTimeNs-50, valueProducer->mCurrentBucketStartTimeNs); - EXPECT_EQ(1UL, valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, - {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}); + {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); EXPECT_FALSE(valueProducer->mCondition); } @@ -851,35 +909,36 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(30, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { @@ -894,59 +953,54 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, - eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); + ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); valueProducer.mCondition = ConditionState::kFalse; - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has 1 slice - EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(20, curInterval.value.long_value); - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 30); - event3->write(1); - event3->write(30); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(50, curInterval.value.long_value); valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35); - shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 40); - event4->write(1); - event4->write(40); - event4->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(50, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestAnomalyDetection) { @@ -969,136 +1023,137 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, -1 /*not pulled*/, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, + -1 /*not pulled*/, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); valueProducer.prepareFirstBucket(); sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 1 * NS_PER_SEC, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 2 + NS_PER_SEC, 20); + + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, + bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, 130); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC, 1); - shared_ptr<LogEvent> event1 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1 * NS_PER_SEC); - event1->write(161); - event1->write(10); // value of interest - event1->init(); - shared_ptr<LogEvent> event2 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 + NS_PER_SEC); - event2->write(162); - event2->write(20); // value of interest - event2->init(); - shared_ptr<LogEvent> event3 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC); - event3->write(163); - event3->write(130); // value of interest - event3->init(); - shared_ptr<LogEvent> event4 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC); - event4->write(35); - event4->write(1); // value of interest - event4->init(); - shared_ptr<LogEvent> event5 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC); - event5->write(45); - event5->write(150); // value of interest - event5->init(); - shared_ptr<LogEvent> event6 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC); - event6->write(25); - event6->write(160); // value of interest - event6->init(); + LogEvent event5(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event5, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, 150); + + LogEvent event6(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event6, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC, 160); // Two events in bucket #0. - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // Value sum == 30 <= 130. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // One event in bucket #2. No alarm as bucket #0 is trashed out. - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); // Value sum == 130 <= 130. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // Three events in bucket #3. - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); // Anomaly at event 4 since Value sum == 131 > 130! EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5); + std::ceil(1.0 * event4.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event5); // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + std::ceil(1.0 * event4.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event6); // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event6->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + std::ceil(1.0 * event6.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); } // Test value metric no condition, the pull on bucket boundary come in time and too late TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true)); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(true)); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; // pull 1 allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:true sum:0 start:11 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // pull 2 at correct time allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(23); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // tartUpdated:false sum:12 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(23, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(23, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); // pull 3 come late. // The previous bucket gets closed with error. (Has start value 23, no ending) // Another bucket gets closed with error. (No start, but ending with 36) // The new bucket is back to normal. allData.clear(); - event = make_shared<LogEvent>(tagId, bucket6StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:12 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); + // The 1st bucket is dropped because of no data + // The 3rd bucket is dropped due to multiple buckets being skipped. + ASSERT_EQ(2, valueProducer->mSkippedBuckets.size()); + + EXPECT_EQ(bucketStartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); + ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size()); + EXPECT_EQ(NO_DATA, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); + EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs); + + EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[1].bucketStartTimeNs); + EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].bucketEndTimeNs); + ASSERT_EQ(1, valueProducer->mSkippedBuckets[1].dropEvents.size()); + EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[1].dropEvents[0].reason); + EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].dropEvents[0].dropTimeNs); } /* @@ -1109,56 +1164,58 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) // condition becomes false - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + EXPECT_EQ(false, curBaseInfo.hasBase); // Now the alarm is delivered. // since the condition turned to off before this pull finish, it has no effect vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 110)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -1170,87 +1227,90 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) // condition becomes false - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); return true; })) // condition becomes true again - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 25); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 25); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 25, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:0 start:100 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); // condition changed to true again, before the pull alarm is delivered valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(130, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(130, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); // Now the alarm is delivered, but it is considered late, the data will be used // for the new bucket since it was just pulled. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 50, 140)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(140, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs, 160)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30}, - {bucketSizeNs - 8, bucketSizeNs - 24}); + assertPastBucketValuesSingleKey( + valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24}, + {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateMin) { @@ -1266,36 +1326,35 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateMax) { @@ -1311,38 +1370,34 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(20, curInterval.value.long_value); - valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); */ - /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); */ - /* EXPECT_EQ(20, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); */ + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { @@ -1358,39 +1413,36 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(15); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval; curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(1, curInterval.sampleSize); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(25, curInterval.value.long_value); EXPECT_EQ(2, curInterval.sampleSize); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); EXPECT_TRUE(std::abs(valueProducer.mPastBuckets.begin()->second.back().values[0].double_value - 12.5) < epsilon); @@ -1409,36 +1461,34 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(15); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(25, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { @@ -1455,63 +1505,61 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 15); - event2->write(1); - event2->write(15); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(10, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(5, curInterval.value.long_value); // no change in data. - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event3->write(1); - event3->write(15); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(15, curInterval.base.long_value); + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); - shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 15); - event4->write(1); - event4->write(15); - event4->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(15, curInterval.base.long_value); + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { @@ -1529,85 +1577,84 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->write(20); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 15); - event2->write(1); - event2->write(15); - event2->write(22); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10, 20); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15, 22); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval0 = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; - ValueMetricProducer::Interval curInterval1 = - valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasBase); - EXPECT_EQ(10, curInterval0.base.long_value); - EXPECT_EQ(false, curInterval0.hasValue); - EXPECT_EQ(true, curInterval1.hasBase); - EXPECT_EQ(20, curInterval1.base.long_value); - EXPECT_EQ(false, curInterval1.hasValue); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(20, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasValue); - EXPECT_EQ(5, curInterval0.value.long_value); - EXPECT_EQ(true, curInterval1.hasValue); - EXPECT_EQ(2, curInterval1.value.long_value); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(5, curInterval.value.long_value); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(2, curInterval.value.long_value); // no change in first value field - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event3->write(1); - event3->write(15); - event3->write(25); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasBase); - EXPECT_EQ(15, curInterval0.base.long_value); - EXPECT_EQ(true, curInterval0.hasValue); - EXPECT_EQ(true, curInterval1.hasBase); - EXPECT_EQ(25, curInterval1.base.long_value); - EXPECT_EQ(true, curInterval1.hasValue); - - shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 15); - event4->write(1); - event4->write(15); - event4->write(29); - event4->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasBase); - EXPECT_EQ(15, curInterval0.base.long_value); - EXPECT_EQ(true, curInterval0.hasValue); - EXPECT_EQ(true, curInterval1.hasBase); - EXPECT_EQ(29, curInterval1.base.long_value); - EXPECT_EQ(true, curInterval1.hasValue); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15, 25); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(25, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15, 29); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(29, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size()); EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second[0].values[0].long_value); @@ -1630,47 +1677,38 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { metric.set_use_zero_default_base(true); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(1); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto iter = valueProducer->mCurrentSlicedBucket.begin(); auto& interval1 = iter->second[0]; + auto iterBase = valueProducer->mCurrentBaseInfo.begin(); + auto& baseInfo1 = iterBase->second[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(3, interval1.base.long_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event1->write(2); - event1->write(4); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(11); - event2->init(); - allData.push_back(event1); - allData.push_back(event2); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(11, interval1.base.long_value); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); @@ -1680,15 +1718,23 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { break; } } + auto itBase = valueProducer->mCurrentBaseInfo.begin(); + for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { + if (itBase != iterBase) { + break; + } + } EXPECT_TRUE(it != iter); + EXPECT_TRUE(itBase != iterBase); auto& interval2 = it->second[0]; + auto& baseInfo2 = itBase->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(4, interval2.base.long_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_EQ(4, interval2.value.long_value); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); auto iterator = valueProducer->mPastBuckets.begin(); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); EXPECT_EQ(8, iterator->second[0].values[0].long_value); @@ -1707,107 +1753,100 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { metric.set_use_zero_default_base(true); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(1); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto iter = valueProducer->mCurrentSlicedBucket.begin(); - auto& interval1 = iter->second[0]; - EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(3, interval1.base.long_value); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + const auto& it = valueProducer->mCurrentSlicedBucket.begin(); + ValueMetricProducer::Interval& interval1 = it->second[0]; + ValueMetricProducer::BaseInfo& baseInfo1 = + valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())->second[0]; + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event1->write(2); - event1->write(4); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(11); - event2->init(); - allData.push_back(event1); - allData.push_back(event2); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(11, interval1.base.long_value); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); - auto it = valueProducer->mCurrentSlicedBucket.begin(); - for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { - if (it != iter) { + auto it2 = valueProducer->mCurrentSlicedBucket.begin(); + for (; it2 != valueProducer->mCurrentSlicedBucket.end(); it2++) { + if (it2 != it) { break; } } - EXPECT_TRUE(it != iter); - auto& interval2 = it->second[0]; - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(4, interval2.base.long_value); + EXPECT_TRUE(it2 != it); + ValueMetricProducer::Interval& interval2 = it2->second[0]; + ValueMetricProducer::BaseInfo& baseInfo2 = + valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())->second[0]; + EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_EQ(4, interval2.value.long_value); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); // next pull somehow did not happen, skip to end of bucket 3 allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event1->write(2); - event1->write(5); - event1->init(); - allData.push_back(event1); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(5, interval2.base.long_value); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(5, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); - event1->write(2); - event1->write(13); - event1->init(); - allData.push_back(event1); - event2 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); - event2->write(1); - event2->write(5); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 13)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 1, 5)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - auto it1 = std::next(valueProducer->mCurrentSlicedBucket.begin())->second[0]; - EXPECT_EQ(true, it1.hasBase); - EXPECT_EQ(13, it1.base.long_value); - EXPECT_EQ(false, it1.hasValue); - EXPECT_EQ(8, it1.value.long_value); - auto it2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, it2.hasBase); - EXPECT_EQ(5, it2.base.long_value); - EXPECT_EQ(false, it2.hasValue); - EXPECT_EQ(5, it2.value.long_value); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Get new references now that entries have been deleted from the map + const auto& it3 = valueProducer->mCurrentSlicedBucket.begin(); + const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin()); + ASSERT_EQ(it3->second.size(), 1); + ASSERT_EQ(it4->second.size(), 1); + ValueMetricProducer::Interval& interval3 = it3->second[0]; + ValueMetricProducer::Interval& interval4 = it4->second[0]; + ValueMetricProducer::BaseInfo& baseInfo3 = + valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())->second[0]; + ValueMetricProducer::BaseInfo& baseInfo4 = + valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())->second[0]; + + EXPECT_EQ(true, baseInfo3.hasBase); + EXPECT_EQ(5, baseInfo3.base.long_value); + EXPECT_EQ(false, interval3.hasValue); + EXPECT_EQ(5, interval3.value.long_value); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + + EXPECT_EQ(true, baseInfo4.hasBase); + EXPECT_EQ(13, baseInfo4.base.long_value); + EXPECT_EQ(false, interval4.hasValue); + EXPECT_EQ(8, interval4.value.long_value); + + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); } /* @@ -1819,50 +1858,42 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { metric.mutable_dimensions_in_what()->add_child()->set_field(1); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(1); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto iter = valueProducer->mCurrentSlicedBucket.begin(); auto& interval1 = iter->second[0]; + auto iterBase = valueProducer->mCurrentBaseInfo.begin(); + auto& baseInfo1 = iterBase->second[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(3, interval1.base.long_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); - vector<shared_ptr<LogEvent>> allData; + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event1->write(2); - event1->write(4); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(11); - event2->init(); - allData.push_back(event1); - allData.push_back(event2); - + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(11, interval1.base.long_value); + + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); EXPECT_FALSE(interval1.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); auto it = valueProducer->mCurrentSlicedBucket.begin(); for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { @@ -1870,51 +1901,57 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { break; } } + auto itBase = valueProducer->mCurrentBaseInfo.begin(); + for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { + if (itBase != iterBase) { + break; + } + } EXPECT_TRUE(it != iter); - auto& interval2 = it->second[0]; + EXPECT_TRUE(itBase != iterBase); + auto interval2 = it->second[0]; + auto baseInfo2 = itBase->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(4, interval2.base.long_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); // next pull somehow did not happen, skip to end of bucket 3 allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event1->write(2); - event1->write(5); - event1->init(); - allData.push_back(event1); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - // Only one interval left. One was trimmed. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Only one interval left. One was trimmed. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(5, interval2.base.long_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(5, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); - event1->write(2); - event1->write(14); - event1->init(); - allData.push_back(event1); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(14, interval2.base.long_value); + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(14, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); auto iterator = valueProducer->mPastBuckets.begin(); + EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs); EXPECT_EQ(9, iterator->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); iterator++; + EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs); EXPECT_EQ(8, iterator->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); } @@ -1924,33 +1961,32 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfB sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); // Used by onConditionChanged. - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); vector<shared_ptr<LogEvent>> allData; valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(false, curInterval.hasBase); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -1959,38 +1995,38 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // Condition change to true. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) .WillOnce(Return(false)); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); valueProducer->onConditionChanged(false, bucketStartTimeNs + 20); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(false, curInterval.hasBase); + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -1998,41 +2034,39 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(50); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 50)); return false; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to false. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); // Don't directly set mCondition; the real code never does that. Go through regular code path // to avoid unexpected behaviors. // valueProducer->mCondition = ConditionState::kTrue; valueProducer->onConditionChanged(true, bucketStartTimeNs); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); valueProducer->onConditionChanged(false, bucketStartTimeNs + 1); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -2043,25 +2077,21 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded) { metric.set_max_pull_delay_sec(0); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 120)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); // Max delay is set to 0 so pull will exceed max delay. valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); } TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { @@ -2075,84 +2105,77 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, - eventMatcherWizard, tagId, bucket2StartTimeNs, - bucket2StartTimeNs, pullerManager); + ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucket2StartTimeNs, bucket2StartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); valueProducer.mCondition = ConditionState::kFalse; // Event should be skipped since it is from previous bucket. // Pull should not be called. valueProducer.onConditionChanged(true, bucketStartTimeNs); - EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); } TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->mHasGlobalBase = false; valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); valueProducer->mHasGlobalBase = true; - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) { +/* + * Tests that a bucket is marked invalid when a condition change pull fails. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged .WillOnce(Return(false)) // Second onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kTrue; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); // Bucket start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); // This will fail and should invalidate the whole bucket since we do not have all the data @@ -2162,94 +2185,137 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) { // Bucket end. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(140); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // Contains base from last pull which was successful. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(140, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10, false /* include partial bucket */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit) { +/* + * Tests that a bucket is marked invalid when the guardrail is hit. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); metric.mutable_dimensions_in_what()->set_field(tagId); metric.mutable_dimensions_in_what()->add_child()->set_field(1); metric.set_condition(StringToId("SCREEN_ON")); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 2, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { for (int i = 0; i < 2000; i++) { - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(i); - event->write(i); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); } return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 2); - EXPECT_EQ(true, valueProducer->mCurrentBucketIsInvalid); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, valueProducer->mCurrentBucketIsSkipped); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mSkippedBuckets.size()); + + // Bucket 2 start. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // First bucket added to mSkippedBuckets after flush. + ASSERT_EQ(1UL, valueProducer->mSkippedBuckets.size()); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::DIMENSION_GUARDRAIL_REACHED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) { +/* + * Tests that a bucket is marked invalid when the bucket's initial pull fails. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); return true; })) // Second onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kTrue; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); // Bucket start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs); valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); @@ -2257,105 +2323,131 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) { // Bucket end. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(140); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // Contains base from last pull which was successful. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(140, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed) { +/* + * Tests that a bucket is marked invalid when the bucket's final pull fails + * (i.e. failed pull on bucket boundary). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); return true; })) // Second onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kTrue; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); // Bucket start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - // This will fail and should invalidate the whole bucket since we do not have all the data - // needed to compute the metric value when the screen was on. valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); // Bucket end. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(140); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); - // Last pull failed so based has been reset. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + // Last pull failed so base has been reset. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); } TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Start bucket. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })); @@ -2365,62 +2457,59 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) { // Bucket 2 start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); // Bucket 3 empty. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateNoValuesLogEvent(tagId, bucket3StartTimeNs + 1)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // Data has been trimmed. - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); } TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); // Empty pull. valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -2429,46 +2518,42 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 11); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(2); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 2)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 12); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(5); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 5)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); valueProducer->onConditionChanged(false, bucketStartTimeNs + 11); valueProducer->onConditionChanged(true, bucketStartTimeNs + 12); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); @@ -2476,16 +2561,18 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { vector<shared_ptr<LogEvent>> allData; allData.clear(); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // Data is empty, base should be reset. - EXPECT_EQ(false, curInterval.hasBase); - EXPECT_EQ(5, curInterval.base.long_value); + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(5, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { @@ -2495,165 +2582,149 @@ TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { metric.set_condition(StringToId("SCREEN_ON")); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); // End of bucket vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(2); - event->write(2); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 2)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Key 1 should be reset since in not present in the most pull. - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); auto iterator = valueProducer->mCurrentSlicedBucket.begin(); - EXPECT_EQ(true, iterator->second[0].hasBase); - EXPECT_EQ(2, iterator->second[0].base.long_value); + auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin(); + EXPECT_EQ(true, baseInfoIter->second[0].hasBase); + EXPECT_EQ(2, baseInfoIter->second[0].base.long_value); EXPECT_EQ(false, iterator->second[0].hasValue); iterator++; - EXPECT_EQ(false, iterator->second[0].hasBase); - EXPECT_EQ(1, iterator->second[0].base.long_value); + baseInfoIter++; + EXPECT_EQ(false, baseInfoIter->second[0].hasBase); + EXPECT_EQ(1, baseInfoIter->second[0].base.long_value); EXPECT_EQ(false, iterator->second[0].hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); } -TEST(ValueMetricProducerTest, TestBucketIncludingUnknownConditionIsInvalid) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - metric.set_condition(StringToId("SCREEN_ON")); - - sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - // Second onConditionChanged. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { - data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(2); - event->write(2); - event->init(); - data->push_back(event); - return true; - })); - - sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kUnknown; - - valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 20); - - // End of bucket - vector<shared_ptr<LogEvent>> allData; - allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(4); - event->write(4); - event->init(); - allData.push_back(event); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // Bucket is incomplete so it is mark as invalid, however the base is fine since the last pull - // succeeded. - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); -} - -TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2; + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initialization. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // notifyAppUpgrade. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + bucketSizeNs / 2, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); - valueProducer->notifyAppUpgrade(bucketStartTimeNs + bucketSizeNs / 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size()); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 1, 4)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4)); + // Pull fails and arrives late. valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + ASSERT_EQ(1, valueProducer->mSkippedBuckets.size()); + ASSERT_EQ(2, valueProducer->mSkippedBuckets[0].dropEvents.size()); + EXPECT_EQ(PULL_FAILED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); + EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[1].reason); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); } TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Second onConditionChanged. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); data->clear(); - data->push_back( - ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 10, 5)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5)); return true; })) // Third onConditionChanged. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 10); data->clear(); - data->push_back( - ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 10, 7)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 10, 7)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kUnknown; + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); valueProducer->onConditionChanged(false, bucketStartTimeNs); ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); // End of first bucket vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 1, 4)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 4)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(5, curInterval.base.long_value); + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(5, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { @@ -2665,26 +2736,28 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 30, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Initialization. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })); @@ -2692,148 +2765,160 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 30, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } -TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initialization. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // notifyAppUpgrade. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - data->push_back( - ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 2, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First on condition changed. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // Second on condition changed. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 3)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 12); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(2, curInterval.value.long_value); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 1, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); } +// TODO: b/145705635 fix or delete this test TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First condition change. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // 2nd condition change. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); return true; })) // 3rd condition change. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 3, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 3, 10)); valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs + 3); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); valueProducer->onConditionChanged(false, bucket2StartTimeNs + 8); valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // There was not global base available so all buckets are invalid. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); -} - -static StatsLogReport outputStreamToProto(ProtoOutputStream* proto) { - vector<uint8_t> bytes; - bytes.resize(proto->size()); - size_t pos = 0; - sp<ProtoReader> reader = proto->data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - StatsLogReport report; - report.ParseFromArray(bytes.data(), bytes.size()); - return report; + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); } TEST(ValueMetricProducerTest, TestPullNeededFastDump) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -2843,40 +2928,35 @@ TEST(ValueMetricProducerTest, TestPullNeededFastDump) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Initial pull. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); ProtoOutputStream output; std::set<string> strSet; - valueProducer.onDumpReport(bucketStartTimeNs + 10, - true /* include recent buckets */, true, + valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, FAST, &strSet, &output); StatsLogReport report = outputStreamToProto(&output); // Bucket is invalid since we did not pull when dump report was called. - EXPECT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(0, report.value_metrics().data_size()); } TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -2886,51 +2966,41 @@ TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Initial pull. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(2); - event->write(2); - event->init(); - allData.push_back(event); + allData.push_back(CreateThreeValueLogEvent(tagId, bucket2StartTimeNs + 1, tagId, 2, 2)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); ProtoOutputStream output; std::set<string> strSet; - valueProducer.onDumpReport(bucket4StartTimeNs, - false /* include recent buckets */, true, - FAST, &strSet, &output); + valueProducer.onDumpReport(bucket4StartTimeNs, false /* include recent buckets */, true, FAST, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); // Previous bucket is part of the report. - EXPECT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().data_size()); EXPECT_EQ(0, report.value_metrics().data(0).bucket_info(0).bucket_num()); } TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -2940,46 +3010,40 @@ TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initial pull. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(tagId); - event->write(3); - event->write(3); - event->init(); - data->push_back(event); + data->push_back( + CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10, tagId, 3, 3)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); ProtoOutputStream output; std::set<string> strSet; - valueProducer.onDumpReport(bucketStartTimeNs + 10, - true /* include recent buckets */, true, + valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, &strSet, &output); StatsLogReport report = outputStreamToProto(&output); - EXPECT_EQ(1, report.value_metrics().data_size()); - EXPECT_EQ(1, report.value_metrics().data(0).bucket_info_size()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); } @@ -2992,11 +3056,12 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition) { ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) { @@ -3004,44 +3069,48 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges metric.set_use_diff(false); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 30, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); return true; })) // condition becomes false - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 50, 20)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 20)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); - // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 110)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -3050,29 +3119,31 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) { metric.set_use_diff(false); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 30, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -3082,16 +3153,16 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Condition was always false. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); } TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { @@ -3099,29 +3170,1887 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { metric.set_use_diff(false); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 30, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); return true; })) .WillOnce(Return(false)); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // No buckets, we had a failure. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); +} + +/* + * Test that DUMP_REPORT_REQUESTED dump reason is logged. + * + * For the bucket to be marked invalid during a dump report requested, + * three things must be true: + * - we want to include the current partial bucket + * - we need a pull (metric is pulled and condition is true) + * - the dump latency must be FAST + */ + +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenDumpReportRequested) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 20, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 40, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), dropEvent.drop_time_millis()); +} + +/* + * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late condition + * change event (i.e. the condition change occurs in the wrong bucket). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWrongBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 50, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Bucket boundary pull. + vector<shared_ptr<LogEvent>> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Late condition change event. + valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), dropEvent.drop_time_millis()); +} + +/* + * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late accumulate + * event (i.e. the accumulate events call occurs in the wrong bucket). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenAccumulateEventWrongBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 100); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 100, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Bucket boundary pull. + vector<shared_ptr<LogEvent>> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 20)); + + // Late accumulateEvents event. + valueProducer->accumulateEvents(allData, bucket2StartTimeNs - 100, bucket2StartTimeNs - 100); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); +} + +/* + * Test that CONDITION_UNKNOWN dump reason is logged due to an unknown condition + * when a metric is initialized. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10000); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 100, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that PULL_FAILED dump reason is logged due to a pull failure in + * #pullAndMatchEventsLocked. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenPullFailed) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested, pull fails. + .WillOnce(Return(false)); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that MULTIPLE_BUCKETS_SKIPPED dump reason is logged when a log event + * skips over more than one bucket. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenMultipleBucketsSkipped) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket4StartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1000, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Condition change event that skips forward by three buckets. + valueProducer->onConditionChanged(false, bucket4StartTimeNs + 10); + + int64_t dumpTimeNs = bucket4StartTimeNs + 1000; + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(dumpTimeNs, true /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(2, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::MULTIPLE_BUCKETS_SKIPPED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs + 10), dropEvent.drop_time_millis()); + + // This bucket is skipped because a dumpReport with include current buckets is called. + // This creates a new bucket from bucket4StartTimeNs to dumpTimeNs in which we have no data + // since the condition is false for the entire bucket interval. + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size + * is smaller than the "min_bucket_size_nanos" specified in the metric config. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + metric.set_min_bucket_size_nanos(10000000000); // 10 seconds + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 9000000); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 9000000, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 9000000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that NO_DATA dump reason is logged when a flushed bucket contains no data. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kFalse); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that all buckets are dropped due to condition unknown until the first onConditionChanged. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestConditionUnknownMultipleBuckets) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 15 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 15 * NS_PER_SEC, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Bucket should be dropped because of condition unknown. + int64_t appUpgradeTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; + valueProducer->notifyAppUpgrade(appUpgradeTimeNs); + + // Bucket also dropped due to condition unknown + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 3)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // This bucket is also dropped due to condition unknown. + int64_t conditionChangeTimeNs = bucket2StartTimeNs + 10 * NS_PER_SEC; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; // 15 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(3, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(2).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(2).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(2).drop_event_size()); + + dropEvent = report.value_metrics().skipped(2).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(conditionChangeTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket + * was not flushed in time. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenForceBucketSplitBeforeBucketFlush) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // App Update. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1000); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1000, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition changed event + int64_t conditionChangeTimeNs = bucketStartTimeNs + 10; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // App update event. + int64_t appUpdateTimeNs = bucket2StartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); + auto data = report.value_metrics().data(0); + ASSERT_EQ(0, data.bucket_info(0).bucket_num()); + EXPECT_EQ(5, data.bucket_info(0).values(0).value_long()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test multiple bucket drop events in the same bucket. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestMultipleBucketDropEvents) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that the number of logged bucket drop events is capped at the maximum. + * The maximum is currently 10 and is set in MetricProducer::maxDropEventsReached(). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestMaxBucketDropEvents) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // First condition change event. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + for (int i = 0; i < 2000; i++) { + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); + } + return true; + })) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 220); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 220, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // First condition change event causes guardrail to be reached. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // 2-10 condition change events result in failed pulls. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 30); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 70); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 90); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 100); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 150); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 170); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 190); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 200); + + // Condition change event 11 + valueProducer->onConditionChanged(true, bucketStartTimeNs + 220); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; + // Because we already have 10 dump events in the current bucket, + // this case should not be added to the list of dump events. + valueProducer->onDumpReport(bucketStartTimeNs + 1000, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(10, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 30), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(2); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 50), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(3); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 70), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(4); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 90), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(5); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(6); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 150), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(7); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 170), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(8); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 190), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(9); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 200), dropEvent.drop_time_millis()); +} + +/* + * Test metric with a simple sliced state + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedState) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + return true; + })) + // Screen state change to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::SCREEN_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after screen state change kStateUnknown->ON. + auto screenEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change ON->OFF. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(9, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change OFF->ON. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(21, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(12, it->second[0].value.long_value); + // Value for dimension, state key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + + data = report.value_metrics().data(2); + ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); +} + +/* + * Test metric with sliced state with map + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE_ONOFF"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + return true; + })) + // Screen state change to VR has no pull because it is in the same + // state group as ON. + + // Screen state change to ON has no pull because it is in the same + // state group as VR. + + // Screen state change to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + return true; + })); + + const StateMap& stateMap = + CreateScreenStateOnOffMap(/*screen on id=*/321, /*screen off id=*/123); + const StateMap_StateGroup screenOnGroup = stateMap.group(0); + const StateMap_StateGroup screenOffGroup = stateMap.group(1); + + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + for (auto group : stateMap.group()) { + for (auto value : group.value()) { + stateGroupMap[SCREEN_STATE_ATOM_ID][value] = group.group_id(); + } + } + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::SCREEN_STATE_CHANGED}, stateGroupMap); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, {kStateUnknown}} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after screen state change kStateUnknown->ON. + auto screenEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.long_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change ON->VR. + // Both ON and VR are in the same state group, so the base should not change. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + android::view::DisplayStateEnum::DISPLAY_STATE_VR); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change VR->ON. + // Both ON and VR are in the same state group, so the base should not change. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change VR->OFF. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(21, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOffGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.long_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(16, it->second[0].value.long_value); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id()); + + data = report.value_metrics().data(2); + ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id()); +} + +/* + * Test metric that slices by state with a primary field and has dimensions + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("UID_PROCESS_STATE"); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + + MetricStateLink* stateLink = metric.add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 2 /*uid*/, 7)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1 /*uid*/, 3)); + return true; + })) + // Uid 1 process state change from kStateUnknown -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 1 /*uid*/, 6)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 2 /*uid*/, 8)); + return true; + })) + // Uid 2 process state change from kStateUnknown -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 2 /*uid*/, 9)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 1 /*uid*/, 12)); + return true; + })) + // Uid 1 process state change from Foreground -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 1 /*uid*/, 13)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 2 /*uid*/, 11)); + return true; + })) + // Uid 1 process state change from Background -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 1 /*uid*/, 17)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 2 /*uid */, 15)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 2 /*uid*/, 20)); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 1 /*uid*/, 21)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{uid 1}, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + // Base for dimension key {uid 2} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(7, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{uid 2}, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 1 process state change kStateUnknown -> Foreground. + auto uidProcessEvent = CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(6, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + + // Base for dimension key {uid 2} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(7, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 2 process state change kStateUnknown -> Background. + uidProcessEvent = CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 40, 2 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(6, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + + // Base for dimension key {uid 2} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(9, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Pull at end of first bucket. + vector<shared_ptr<LogEvent>> allData; + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 10)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Buckets flushed after end of first bucket. + // None of the buckets should have a value. + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2}. + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(15, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(10, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* kStateTracker::kUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* kStateTracker::kUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 1 process state change from Foreground -> Background. + uidProcessEvent = CreateUidProcessStateChangedEvent( + bucket2StartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(15, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(13, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 1 process state change Background->Foreground. + uidProcessEvent = CreateUidProcessStateChangedEvent( + bucket2StartTimeNs + 40, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(15, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(17, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Value for key {uid 1, BACKGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(5, report.value_metrics().data_size()); + + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + + data = report.value_metrics().data(2); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long()); + + data = report.value_metrics().data(3); + ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size()); + EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + + data = report.value_metrics().data(4); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size()); + EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long()); + EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); +} + +TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kFalse); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + // Condition is false so we do nothing. + unique_ptr<LogEvent> batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + std::unordered_map<HashableDimensionKey, std::vector<ValueMetricProducer::BaseInfo>>::iterator + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, -1} + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + std::unordered_map<MetricDimensionKey, std::vector<ValueMetricProducer::Interval>>::iterator + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after battery saver mode OFF event. + unique_ptr<LogEvent> batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Pull at end of first bucket. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(11, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + + // Bucket 2 status after condition change to false. + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_FALSE(itBase->second[0].hasBase); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(2, report.value_metrics().data_size()); + + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).values(0).value_long()); + + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(6, data.bucket_info(0).values(0).value_long()); + EXPECT_EQ(4, data.bucket_info(1).values(0).value_long()); +} + +/* + * Test bucket splits when condition is unknown. + */ +TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, + ConditionState::kUnknown); + + // App update event. + int64_t appUpdateTimeNs = bucketStartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); } } // namespace statsd diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.cpp b/cmds/statsd/tests/metrics/metrics_test_helper.cpp index 7b9c0d6ab28e..108df04b45cb 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.cpp +++ b/cmds/statsd/tests/metrics/metrics_test_helper.cpp @@ -26,10 +26,23 @@ HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) { return dimension; } +HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value) { + HashableDimensionKey dimension; + int pos[] = {key, 0, 0}; + dimension.addValue(FieldValue(Field(tagId, pos, 0), Value(value))); + + return dimension; +} + MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) { return MetricDimensionKey(getMockedDimensionKey(tagId, key, value), DEFAULT_DIMENSION_KEY); } +MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value) { + return MetricDimensionKey(DEFAULT_DIMENSION_KEY, + getMockedDimensionKeyLongValue(tagId, key, value)); +} + void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher) { matcher->set_field(tagId); } @@ -41,4 +54,4 @@ void buildSimpleAtomFieldMatcher(const int tagId, const int fieldNum, FieldMatch } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h index 97c107228f9c..eeb38a4644fd 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -26,33 +26,39 @@ namespace statsd { class MockConditionWizard : public ConditionWizard { public: - MOCK_METHOD6(query, + MOCK_METHOD3(query, ConditionState(const int conditionIndex, const ConditionKey& conditionParameters, - const vector<Matcher>& dimensionFields, - const bool isSubsetDim, const bool isPartialLink, - std::unordered_set<HashableDimensionKey>* dimensionKeySet)); + const bool isPartialLink)); }; class MockStatsPullerManager : public StatsPullerManager { public: - MOCK_METHOD4(RegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver, - int64_t nextPulltimeNs, int64_t intervalNs)); - MOCK_METHOD2(UnRegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver)); - MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data)); -}; - -class MockUidMap : public UidMap { - public: - MOCK_CONST_METHOD1(getHostUidOrSelf, int(int uid)); + MOCK_METHOD5(RegisterReceiver, + void(int tagId, const ConfigKey& key, wp<PullDataReceiver> receiver, + int64_t nextPulltimeNs, int64_t intervalNs)); + MOCK_METHOD3(UnRegisterReceiver, + void(int tagId, const ConfigKey& key, wp<PullDataReceiver> receiver)); + MOCK_METHOD5(Pull, bool(const int pullCode, const ConfigKey& key, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool useUids)); + MOCK_METHOD5(Pull, + bool(const int pullCode, const vector<int32_t>& uids, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool useUids)); + MOCK_METHOD2(RegisterPullUidProvider, + void(const ConfigKey& configKey, wp<PullUidProvider> provider)); + MOCK_METHOD2(UnregisterPullUidProvider, + void(const ConfigKey& configKey, wp<PullUidProvider> provider)); }; HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value); MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value); +HashableDimensionKey getMockedDimensionKeyLongValue(int tagId, int key, int64_t value); +MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value); + // Utils to build FieldMatcher proto for simple one-depth atoms. void buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum, FieldMatcher* matcher); void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher); } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp index 73d1fd7850e9..e384b6ac7c84 100644 --- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp +++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp @@ -12,17 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <gtest/gtest.h> +#include "src/shell/ShellSubscriber.h" +#include <gtest/gtest.h> +#include <stdio.h> #include <unistd.h> + +#include <vector> + #include "frameworks/base/cmds/statsd/src/atoms.pb.h" #include "frameworks/base/cmds/statsd/src/shell/shell_config.pb.h" #include "frameworks/base/cmds/statsd/src/shell/shell_data.pb.h" -#include "src/shell/ShellSubscriber.h" +#include "stats_event.h" #include "tests/metrics/metrics_test_helper.h" - -#include <stdio.h> -#include <vector> +#include "tests/statsd_test_util.h" using namespace android::os::statsd; using android::sp; @@ -34,27 +37,6 @@ using testing::StrictMock; #ifdef __ANDROID__ -class MyResultReceiver : public BnResultReceiver { -public: - Mutex mMutex; - Condition mCondition; - bool mHaveResult = false; - int32_t mResult = 0; - - virtual void send(int32_t resultCode) { - AutoMutex _l(mMutex); - mResult = resultCode; - mHaveResult = true; - mCondition.signal(); - } - - int32_t waitForResult() { - AutoMutex _l(mMutex); - mCondition.waitRelative(mMutex, 1000000000); - return mResult; - } -}; - void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap, sp<MockStatsPullerManager> pullerManager, const vector<std::shared_ptr<LogEvent>>& pushedEvents, @@ -67,10 +49,7 @@ void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap, ASSERT_EQ(0, pipe(fds_data)); size_t bufferSize = config.ByteSize(); - // write the config to pipe, first write size of the config - vector<uint8_t> size_buffer(sizeof(bufferSize)); - std::memcpy(size_buffer.data(), &bufferSize, sizeof(bufferSize)); write(fds_config[1], &bufferSize, sizeof(bufferSize)); // then write config itself vector<uint8_t> buffer(bufferSize); @@ -79,11 +58,10 @@ void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap, close(fds_config[1]); sp<ShellSubscriber> shellClient = new ShellSubscriber(uidMap, pullerManager); - sp<MyResultReceiver> resultReceiver = new MyResultReceiver(); // mimic a binder thread that a shell subscriber runs on. it would block. - std::thread reader([&resultReceiver, &fds_config, &fds_data, &shellClient] { - shellClient->startNewSubscription(fds_config[0], fds_data[1], resultReceiver, -1); + std::thread reader([&shellClient, &fds_config, &fds_data] { + shellClient->startNewSubscription(fds_config[0], fds_data[1], /*timeoutSec=*/-1); }); reader.detach(); @@ -108,26 +86,37 @@ void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap, // wait for the data to be written. std::this_thread::sleep_for(100ms); - int expected_data_size = expectedData.ByteSize(); - - // now read from the pipe. firstly read the atom size. - size_t dataSize = 0; - EXPECT_EQ((int)sizeof(dataSize), read(fds_data[0], &dataSize, sizeof(dataSize))); - EXPECT_EQ(expected_data_size, (int)dataSize); - - // then read that much data which is the atom in proto binary format - vector<uint8_t> dataBuffer(dataSize); - EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize)); - - // make sure the received bytes can be parsed to an atom - ShellData receivedAtom; - EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0); + // Because we might receive heartbeats from statsd, consisting of data sizes + // of 0, encapsulate reads within a while loop. + bool readAtom = false; + while (!readAtom) { + // Read the atom size. + size_t dataSize = 0; + read(fds_data[0], &dataSize, sizeof(dataSize)); + if (dataSize == 0) continue; + EXPECT_EQ(expectedData.ByteSize(), int(dataSize)); + + // Read that much data in proto binary format. + vector<uint8_t> dataBuffer(dataSize); + EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize)); + + // Make sure the received bytes can be parsed to an atom. + ShellData receivedAtom; + EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0); + + // Serialize the expected atom to byte array and compare to make sure + // they are the same. + vector<uint8_t> expectedAtomBuffer(expectedData.ByteSize()); + expectedData.SerializeToArray(expectedAtomBuffer.data(), expectedData.ByteSize()); + EXPECT_EQ(expectedAtomBuffer, dataBuffer); + + readAtom = true; + } - // serialze the expected atom to bytes. and compare. to make sure they are the same. - vector<uint8_t> atomBuffer(expected_data_size); - expectedData.SerializeToArray(&atomBuffer[0], expected_data_size); - EXPECT_EQ(atomBuffer, dataBuffer); close(fds_data[0]); + if (reader.joinable()) { + reader.join(); + } } TEST(ShellSubscriberTest, testPushedSubscription) { @@ -136,11 +125,10 @@ TEST(ShellSubscriberTest, testPushedSubscription) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); vector<std::shared_ptr<LogEvent>> pushedList; - std::shared_ptr<LogEvent> event1 = - std::make_shared<LogEvent>(29 /*screen_state_atom_id*/, 1000 /*timestamp*/); - event1->write(::android::view::DisplayStateEnum::DISPLAY_STATE_ON); - event1->init(); - pushedList.push_back(event1); + // Create the LogEvent from an AStatsEvent + std::unique_ptr<LogEvent> logEvent = CreateScreenStateChangedEvent( + 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON); + pushedList.push_back(std::move(logEvent)); // create a simple config to get screen events ShellSubscription config; @@ -183,29 +171,33 @@ ShellSubscription getPulledConfig() { return config; } +shared_ptr<LogEvent> makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 10016); + AStatsEvent_overwriteTimestamp(statsEvent, 1111L); + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeInt64(statsEvent, timeMillis); + + std::shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + } // namespace TEST(ShellSubscriberTest, testPulledSubscription) { sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(10016, _)) - .WillRepeatedly(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + const vector<int32_t> uids = {AID_SYSTEM}; + EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _, _)) + .WillRepeatedly(Invoke([](int tagId, const vector<int32_t>&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, 1111L); - event->write(kUid1); - event->write(kCpuTime1); - event->init(); - data->push_back(event); - // another event - event = make_shared<LogEvent>(tagId, 1111L); - event->write(kUid2); - event->write(kCpuTime2); - event->init(); - data->push_back(event); + data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid1, /*timeMillis=*/kCpuTime1)); + data->push_back(makeCpuActiveTimeAtom(/*uid=*/kUid2, /*timeMillis=*/kCpuTime2)); return true; })); - runShellTest(getPulledConfig(), uidMap, pullerManager, vector<std::shared_ptr<LogEvent>>(), getExpectedShellData()); } diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp new file mode 100644 index 000000000000..6516c1529514 --- /dev/null +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -0,0 +1,571 @@ +/* + * Copyright (C) 2019, 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 "state/StateTracker.h" + +#include <gtest/gtest.h> +#include <private/android_filesystem_config.h> + +#include "state/StateListener.h" +#include "state/StateManager.h" +#include "state/StateTracker.h" +#include "stats_event.h" +#include "tests/statsd_test_util.h" + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +const int32_t timestampNs = 1000; + +/** + * Mock StateListener class for testing. + * Stores primary key and state pairs. + */ +class TestStateListener : public virtual StateListener { +public: + TestStateListener(){}; + + virtual ~TestStateListener(){}; + + struct Update { + Update(const HashableDimensionKey& key, int state) : mKey(key), mState(state){}; + HashableDimensionKey mKey; + int mState; + }; + + std::vector<Update> updates; + + void onStateChanged(const int64_t eventTimeNs, const int32_t atomId, + const HashableDimensionKey& primaryKey, const FieldValue& oldState, + const FieldValue& newState) { + updates.emplace_back(primaryKey, newState.mValue.int_value); + } +}; + +int getStateInt(StateManager& mgr, int atomId, const HashableDimensionKey& queryKey) { + FieldValue output; + mgr.getStateValue(atomId, queryKey, &output); + return output.mValue.int_value; +} + +// START: build event functions. +// Incorrect event - missing fields +std::unique_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, + int state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, 1000); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, packageName.c_str()); + // Missing field 3 - using_alert_window. + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +// Incorrect event - exclusive state has wrong type +std::unique_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::string& packageName) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, 1000); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, packageName.c_str()); + AStatsEvent_writeInt32(statsEvent, true); // using_alert_window + AStatsEvent_writeString(statsEvent, "string"); // exclusive state: string instead of int + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} +// END: build event functions. + +TEST(StateListenerTest, TestStateListenerWeakPointer) { + sp<TestStateListener> listener = new TestStateListener(); + wp<TestStateListener> wListener = listener; + listener = nullptr; // let go of listener + EXPECT_TRUE(wListener.promote() == nullptr); +} + +TEST(StateManagerTest, TestStateManagerGetInstance) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager& mgr = StateManager::getInstance(); + mgr.clear(); + + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); +} + +TEST(StateManagerTest, TestOnLogEvent) { + sp<MockUidMap> uidMap = makeMockUidMapForPackage("com.android.systemui", {10111}); + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.updateLogSources(uidMap); + // Add StateTracker by registering a listener. + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + + // log event using AID_ROOT + std::unique_ptr<LogEvent> event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + mgr.onLogEvent(*event); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); + + // log event using mocked uid + event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF, 10111); + mgr.onLogEvent(*event); + + // check StateTracker was updated by querying for state + queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); + + // log event using non-whitelisted uid + event = CreateScreenStateChangedEvent(timestampNs, + android::view::DisplayStateEnum::DISPLAY_STATE_ON, 10112); + mgr.onLogEvent(*event); + + // check StateTracker was NOT updated by querying for state + queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); + + // log event using AID_SYSTEM + event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON, AID_SYSTEM); + mgr.onLogEvent(*event); + + // check StateTracker was updated by querying for state + queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); +} + +/** + * Test registering listeners to StateTrackers + * + * - StateManager will create a new StateTracker if it doesn't already exist + * and then register the listener to the StateTracker. + * - If a listener is already registered to a StateTracker, it is not added again. + * - StateTrackers are only created for atoms that are state atoms. + */ +TEST(StateTrackerTest, TestRegisterListener) { + sp<TestStateListener> listener1 = new TestStateListener(); + sp<TestStateListener> listener2 = new TestStateListener(); + StateManager mgr; + + // Register listener to non-existing StateTracker + EXPECT_EQ(0, mgr.getStateTrackersCount()); + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Register listener to existing StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Register already registered listener to existing StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(2, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Register listener to non-state atom + mgr.registerListener(util::BATTERY_LEVEL_CHANGED, listener2); + EXPECT_EQ(2, mgr.getStateTrackersCount()); +} + +/** + * Test unregistering listeners from StateTrackers + * + * - StateManager will unregister listeners from a StateTracker only if the + * StateTracker exists and the listener is registered to the StateTracker. + * - Once all listeners are removed from a StateTracker, the StateTracker + * is also removed. + */ +TEST(StateTrackerTest, TestUnregisterListener) { + sp<TestStateListener> listener1 = new TestStateListener(); + sp<TestStateListener> listener2 = new TestStateListener(); + StateManager mgr; + + // Unregister listener from non-existing StateTracker + EXPECT_EQ(0, mgr.getStateTrackersCount()); + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(0, mgr.getStateTrackersCount()); + EXPECT_EQ(-1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Unregister non-registered listener from existing StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Unregister second-to-last listener from StateTracker + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener2); + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::SCREEN_STATE_CHANGED)); + + // Unregister last listener from StateTracker + mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener2); + EXPECT_EQ(0, mgr.getStateTrackersCount()); + EXPECT_EQ(-1, mgr.getListenersCount(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(util::WAKELOCK_STATE_CHANGED, listener); + + std::vector<int> attributionUids1 = {1000}; + std::vector<string> attributionTags1 = {"tag"}; + + std::unique_ptr<LogEvent> event1 = CreateAcquireWakelockEvent(timestampNs, attributionUids1, + attributionTags1, "wakelockName"); + mgr.onLogEvent(*event1); + ASSERT_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::unique_ptr<LogEvent> event2 = CreateAcquireWakelockEvent( + timestampNs + 1000, attributionUids1, attributionTags1, "wakelockName"); + mgr.onLogEvent(*event2); + ASSERT_EQ(0, listener->updates.size()); + + std::unique_ptr<LogEvent> event3 = CreateReleaseWakelockEvent( + timestampNs + 2000, attributionUids1, attributionTags1, "wakelockName"); + mgr.onLogEvent(*event3); + ASSERT_EQ(0, listener->updates.size()); + + std::unique_ptr<LogEvent> event4 = CreateReleaseWakelockEvent( + timestampNs + 3000, attributionUids1, attributionTags1, "wakelockName"); + mgr.onLogEvent(*event4); + ASSERT_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(util::BLE_SCAN_STATE_CHANGED, listener); + + std::vector<int> attributionUids1 = {1000}; + std::vector<string> attributionTags1 = {"tag1"}; + std::vector<int> attributionUids2 = {2000}; + + std::unique_ptr<LogEvent> event1 = + CreateBleScanStateChangedEvent(timestampNs, attributionUids1, attributionTags1, + BleScanStateChanged::ON, false, false, false); + mgr.onLogEvent(*event1); + ASSERT_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); + FieldValue stateFieldValue; + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); + listener->updates.clear(); + + std::unique_ptr<LogEvent> event2 = + CreateBleScanStateChangedEvent(timestampNs + 1000, attributionUids2, attributionTags1, + BleScanStateChanged::ON, false, false, false); + mgr.onLogEvent(*event2); + ASSERT_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); + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::ON, stateFieldValue.mValue.int_value); + listener->updates.clear(); + + std::unique_ptr<LogEvent> event3 = + CreateBleScanStateChangedEvent(timestampNs + 2000, attributionUids2, attributionTags1, + BleScanStateChanged::RESET, false, false, false); + mgr.onLogEvent(*event3); + ASSERT_EQ(2, listener->updates.size()); + for (const TestStateListener::Update& update : listener->updates) { + EXPECT_EQ(BleScanStateChanged::OFF, update.mState); + + mgr.getStateValue(util::BLE_SCAN_STATE_CHANGED, update.mKey, &stateFieldValue); + EXPECT_EQ(BleScanStateChanged::OFF, stateFieldValue.mValue.int_value); + } +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged correctly + * updates listener for states without primary keys. + */ +TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + + // log event + std::unique_ptr<LogEvent> event = CreateScreenStateChangedEvent( + timestampNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + mgr.onLogEvent(*event); + + // check listener was updated + ASSERT_EQ(1, listener1->updates.size()); + EXPECT_EQ(DEFAULT_DIMENSION_KEY, listener1->updates[0].mKey); + EXPECT_EQ(2, listener1->updates[0].mState); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY; + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, queryKey)); +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged correctly + * updates listener for states with one primary key. + */ +TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener1); + + // log event + std::unique_ptr<LogEvent> event = CreateUidProcessStateChangedEvent( + timestampNs, 1000 /*uid*/, android::app::ProcessStateEnum::PROCESS_STATE_TOP); + mgr.onLogEvent(*event); + + // check listener was updated + ASSERT_EQ(1, listener1->updates.size()); + EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1002, listener1->updates[0].mState); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey; + getUidProcessKey(1000 /* uid */, &queryKey); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey)); +} + +TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener1); + + // Log event. + std::vector<int> attributionUids = {1001}; + std::vector<string> attributionTags = {"tag1"}; + + std::unique_ptr<LogEvent> event = CreateAcquireWakelockEvent(timestampNs, attributionUids, + attributionTags, "wakelockName"); + mgr.onLogEvent(*event); + EXPECT_EQ(1, mgr.getStateTrackersCount()); + EXPECT_EQ(1, mgr.getListenersCount(util::WAKELOCK_STATE_CHANGED)); + + // Check listener was updated. + ASSERT_EQ(1, listener1->updates.size()); + ASSERT_EQ(3, listener1->updates[0].mKey.getValues().size()); + 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("wakelockName", listener1->updates[0].mKey.getValues()[2].mValue.str_value); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, listener1->updates[0].mState); + + // Check StateTracker was updated by querying for state. + HashableDimensionKey queryKey; + getPartialWakelockKey(1001 /* uid */, "wakelockName", &queryKey); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey)); + + // No state stored for this query key. + HashableDimensionKey queryKey2; + getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey2)); + + // Partial query fails. + HashableDimensionKey queryKey3; + getPartialWakelockKey(1001 /* uid */, &queryKey3); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey3)); +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged correctly + * updates listener for states with multiple primary keys. + */ +TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1); + + // log event + std::unique_ptr<LogEvent> event = CreateOverlayStateChangedEvent( + timestampNs, 1000 /* uid */, "package1", true /*using_alert_window*/, + OverlayStateChanged::ENTERED); + mgr.onLogEvent(*event); + + // check listener was updated + ASSERT_EQ(1, listener1->updates.size()); + EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value); + EXPECT_EQ(1, listener1->updates[0].mState); + + // check StateTracker was updated by querying for state + HashableDimensionKey queryKey; + getOverlayKey(1000 /* uid */, "package1", &queryKey); + EXPECT_EQ(OverlayStateChanged::ENTERED, + getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey)); +} + +/** + * Test StateManager's onLogEvent and StateListener's onStateChanged + * when there is an error extracting state from log event. Listener is not + * updated of state change. + */ +TEST(StateTrackerTest, TestStateChangeEventError) { + sp<TestStateListener> listener1 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1); + + // log event + std::shared_ptr<LogEvent> event1 = + buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */); + std::shared_ptr<LogEvent> event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2"); + + // check listener was updated + mgr.onLogEvent(*event1); + ASSERT_EQ(0, listener1->updates.size()); + mgr.onLogEvent(*event2); + ASSERT_EQ(0, listener1->updates.size()); +} + +TEST(StateTrackerTest, TestStateQuery) { + sp<TestStateListener> listener1 = new TestStateListener(); + sp<TestStateListener> listener2 = new TestStateListener(); + sp<TestStateListener> listener3 = new TestStateListener(); + sp<TestStateListener> listener4 = new TestStateListener(); + StateManager mgr; + mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1); + mgr.registerListener(util::UID_PROCESS_STATE_CHANGED, listener2); + mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener3); + mgr.registerListener(util::WAKELOCK_STATE_CHANGED, listener4); + + std::unique_ptr<LogEvent> event1 = CreateUidProcessStateChangedEvent( + timestampNs, 1000 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 + std::unique_ptr<LogEvent> event2 = CreateUidProcessStateChangedEvent( + timestampNs + 1000, 1001 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE); // state value: + // 1003 + std::unique_ptr<LogEvent> event3 = CreateUidProcessStateChangedEvent( + timestampNs + 2000, 1002 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT); // state value: 1000 + std::unique_ptr<LogEvent> event4 = CreateUidProcessStateChangedEvent( + timestampNs + 3000, 1001 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002 + std::unique_ptr<LogEvent> event5 = CreateScreenStateChangedEvent( + timestampNs + 4000, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + std::unique_ptr<LogEvent> event6 = CreateOverlayStateChangedEvent( + timestampNs + 5000, 1000 /*uid*/, "package1", true /*using_alert_window*/, + OverlayStateChanged::ENTERED); + std::unique_ptr<LogEvent> event7 = CreateOverlayStateChangedEvent( + timestampNs + 6000, 1000 /*uid*/, "package2", true /*using_alert_window*/, + OverlayStateChanged::EXITED); + + std::vector<int> attributionUids = {1005}; + std::vector<string> attributionTags = {"tag"}; + + std::unique_ptr<LogEvent> event8 = CreateAcquireWakelockEvent( + timestampNs + 7000, attributionUids, attributionTags, "wakelock1"); + std::unique_ptr<LogEvent> event9 = CreateReleaseWakelockEvent( + timestampNs + 8000, attributionUids, attributionTags, "wakelock2"); + + mgr.onLogEvent(*event1); + mgr.onLogEvent(*event2); + mgr.onLogEvent(*event3); + mgr.onLogEvent(*event5); + mgr.onLogEvent(*event5); + mgr.onLogEvent(*event6); + mgr.onLogEvent(*event7); + mgr.onLogEvent(*event8); + mgr.onLogEvent(*event9); + + // Query for UidProcessState of uid 1001 + HashableDimensionKey queryKey1; + getUidProcessKey(1001, &queryKey1); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE, + getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1)); + + // Query for UidProcessState of uid 1004 - not in state map + HashableDimensionKey queryKey2; + getUidProcessKey(1004, &queryKey2); + EXPECT_EQ(-1, getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, + queryKey2)); // default state + + // Query for UidProcessState of uid 1001 - after change in state + mgr.onLogEvent(*event4); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP, + getStateInt(mgr, util::UID_PROCESS_STATE_CHANGED, queryKey1)); + + // Query for ScreenState + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + getStateInt(mgr, util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY)); + + // Query for OverlayState of uid 1000, package name "package2" + HashableDimensionKey queryKey3; + getOverlayKey(1000, "package2", &queryKey3); + EXPECT_EQ(OverlayStateChanged::EXITED, + getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey3)); + + // Query for WakelockState of uid 1005, tag 2 + HashableDimensionKey queryKey4; + getPartialWakelockKey(1005, "wakelock2", &queryKey4); + EXPECT_EQ(WakelockStateChanged::RELEASE, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey4)); + + // Query for WakelockState of uid 1005, tag 1 + HashableDimensionKey queryKey5; + getPartialWakelockKey(1005, "wakelock1", &queryKey5); + EXPECT_EQ(WakelockStateChanged::ACQUIRE, + getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey5)); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 2c4f3c7692c9..cee83725d075 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -14,10 +14,33 @@ #include "statsd_test_util.h" +#include <aidl/android/util/StatsEventParcel.h> +#include "stats_event.h" + +using aidl::android::util::StatsEventParcel; +using std::shared_ptr; + namespace android { namespace os { namespace statsd { +StatsLogReport outputStreamToProto(ProtoOutputStream* proto) { + vector<uint8_t> bytes; + bytes.resize(proto->size()); + size_t pos = 0; + sp<ProtoReader> reader = proto->data(); + + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + StatsLogReport report; + report.ParseFromArray(bytes.data(), bytes.size()); + return report; +} AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { AtomMatcher atom_matcher; @@ -28,7 +51,7 @@ AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { } AtomMatcher CreateTemperatureAtomMatcher() { - return CreateSimpleAtomMatcher("TemperatureMatcher", android::util::TEMPERATURE); + return CreateSimpleAtomMatcher("TemperatureMatcher", util::TEMPERATURE); } AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name, @@ -36,7 +59,7 @@ AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name, AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::SCHEDULED_JOB_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::SCHEDULED_JOB_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(3); // State field. field_value_matcher->set_eq_int(state); @@ -57,7 +80,7 @@ AtomMatcher CreateScreenBrightnessChangedAtomMatcher() { AtomMatcher atom_matcher; atom_matcher.set_id(StringToId("ScreenBrightnessChanged")); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED); + simple_atom_matcher->set_atom_id(util::SCREEN_BRIGHTNESS_CHANGED); return atom_matcher; } @@ -65,7 +88,7 @@ AtomMatcher CreateUidProcessStateChangedAtomMatcher() { AtomMatcher atom_matcher; atom_matcher.set_id(StringToId("UidProcessStateChanged")); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::UID_PROCESS_STATE_CHANGED); return atom_matcher; } @@ -74,7 +97,7 @@ AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::WAKELOCK_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(4); // State field. field_value_matcher->set_eq_int(state); @@ -94,7 +117,7 @@ AtomMatcher CreateBatterySaverModeStateChangedAtomMatcher( AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::BATTERY_SAVER_MODE_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::BATTERY_SAVER_MODE_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(1); // State field. field_value_matcher->set_eq_int(state); @@ -112,20 +135,40 @@ AtomMatcher CreateBatterySaverModeStopAtomMatcher() { "BatterySaverModeStop", BatterySaverModeStateChanged::OFF); } +AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name, + BatteryPluggedStateEnum state) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(util::PLUGGED_STATE_CHANGED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(1); // State field. + field_value_matcher->set_eq_int(state); + return atom_matcher; +} + +AtomMatcher CreateBatteryStateNoneMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone", + BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE); +} + +AtomMatcher CreateBatteryStateUsbMatcher() { + return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb", + BatteryPluggedStateEnum::BATTERY_PLUGGED_USB); +} AtomMatcher CreateScreenStateChangedAtomMatcher( const string& name, android::view::DisplayStateEnum state) { AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::SCREEN_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(1); // State field. field_value_matcher->set_eq_int(state); return atom_matcher; } - AtomMatcher CreateScreenTurnedOnAtomMatcher() { return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn", android::view::DisplayStateEnum::DISPLAY_STATE_ON); @@ -141,7 +184,7 @@ AtomMatcher CreateSyncStateChangedAtomMatcher( AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::SYNC_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(3); // State field. field_value_matcher->set_eq_int(state); @@ -161,7 +204,7 @@ AtomMatcher CreateActivityForegroundStateChangedAtomMatcher( AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::ACTIVITY_FOREGROUND_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(4); // Activity field. field_value_matcher->set_eq_int(state); @@ -183,7 +226,7 @@ AtomMatcher CreateProcessLifeCycleStateChangedAtomMatcher( AtomMatcher atom_matcher; atom_matcher.set_id(StringToId(name)); auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); - simple_atom_matcher->set_atom_id(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + simple_atom_matcher->set_atom_id(util::PROCESS_LIFE_CYCLE_STATE_CHANGED); auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); field_value_matcher->set_field(3); // Process state field. field_value_matcher->set_eq_int(state); @@ -211,6 +254,14 @@ Predicate CreateBatterySaverModePredicate() { return predicate; } +Predicate CreateDeviceUnpluggedPredicate() { + Predicate predicate; + predicate.set_id(StringToId("DeviceUnplugged")); + predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone")); + predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb")); + return predicate; +} + Predicate CreateScreenIsOnPredicate() { Predicate predicate; predicate.set_id(StringToId("ScreenIsOn")); @@ -251,6 +302,95 @@ Predicate CreateIsInBackgroundPredicate() { return predicate; } +State CreateScreenState() { + State state; + state.set_id(StringToId("ScreenState")); + state.set_atom_id(util::SCREEN_STATE_CHANGED); + return state; +} + +State CreateUidProcessState() { + State state; + state.set_id(StringToId("UidProcessState")); + state.set_atom_id(util::UID_PROCESS_STATE_CHANGED); + return state; +} + +State CreateOverlayState() { + State state; + state.set_id(StringToId("OverlayState")); + state.set_atom_id(util::OVERLAY_STATE_CHANGED); + return state; +} + +State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId) { + State state; + state.set_id(StringToId("ScreenStateOnOff")); + state.set_atom_id(util::SCREEN_STATE_CHANGED); + + auto map = CreateScreenStateOnOffMap(screenOnId, screenOffId); + *state.mutable_map() = map; + + return state; +} + +State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { + State state; + state.set_id(StringToId("ScreenStateSimpleOnOff")); + state.set_atom_id(util::SCREEN_STATE_CHANGED); + + auto map = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId); + *state.mutable_map() = map; + + return state; +} + +StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) { + StateMap_StateGroup group; + group.set_group_id(screenOnId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_VR); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND); + return group; +} + +StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) { + StateMap_StateGroup group; + group.set_group_id(screenOffId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND); + return group; +} + +StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId) { + StateMap_StateGroup group; + group.set_group_id(screenOnId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON); + return group; +} + +StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId) { + StateMap_StateGroup group; + group.set_group_id(screenOffId); + group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + return group; +} + +StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId) { + StateMap map; + *map.add_group() = CreateScreenStateOnGroup(screenOnId); + *map.add_group() = CreateScreenStateOffGroup(screenOffId); + return map; +} + +StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) { + StateMap map; + *map.add_group() = CreateScreenStateSimpleOnGroup(screenOnId); + *map.add_group() = CreateScreenStateSimpleOffGroup(screenOffId); + return map; +} + void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combinationPredicate) { combinationPredicate->mutable_combination()->add_predicate(predicate.id()); @@ -292,173 +432,551 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) return dimensions; } -std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( - const android::view::DisplayStateEnum state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs); - EXPECT_TRUE(event->write(state)); - event->init(); - return event; +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector<Position>& positions, + const std::vector<int>& fields) { + FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions); + + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +// START: get primary key functions +void getUidProcessKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + Field field1(27 /* atom id */, pos1, 0 /* depth */); + Value value1((int32_t)uid); + + key->addValue(FieldValue(field1, value1)); +} + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) { + int pos1[] = {1, 0, 0}; + int pos2[] = {2, 0, 0}; + + Field field1(59 /* atom id */, pos1, 0 /* depth */); + Field field2(59 /* atom id */, pos2, 0 /* depth */); + + Value value1((int32_t)uid); + Value value2(packageName); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field2, value2)); +} + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + int pos4[] = {3, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + + Field field3(10 /* atom id */, pos3, 0 /* depth */); + Field field4(10 /* atom id */, pos4, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + Value value4(tag); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); + key->addValue(FieldValue(field4, value4)); +} + +void getPartialWakelockKey(int uid, HashableDimensionKey* key) { + int pos1[] = {1, 1, 1}; + int pos3[] = {2, 0, 0}; + + Field field1(10 /* atom id */, pos1, 2 /* depth */); + Field field3(10 /* atom id */, pos3, 0 /* depth */); + + Value value1((int32_t)uid); + Value value3((int32_t)1 /*partial*/); + + key->addValue(FieldValue(field1, value1)); + key->addValue(FieldValue(field3, value3)); +} +// END: get primary key functions + +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags) { + vector<const char*> cTags(attributionTags.size()); + for (int i = 0; i < cTags.size(); i++) { + cTags[i] = attributionTags[i].c_str(); + } + + AStatsEvent_writeAttributionChain(statsEvent, + reinterpret_cast<const uint32_t*>(attributionUids.data()), + cTags.data(), attributionUids.size()); +} + +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) { + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + logEvent->parseBuffer(buf, size); + + AStatsEvent_release(statsEvent); +} + +void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, value1); + AStatsEvent_writeInt32(statsEvent, value2); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2) { + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2); + return logEvent; +} + +void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, value1); + AStatsEvent_writeInt32(statsEvent, value2); + AStatsEvent_writeInt32(statsEvent, value3); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3) { + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3); + return logEvent; +} + +void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, + int32_t value) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, value); + AStatsEvent_writeInt32(statsEvent, value); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) { + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value); + return logEvent; +} + +void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) { + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs); + return logEvent; +} + +shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + int data2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_writeInt32(statsEvent, data1); + AStatsEvent_writeInt32(statsEvent, data2); + + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs, + const vector<int>& uids, const vector<string>& tags, + int data1, int data2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + + writeAttribution(statsEvent, uids, tags); + AStatsEvent_writeInt32(statsEvent, data1); + AStatsEvent_writeInt32(statsEvent, data2); + + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +sp<MockUidMap> makeMockUidMapForOneHost(int hostUid, const vector<int>& isolatedUids) { + sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>(); + EXPECT_CALL(*uidMap, getHostUidOrSelf(_)).WillRepeatedly(ReturnArg<0>()); + for (const int isolatedUid : isolatedUids) { + EXPECT_CALL(*uidMap, getHostUidOrSelf(isolatedUid)).WillRepeatedly(Return(hostUid)); + } + + return uidMap; +} + +sp<MockUidMap> makeMockUidMapForPackage(const string& pkg, const set<int32_t>& uids) { + sp<MockUidMap> uidMap = new StrictMock<MockUidMap>(); + EXPECT_CALL(*uidMap, getAppUid(_)).Times(AnyNumber()); + EXPECT_CALL(*uidMap, getAppUid(pkg)).WillRepeatedly(Return(uids)); + + return uidMap; +} + +std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(uint64_t timestampNs, + const android::view::DisplayStateEnum state, + int loggerUid) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(loggerUid, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>( - android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs); - EXPECT_TRUE(event->write(BatterySaverModeStateChanged::ON)); - event->init(); - return event; + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>( - android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs); - EXPECT_TRUE(event->write(BatterySaverModeStateChanged::OFF)); - event->init(); - return event; + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent( - int level, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs); - EXPECT_TRUE(event->write(level)); - event->init(); - return event; +std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, state); + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& jobName, - const ScheduledJobStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs); - event->write(attributions); - event->write(jobName); - event->write(state); - event->init(); - return event; +std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + AStatsEvent_writeInt32(statsEvent, level); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateStartScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs) { - return CreateScheduledJobStateChangedEvent( - attributions, name, ScheduledJobStateChanged::STARTED, timestampNs); +std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent( + const vector<int>& attributionUids, const vector<string>& attributionTags, + const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, jobName.c_str()); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -// Create log event when scheduled job finishes. -std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs) { - return CreateScheduledJobStateChangedEvent( - attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs); +std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::STARTED, timestampNs); } -std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - const WakelockStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs); - event->write(attributions); - event->write(android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); - event->write(wakelockName); - event->write(state); - event->init(); - return event; +// Create log event when scheduled job finishes. +std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName) { + return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName, + ScheduledJobStateChanged::FINISHED, timestampNs); +} + +std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& wakelockName, + const WakelockStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); + AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeString(statsEvent, wakelockName.c_str()); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs) { - return CreateWakelockStateChangedEvent( - attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs); +std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& wakelockName) { + return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags, + wakelockName, WakelockStateChanged::ACQUIRE); } -std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs) { - return CreateWakelockStateChangedEvent( - attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs); +std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& wakelockName) { + return CreateWakelockStateChangedEvent(timestampNs, attributionUids, attributionTags, + wakelockName, WakelockStateChanged::RELEASE); } std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent( - const int uid, const ActivityForegroundStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>( - android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs); - event->write(uid); - event->write("pkg_name"); - event->write("class_name"); - event->write(state); - event->init(); - return event; + uint64_t timestampNs, const int uid, const ActivityForegroundStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::ACTIVITY_FOREGROUND_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, "pkg_name"); + AStatsEvent_writeString(statsEvent, "class_name"); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) { - return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::BACKGROUND, timestampNs); +std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid) { + return CreateActivityForegroundStateChangedEvent(timestampNs, uid, + ActivityForegroundStateChanged::BACKGROUND); } -std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) { - return CreateActivityForegroundStateChangedEvent( - uid, ActivityForegroundStateChanged::FOREGROUND, timestampNs); +std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid) { + return CreateActivityForegroundStateChangedEvent(timestampNs, uid, + ActivityForegroundStateChanged::FOREGROUND); } -std::unique_ptr<LogEvent> CreateSyncStateChangedEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - const SyncStateChanged::State state, uint64_t timestampNs) { - auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs); - event->write(attributions); - event->write(name); - event->write(state); - event->init(); - return event; +std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name, + const SyncStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeString(statsEvent, name.c_str()); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateSyncStartEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs) { - return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs); +std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::ON); } -std::unique_ptr<LogEvent> CreateSyncEndEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs) { - return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs); +std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& name) { + return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name, + SyncStateChanged::OFF); } std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent( - const int uid, const ProcessLifeCycleStateChanged::State state, uint64_t timestampNs) { - auto logEvent = std::make_unique<LogEvent>( - android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, timestampNs); - logEvent->write(uid); - logEvent->write(""); - logEvent->write(state); - logEvent->init(); + uint64_t timestampNs, const int uid, const ProcessLifeCycleStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, ""); + AStatsEvent_writeInt32(statsEvent, state); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr<LogEvent> CreateAppCrashEvent(uint64_t timestampNs, const int uid) { + return CreateProcessLifeCycleStateChangedEvent(timestampNs, uid, + ProcessLifeCycleStateChanged::CRASHED); +} + +std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::APP_CRASH_OCCURRED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_writeString(statsEvent, "eventType"); + AStatsEvent_writeString(statsEvent, "processName"); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } -std::unique_ptr<LogEvent> CreateAppCrashEvent(const int uid, uint64_t timestampNs) { - return CreateProcessLifeCycleStateChangedEvent( - uid, ProcessLifeCycleStateChanged::CRASHED, timestampNs); +std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid, + int isolatedUid, bool is_create) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::ISOLATED_UID_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, hostUid); + AStatsEvent_writeInt32(statsEvent, isolatedUid); + AStatsEvent_writeInt32(statsEvent, is_create); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr<LogEvent> CreateUidProcessStateChangedEvent( + uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::UID_PROCESS_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + +std::unique_ptr<LogEvent> CreateBleScanStateChangedEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const BleScanStateChanged::State state, + const bool filtered, const bool firstMatch, + const bool opportunistic) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, true); + if (state == util::BLE_SCAN_STATE_CHANGED__STATE__RESET) { + AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_TRIGGER_STATE_RESET, + util::BLE_SCAN_STATE_CHANGED__STATE__OFF); + } + AStatsEvent_writeBool(statsEvent, filtered); // filtered + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, firstMatch); // first match + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, opportunistic); // opportunistic + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; } -std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent( - int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs) { - auto logEvent = std::make_unique<LogEvent>( - android::util::ISOLATED_UID_CHANGED, timestampNs); - logEvent->write(hostUid); - logEvent->write(isolatedUid); - logEvent->write(is_create); - logEvent->init(); +std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid, + const string& packageName, + const bool usingAlertWindow, + const OverlayStateChanged::State state) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_IS_UID, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeString(statsEvent, packageName.c_str()); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_PRIMARY_FIELD, true); + AStatsEvent_writeBool(statsEvent, usingAlertWindow); + AStatsEvent_writeInt32(statsEvent, state); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); return logEvent; } sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, - const StatsdConfig& config, const ConfigKey& key) { - sp<UidMap> uidMap = new UidMap(); + const StatsdConfig& config, const ConfigKey& key, + const shared_ptr<IPullAtomCallback>& puller, + const int32_t atomTag, const sp<UidMap> uidMap) { sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + if (puller != nullptr) { + pullerManager->RegisterPullAtomCallback(/*uid=*/0, atomTag, NS_PER_SEC, NS_PER_SEC * 10, {}, + puller); + } sp<AlarmMonitor> anomalyAlarmMonitor = - new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){}, - [](const sp<IStatsCompanionService>&){}); + new AlarmMonitor(1, + [](const shared_ptr<IStatsCompanionService>&, int64_t){}, + [](const shared_ptr<IStatsCompanionService>&){}); sp<AlarmMonitor> periodicAlarmMonitor = - new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){}, - [](const sp<IStatsCompanionService>&){}); + new AlarmMonitor(1, + [](const shared_ptr<IStatsCompanionService>&, int64_t){}, + [](const shared_ptr<IStatsCompanionService>&){}); sp<StatsLogProcessor> processor = new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&) { return true; }, @@ -467,13 +985,6 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const in return processor; } -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) { - AttributionNodeInternal attribution; - attribution.set_uid(uid); - attribution.set_tag(tag); - return attribution; -} - void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { std::sort(events->begin(), events->end(), [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { @@ -485,22 +996,25 @@ int64_t StringToId(const string& str) { return static_cast<int64_t>(std::hash<std::string>()(str)); } -void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) { +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag) { EXPECT_EQ(value.field(), atomId); + ASSERT_EQ(value.value_tuple().dimensions_value_size(), 2); // Attribution field. EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(value.value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), uid); + // Uid field. + ASSERT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1); + EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(), + uid); + // Tag field. + EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3); + EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag); } -void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) { +void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) { EXPECT_EQ(value.field(), atomId); - EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1); + ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1); // Attribution field. EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1); // Uid only. @@ -514,7 +1028,7 @@ void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) { void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid) { EXPECT_EQ(value.field(), atomId); - EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx); + ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx); // Attribution field. EXPECT_EQ(value.value_tuple().dimensions_value(node_idx).field(), 1); EXPECT_EQ(value.value_tuple().dimensions_value(node_idx) @@ -526,7 +1040,7 @@ void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId void ValidateAttributionUidAndTagDimension( const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag) { EXPECT_EQ(value.field(), atomId); - EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx); + ASSERT_GT(value.value_tuple().dimensions_value_size(), node_idx); // Attribution field. EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx).field()); // Uid only. @@ -545,7 +1059,7 @@ void ValidateAttributionUidAndTagDimension( void ValidateAttributionUidAndTagDimension( const DimensionsValue& value, int atomId, int uid, const std::string& tag) { EXPECT_EQ(value.field(), atomId); - EXPECT_EQ(1, value.value_tuple().dimensions_value_size()); + ASSERT_EQ(1, value.value_tuple().dimensions_value_size()); // Attribution field. EXPECT_EQ(1, value.value_tuple().dimensions_value(0).field()); // Uid only. @@ -597,6 +1111,27 @@ bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) { } } +bool LessThan(const google::protobuf::RepeatedPtrField<StateValue>& s1, + const google::protobuf::RepeatedPtrField<StateValue>& s2) { + if (s1.size() != s2.size()) { + return s1.size() < s2.size(); + } + for (int i = 0; i < s1.size(); i++) { + const StateValue& state1 = s1[i]; + const StateValue& state2 = s2[i]; + if (state1.atom_id() != state2.atom_id()) { + return state1.atom_id() < state2.atom_id(); + } + if (state1.value() != state2.value()) { + return state1.value() < state2.value(); + } + if (state1.group_id() != state2.group_id()) { + return state1.group_id() < state2.group_id(); + } + } + return false; +} + bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2) { if (s1.field() != s2.field()) { return s1.field() < s2.field(); @@ -645,7 +1180,7 @@ bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2) { return false; } - return LessThan(s1.dimInCondition, s2.dimInCondition); + return LessThan(s1.stateValues, s2.stateValues); } void backfillStringInDimension(const std::map<uint64_t, string>& str_map, @@ -825,6 +1360,34 @@ void backfillStartEndTimestamp(ConfigMetricsReportList *config_report_list) { } } +Status FakeSubsystemSleepCallback::onPullAtom(int atomTag, + const shared_ptr<IPullAtomResultReceiver>& resultReceiver) { + // Convert stats_events into StatsEventParcels. + std::vector<StatsEventParcel> parcels; + for (int i = 1; i < 3; i++) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomTag); + std::string subsystemName = "subsystem_name_"; + subsystemName = subsystemName + std::to_string(i); + AStatsEvent_writeString(event, subsystemName.c_str()); + AStatsEvent_writeString(event, "subsystem_subname foo"); + AStatsEvent_writeInt64(event, /*count= */ i); + AStatsEvent_writeInt64(event, /*time_millis= */ i * 100); + AStatsEvent_build(event); + size_t size; + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); + + StatsEventParcel p; + // vector.assign() creates a copy, but this is inevitable unless + // stats_event.h/c uses a vector as opposed to a buffer. + p.buffer.assign(buffer, buffer + size); + parcels.push_back(std::move(p)); + AStatsEvent_release(event); + } + resultReceiver->pullFinished(atomTag, /*success=*/true, parcels); + return Status::ok(); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 635c5835333a..3dcf4ecce054 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -14,20 +14,47 @@ #pragma once +#include <aidl/android/os/BnPullAtomCallback.h> +#include <aidl/android/os/IPullAtomCallback.h> +#include <aidl/android/os/IPullAtomResultReceiver.h> +#include <gmock/gmock.h> #include <gtest/gtest.h> + #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" #include "src/StatsLogProcessor.h" -#include "src/logd/LogEvent.h" #include "src/hash.h" +#include "src/logd/LogEvent.h" +#include "src/packages/UidMap.h" #include "src/stats_log_util.h" -#include "statslog.h" +#include "stats_event.h" +#include "statslog_statsdtest.h" namespace android { namespace os { namespace statsd { +using namespace testing; +using ::aidl::android::os::BnPullAtomCallback; +using ::aidl::android::os::IPullAtomCallback; +using ::aidl::android::os::IPullAtomResultReceiver; +using android::util::ProtoReader; using google::protobuf::RepeatedPtrField; +using Status = ::ndk::ScopedAStatus; + +const int SCREEN_STATE_ATOM_ID = util::SCREEN_STATE_CHANGED; +const int UID_PROCESS_STATE_ATOM_ID = util::UID_PROCESS_STATE_CHANGED; + +enum BucketSplitEvent { APP_UPGRADE, BOOT_COMPLETE }; + +class MockUidMap : public UidMap { +public: + MOCK_METHOD(int, getHostUidOrSelf, (int uid), (const)); + MOCK_METHOD(std::set<int32_t>, getAppUid, (const string& package), (const)); +}; + +// Converts a ProtoOutputStream to a StatsLogReport proto. +StatsLogReport outputStreamToProto(ProtoOutputStream* proto); // Create AtomMatcher proto to simply match a specific atom type. AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); @@ -53,6 +80,12 @@ AtomMatcher CreateBatterySaverModeStartAtomMatcher(); // Create AtomMatcher proto for stopping battery save mode. AtomMatcher CreateBatterySaverModeStopAtomMatcher(); +// Create AtomMatcher proto for battery state none mode. +AtomMatcher CreateBatteryStateNoneMatcher(); + +// Create AtomMatcher proto for battery state usb mode. +AtomMatcher CreateBatteryStateUsbMatcher(); + // Create AtomMatcher proto for process state changed. AtomMatcher CreateUidProcessStateChangedAtomMatcher(); @@ -95,6 +128,9 @@ Predicate CreateScheduledJobPredicate(); // Create Predicate proto for battery saver mode. Predicate CreateBatterySaverModePredicate(); +// Create Predicate proto for device unplogged mode. +Predicate CreateDeviceUnpluggedPredicate(); + // Create Predicate proto for holding wakelock. Predicate CreateHoldingWakelockPredicate(); @@ -104,6 +140,39 @@ Predicate CreateIsSyncingPredicate(); // Create a Predicate proto for app is in background. Predicate CreateIsInBackgroundPredicate(); +// Create State proto for screen state atom. +State CreateScreenState(); + +// Create State proto for uid process state atom. +State CreateUidProcessState(); + +// Create State proto for overlay state atom. +State CreateOverlayState(); + +// Create State proto for screen state atom with on/off map. +State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId); + +// Create State proto for screen state atom with simple on/off map. +State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); + +// Create StateGroup proto for ScreenState ON group +StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId); + +// Create StateGroup proto for ScreenState OFF group +StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId); + +// Create StateGroup proto for simple ScreenState ON group +StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId); + +// Create StateGroup proto for simple ScreenState OFF group +StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId); + +// Create StateMap proto for ScreenState ON/OFF map +StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId); + +// Create StateMap proto for simple ScreenState ON/OFF map +StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId); + // Add a predicate to the predicate combination. void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination); @@ -118,76 +187,156 @@ FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, FieldMatcher CreateAttributionUidDimensions(const int atomId, const std::vector<Position>& positions); +FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, + const std::vector<Position>& positions, + const std::vector<int>& fields); + +// START: get primary key functions +// These functions take in atom field information and create FieldValues which are stored in the +// given HashableDimensionKey. +void getUidProcessKey(int uid, HashableDimensionKey* key); + +void getOverlayKey(int uid, string packageName, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key); + +void getPartialWakelockKey(int uid, HashableDimensionKey* key); +// END: get primary key functions + +void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids, + const vector<string>& attributionTags); + +// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent. +void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent); + +shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2); + +void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2); + +shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3); + +void CreateThreeValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1, + int32_t value2, int32_t value3); + +// The repeated value log event helpers create a log event with two int fields, both +// set to the same value. This is useful for testing metrics that are only interested +// in the value of the second field but still need the first field to be populated. +std::shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, + int32_t value); + +void CreateRepeatedValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, + int32_t value); + +std::shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs); + +void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs); + +std::shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + int data2); + +std::shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs, + const vector<int>& uids, + const vector<string>& tags, int data1, int data2); + +sp<MockUidMap> makeMockUidMapForOneHost(int hostUid, const vector<int>& isolatedUids); + +sp<MockUidMap> makeMockUidMapForPackage(const string& pkg, const set<int32_t>& uids); + // Create log event for screen state changed. -std::unique_ptr<LogEvent> CreateScreenStateChangedEvent( - const android::view::DisplayStateEnum state, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(uint64_t timestampNs, + const android::view::DisplayStateEnum state, + int loggerUid = 0); // Create log event for screen brightness state changed. -std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent( - int level, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level); // Create log event when scheduled job starts. -std::unique_ptr<LogEvent> CreateStartScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName); // Create log event when scheduled job finishes. -std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent( - const std::vector<AttributionNodeInternal>& attributions, - const string& name, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const string& jobName); // Create log event when battery saver starts. std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs); // Create log event when battery saver stops. std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs); +// Create log event when battery state changes. +std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state); + // Create log event for app moving to background. -std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid); // Create log event for app moving to foreground. -std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid); // Create log event when the app sync starts. -std::unique_ptr<LogEvent> CreateSyncStartEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs, const vector<int>& uids, + const vector<string>& tags, const string& name); // Create log event when the app sync ends. -std::unique_ptr<LogEvent> CreateSyncEndEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& name, - uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs, const vector<int>& uids, + const vector<string>& tags, const string& name); // Create log event when the app sync ends. -std::unique_ptr<LogEvent> CreateAppCrashEvent( - const int uid, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateAppCrashEvent(uint64_t timestampNs, const int uid); + +// Create log event for an app crash. +std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid); // Create log event for acquiring wakelock. -std::unique_ptr<LogEvent> CreateAcquireWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(uint64_t timestampNs, const vector<int>& uids, + const vector<string>& tags, + const string& wakelockName); // Create log event for releasing wakelock. -std::unique_ptr<LogEvent> CreateReleaseWakelockEvent( - const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName, - uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(uint64_t timestampNs, const vector<int>& uids, + const vector<string>& tags, + const string& wakelockName); // Create log event for releasing wakelock. -std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent( - int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs); +std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(uint64_t timestampNs, int hostUid, + int isolatedUid, bool is_create); + +// Create log event for uid process state change. +std::unique_ptr<LogEvent> CreateUidProcessStateChangedEvent( + uint64_t timestampNs, int uid, const android::app::ProcessStateEnum state); + +std::unique_ptr<LogEvent> CreateBleScanStateChangedEvent(uint64_t timestampNs, + const vector<int>& attributionUids, + const vector<string>& attributionTags, + const BleScanStateChanged::State state, + const bool filtered, const bool firstMatch, + const bool opportunistic); -// Helper function to create an AttributionNodeInternal proto. -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag); +std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, const int32_t uid, + const string& packageName, + const bool usingAlertWindow, + const OverlayStateChanged::State state); // Create a statsd log event processor upon the start time in seconds, config and key. -sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, - const int64_t currentTimeNs, - const StatsdConfig& config, const ConfigKey& key); +sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, + const StatsdConfig& config, const ConfigKey& key, + const shared_ptr<IPullAtomCallback>& puller = nullptr, + const int32_t atomTag = 0 /*for puller only*/, + const sp<UidMap> = new UidMap()); // Util function to sort the log events by timestamp. void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events); int64_t StringToId(const string& str); +void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId, + const int uid, const string& tag); void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid); void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid); void ValidateAttributionUidAndTagDimension( @@ -196,12 +345,14 @@ void ValidateAttributionUidAndTagDimension( const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag); struct DimensionsPair { - DimensionsPair(DimensionsValue m1, DimensionsValue m2) : dimInWhat(m1), dimInCondition(m2){}; + DimensionsPair(DimensionsValue m1, google::protobuf::RepeatedPtrField<StateValue> m2) + : dimInWhat(m1), stateValues(m2){}; DimensionsValue dimInWhat; - DimensionsValue dimInCondition; + google::protobuf::RepeatedPtrField<StateValue> stateValues; }; +bool LessThan(const StateValue& s1, const StateValue& s2); bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2); bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2); @@ -233,6 +384,12 @@ bool backfillDimensionPath(const DimensionsValue& path, const google::protobuf::RepeatedPtrField<DimensionsValue>& leafValues, DimensionsValue* dimension); +class FakeSubsystemSleepCallback : public BnPullAtomCallback { +public: + Status onPullAtom(int atomTag, + const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override; +}; + template <typename T> void backfillDimensionPath(const DimensionsValue& whatPath, const DimensionsValue& conditionPath, @@ -264,7 +421,7 @@ void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) { for (int i = 0; i < metricData.data_size(); ++i) { dimensionIndexMap.insert( std::make_pair(DimensionsPair(metricData.data(i).dimensions_in_what(), - metricData.data(i).dimensions_in_condition()), + metricData.data(i).slice_by_state()), i)); } for (const auto& itr : dimensionIndexMap) { @@ -319,4 +476,4 @@ void backfillStartEndTimestampForSkippedBuckets(const int64_t timeBaseNs, T* met } } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp index 9e15e99781d6..74eafbf56d66 100644 --- a/cmds/statsd/tests/storage/StorageManager_test.cpp +++ b/cmds/statsd/tests/storage/StorageManager_test.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <android-base/unique_fd.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <stdio.h> @@ -39,47 +40,19 @@ TEST(StorageManagerTest, TrainInfoReadWriteTest) { bool result; - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); + result = StorageManager::writeTrainInfo(trainInfo); EXPECT_TRUE(result); InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); EXPECT_TRUE(result); EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); - EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); + ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); EXPECT_EQ(trainInfo.status, trainInfoResult.status); - EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); - EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); -} - -TEST(StorageManagerTest, TrainInfoReadWriteEmptyTrainNameTest) { - InstallTrainInfo trainInfo; - trainInfo.trainVersionCode = 12345; - trainInfo.trainName = ""; - trainInfo.status = 1; - const char* expIds = "test_ids"; - trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); - - bool result; - - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); - - EXPECT_TRUE(result); - - InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); - EXPECT_TRUE(result); - - EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); - EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); - EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); - EXPECT_EQ(trainInfo.status, trainInfoResult.status); - EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); + ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); } @@ -93,20 +66,19 @@ TEST(StorageManagerTest, TrainInfoReadWriteTrainNameSizeOneTest) { bool result; - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); + result = StorageManager::writeTrainInfo(trainInfo); EXPECT_TRUE(result); InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); EXPECT_TRUE(result); EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); - EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); + ASSERT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); EXPECT_EQ(trainInfo.status, trainInfoResult.status); - EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); + ASSERT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); } diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp new file mode 100644 index 000000000000..32cecd3b9dbc --- /dev/null +++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 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 "utils/MultiConditionTrigger.h" + +#include <gtest/gtest.h> + +#include <chrono> +#include <set> +#include <thread> +#include <vector> + +#ifdef __ANDROID__ + +using namespace std; +using std::this_thread::sleep_for; + +namespace android { +namespace os { +namespace statsd { + +TEST(MultiConditionTrigger, TestMultipleConditions) { + int numConditions = 5; + string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5"; + set<string> conditionNames = {t1, t2, t3, t4, t5}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + // Mark done as true and notify in the done. + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + vector<thread> threads; + vector<int> done(numConditions, 0); + + int i = 0; + for (const string& conditionName : conditionNames) { + threads.emplace_back([&done, &conditionName, &trigger, i] { + sleep_for(chrono::milliseconds(3)); + done[i] = 1; + trigger.markComplete(conditionName); + }); + i++; + } + + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { + return triggerCalled; + }); + + for (i = 0; i < numConditions; i++) { + EXPECT_EQ(done[i], 1); + } + + for (i = 0; i < numConditions; i++) { + threads[i].join(); + } +} + +TEST(MultiConditionTrigger, TestNoConditions) { + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); + // Ensure that trigger occurs immediately if no events need to be completed. +} + +TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) { + string t1 = "t1", t2 = "t2"; + set<string> conditionNames = {t1, t2}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + trigger.markComplete(t1); + trigger.markComplete(t1); + + // Ensure that the trigger still hasn't fired. + { + lock_guard lg(lock); + EXPECT_FALSE(triggerCalled); + } + + trigger.markComplete(t2); + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); +} + +TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) { + string t1 = "t1"; + set<string> conditionNames = {t1}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + int triggerCount = 0; + + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] { + { + lock_guard lg(lock); + triggerCount++; + triggerCalled = true; + } + cv.notify_all(); + }); + + trigger.markComplete(t1); + + // Ensure that the trigger fired. + { + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); + EXPECT_EQ(triggerCount, 1); + triggerCalled = false; + } + + trigger.markComplete(t1); + + // Ensure that the trigger does not fire again. + { + unique_lock<mutex> unique_lk(lock); + cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; }); + EXPECT_FALSE(triggerCalled); + EXPECT_EQ(triggerCount, 1); + } +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tools/dogfood/Android.bp b/cmds/statsd/tools/dogfood/Android.bp deleted file mode 100644 index bb494a6025af..000000000000 --- a/cmds/statsd/tools/dogfood/Android.bp +++ /dev/null @@ -1,37 +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. -// -// - -android_app { - name: "StatsdDogfood", - platform_apis: true, - - srcs: ["src/**/*.java"], - - resource_dirs: ["res"], - static_libs: [ - "platformprotoslite", - "statsdprotolite", - ], - - privileged: true, - dex_preopt: { - enabled: false, - }, - certificate: "platform", - optimize: { - enabled: false, - }, -} diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml deleted file mode 100644 index 52673fbdcf92..000000000000 --- a/cmds/statsd/tools/dogfood/AndroidManifest.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.statsd.dogfood" - android:sharedUserId="android.uid.system" - android:versionCode="1" - android:versionName="1.0" > - - <uses-permission android:name="android.permission.DUMP" /> - - <application - android:allowBackup="true" - android:icon="@drawable/ic_launcher" - android:label="@string/app_name" > - <activity - android:name=".MainActivity" - android:label="@string/app_name" - android:launchMode="singleTop" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - - <service android:name=".MainActivity$ReceiverIntentService" android:exported="true" /> - </application> -</manifest> diff --git a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 55621cc1074f..000000000000 --- a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 11ec2068be19..000000000000 --- a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 7c02b784aa5d..000000000000 --- a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 915d91441349..000000000000 --- a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml deleted file mode 100644 index 784ed40ce2c2..000000000000 --- a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml +++ /dev/null @@ -1,162 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <Button - android:id="@+id/push_config" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@android:color/holo_green_light" - android:text="@string/push_config"/> - <Button - android:id="@+id/set_receiver" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@android:color/holo_green_light" - android:text="@string/set_receiver"/> - <Button - android:id="@+id/remove_receiver" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@android:color/holo_green_light" - android:text="@string/remove_receiver"/> - - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <Button android:id="@+id/app_a_wake_lock_acquire1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_a_get_wl1"/> - <Button android:id="@+id/app_a_wake_lock_release1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_a_release_wl1"/> - </LinearLayout> - - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <Button android:id="@+id/app_a_wake_lock_acquire2" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_a_get_wl2"/> - <Button android:id="@+id/app_a_wake_lock_release2" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_a_release_wl2"/> - </LinearLayout> - - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <Button android:id="@+id/app_b_wake_lock_acquire1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_b_get_wl1"/> - <Button android:id="@+id/app_b_wake_lock_release1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_b_release_wl1"/> - </LinearLayout> - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <Button android:id="@+id/app_b_wake_lock_acquire2" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_b_get_wl2"/> - <Button android:id="@+id/app_b_wake_lock_release2" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/app_b_release_wl2"/> - </LinearLayout> - - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <Button android:id="@+id/plug" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/plug"/> - - <Button android:id="@+id/unplug" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/unplug"/> - </LinearLayout> - - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <Button android:id="@+id/screen_on" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/screen_on"/> - - <Button android:id="@+id/screen_off" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/screen_off"/> - </LinearLayout> - - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <Button - android:id="@+id/custom_start" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/custom_start" /> - - <Button - android:id="@+id/custom_stop" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/custom_stop" /> - </LinearLayout> - - <Button android:id="@+id/dump" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@android:color/holo_purple" - android:text="@string/dump"/> - - <TextView - android:id="@+id/header" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/report_header"/> - - <TextView - android:id="@+id/report_text" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - </LinearLayout> - -</ScrollView>
\ No newline at end of file diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config deleted file mode 100644 index d05006124994..000000000000 --- a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config +++ /dev/null @@ -1,109 +0,0 @@ -¹ÓõÕ¯¤”årÝèåâÕ»éÉ7¬ìŸ‘Ψê.–´šÄÁŒç¶ÚšäÑùôºZªÑ€¸ëÐé푘‹Ý™ûÆŽ6ˆ™ÁÖ΂õ›Ã¥¼à¨É—ÿQÝàåÈ߀äÍ@ÆÙÑ„ÓІ„èѿ̲±™¨lŸÄ€ìúˆÖü¶ú̳Šéâ‡ãà§ØÈË´Ù%óÓÚ«¹¼ÜyÛ®±ë¸«”º?¹•繡¸Œ€â鿨ŒÙœ®‘)çæ©Á”§äéîÀ‚Ò®ö‡.ÐÙ•°‹ÁÈy"'(WòóÀ–ðßæ¬ùÕàÀ™ýëZ”ÔÆ™Ž¤š¹»" -(2!ûÄߪæþªâÚ -V¢¦€¡’€Ç€¡ùÕàÀ™ýëZÈ‹ÄÂë¢Ä¯Ø" -(2 ª–¨Œ×ÿ¹¡k -V‡úÌòá㿘>ùÕàÀ™ýëZáž‹â‘Ý»ä" -(2!÷‘–ä–Œ¿¯ -WØõé¼ÃóÀàþùÕàÀ™ýëZ°ÔØùΣ‘¯" -(2!Ïä†÷ɯ -VÚœäÌþ½¬jùÕàÀ™ýëZ³Ö‰Ó¾ ½Ñ" -(2!Ÿ¡ªˆëôùÇ -V¨¾ñ≎̅ùÕàÀ™ýëZ“Ðåÿèåò?" -(2!½šòÐ䤿Œ -U…£²†µÜ‡‘_ùÕàÀ™ýëZñîôÒ‡¤´†z" -(2!é’¬¸ç¼‡Óª -K‘ã´¶üÅçúñ±Ë壆¨D”ÔÆ™Ž¤š¹»" -#(2ûÄߪæþªâÚ#Ië§ì·ºê•Æúñ±Ë壆¨DÈ‹ÄÂë¢Ä¯Ø" -#(2ª–¨Œ×ÿ¹¡k#JÙ¢Öúƒ†ðúñ±Ë壆¨Dáž‹â‘Ý»ä" -#(2÷‘–ä–Œ¿¯#JãýØåóªü)úñ±Ë壆¨D°ÔØùΣ‘¯" -#(2Ïä†÷ɯ#KËÅÝÇž¹Åãúñ±Ë壆¨D³Ö‰Ó¾ ½Ñ" -#(2Ÿ¡ªˆëôùÇ#Jɳ¸ó€„ø‘Ãúñ±Ë壆¨D“Ðåÿèåò?" -#(2½šòÐ䤿Œ#J•–ó«ðô¤Þïúñ±Ë壆¨DñîôÒ‡¤´†z" -#(2é’¬¸ç¼‡Óª#J•ºÔ¯•…Î’:›ºô¿Úó¾kŒÓÜ¢¾ÌÊ" -(2ûÄߪæþªâÚI»ÓòÓí‰àÖ -›ºô¿Úó¾kǮ›ŸÊŸäâ" -(2ª–¨Œ×ÿ¹¡kJÖÐük÷y›ºô¿Úó¾kÿ‚¾žÃ“’§õ" -(2÷‘–ä–Œ¿¯JøÿÊ“åøË¨›ºô¿Úó¾k˜ý˜‘¢ŒÐÀI" -(2Ïä†÷ɯKõß°—’ì¾×›ºô¿Úó¾kì‘áÒîŸÙÍá" -(2Ÿ¡ªˆëôùÇJà¸ëòä¥û݈›ºô¿Úó¾kôé‹¿¿¸ßÄ" -(2½šòÐ䤿ŒJУŒð†¬µÙ›ºô¿Úó¾k‰±Öйˆ–Ôõ" -(2é’¬¸ç¼‡ÓªK—ÍË é÷ÐM鮣öü©µŒÓÜ¢¾ÌÊ" -(2ûÄߪæþªâÚJÌŽë á«¡´{鮣öü©µÇ®Â›ŸÊŸäâ" -(2ª–¨Œ×ÿ¹¡kL˜†ŒÏØì¡‹³é®£öü©µÿ‚¾žÃ“’§õ" -(2÷‘–ä–Œ¿¯Ká°Ñàƒ½†¶ý鮣öü©µ˜ý˜‘¢ŒÐÀI" -(2Ïä†÷ɯKÛ”Ÿò§…ÂÁY鮣öü©µì‘áÒîŸÙÍá" -(2Ÿ¡ªˆëôùÇKϜݗÞç±ù¥é®£öü©µôé‹¿¿¸ßÄ" -(2½šòÐ䤿ŒKÚÖ•·‰–B鮣öü©µ‰±Öйˆ–Ôõ" -(2é’¬¸ç¼‡Óª,Ä®öèÀƒ‘âÌ¥•€†ÜÖ×AÐÙ•°‹ÁÈy" -(€õ»•÷¾ÓçȪŠéñðȃŹ~”‰àÉ®¶f"(2é’¬¸ç¼‡Óª2½šòÐ䤿Œ2Ÿ¡ªˆëôùÇLشͼá™þþæ¿É¶ˆœ‚æð±ûÄߪæþªâÚ" --(2ûÄߪæþªâÚ-JÉ¥˜‘Ûüˆ‹ò¿É¶ˆœ‚æð±ª–¨Œ×ÿ¹¡k" --(2ª–¨Œ×ÿ¹¡k-L¡©äÞ¨ô¶ø½¿É¶ˆœ‚æð±÷‘–ä–Œ¿¯" --(2÷‘–ä–Œ¿¯-L‡õÇþ¡¾ÚÈþ¿É¶ˆœ‚æð±Ïä†÷ɯ" --(2Ïä†÷ɯ-L—î«ãïÚ½û¬¿É¶ˆœ‚æð±Ÿ¡ªˆëôùÇ" --(2Ÿ¡ªˆëôùÇ-LñÍý‚ÿô¿É¶ˆœ‚æð±½šòÐ䤿Œ" --(2½šòÐ䤿Œ-K‹òʰ«»íè|¿É¶ˆœ‚æð±é’¬¸ç¼‡Óª" --(2é’¬¸ç¼‡Óª-"9äëæÚòœ§=̨…¥”ìãÏ‘N ÐÙ•°‹ÁÈy*‘N0@":šˆž‚ÎñÐ̨…¥”ìãÏ‘N ÐÙ•°‹ÁÈy*‘N0@":ù»Žôç¾ãˆÐ̨…¥”ìãÏ‘N ÐÙ•°‹ÁÈy*‘N0@"9›™›ŸÝ†—ãS̨…¥”ìãÏ‘N ÐÙ•°‹ÁÈy*‘N0@":‹ããùŸáŽôºßñ¼ûÌ•˜“N ÐÙ•°‹ÁÈy*“N0@":ç΀àë Œƒôºßñ¼ûÌ•˜“N ÐÙ•°‹ÁÈy*“N0@":™˜µøÖ²Äìõôºßñ¼ûÌ•˜“N ÐÙ•°‹ÁÈy*“N0@":¬Öœ¥¥ÃÆŒÑôºßñ¼ûÌ•˜“N ÐÙ•°‹ÁÈy*“N0@"5ôÇ™±¶Ï¢‘ò•™ÔéŠñ˜Èu›N ÐÙ•°‹ÁÈy*›N0@"4«÷µÿ󲂜e•™ÔéŠñ˜Èu›N ÐÙ•°‹ÁÈy*›N0@"4å¢úˆÑÙúý•™ÔéŠñ˜Èu›N ÐÙ•°‹ÁÈy*›N0@"+ž—Õõ‰î¶±~‡î‚ÒÔÊ´ËœN ÐÙ•°‹ÁÈy0@",‘ó‚³ïÙù¦‚‡î‚ÒÔÊ´ËœN ÐÙ•°‹ÁÈy0@"+òßù¥·«Äà;‡î‚ÒÔÊ´ËœN ÐÙ•°‹ÁÈy0@",ÓûÅñÒ¢ô§Â‡î‚ÒÔÊ´ËœN ÐÙ•°‹ÁÈy0@"+¨²°žò³ƒ¨R‡î‚ÒÔÊ´ËœN ÐÙ•°‹ÁÈy0@"+ÞìøݱþÖq‡î‚ÒÔÊ´ËœN ÐÙ•°‹ÁÈy0@",͉šæÜŽÄÀ‡î‚ÒÔÊ´ËœN ÐÙ•°‹ÁÈy0@"3Ä»±ê¾¸ô°˜þËÉÇåãÃ2 ÐÙ•°‹ÁÈy*0@"‰Ñ½Ëñɤ¡™a˜þËÉÇåãÃ2 ”‰àÉ®¶f*0:é’¬¸ç¼‡Óª:½šòÐ䤿Œ:Ÿ¡ªˆëôùÇ@":ÃêåÒýéó¦ï‘ŽÁ°×ËÇÅß–N ÐÙ•°‹ÁÈy*–N0@":Àš‚ÑÀí£Ï‹‘ŽÁ°×ËÇÅß–N ÐÙ•°‹ÁÈy*–N0@"9¾¢ðÛßÒëéø›²Ö·ÔÈšN ÐÙ•°‹ÁÈy*šN0@"9÷´ç鼯Š~½ ¤„¹®Ð§¨˜N ÐÙ•°‹ÁÈy*˜N0@2YÝæÆËò ‚ú‘»óÓ¤ãËÕ”ÔÆ™Ž¤š¹»"!ûÄߪæþªâÚ -(2 -82X°öòΪáŠþ„»óÓ¤ãËÕÈ‹ÄÂë¢Ä¯Ø" ª–¨Œ×ÿ¹¡k -(2 -82X°ç¦ùºŒó·0»óÓ¤ãËÕáž‹â‘Ý»ä"!÷‘–ä–Œ¿¯ -(2 -82Yôæï°Þ¼ ”»óÓ¤ãËÕ°ÔØùΣ‘¯"!Ïä†÷ɯ -(2 -82Y¡Î°Ãåüý»óÓ¤ãËÕ³Ö‰Ó¾ ½Ñ"!Ÿ¡ªˆëôùÇ -(2 -82XÕ¤ô®Ã£ÇÓÈ»óÓ¤ãËÕ“Ðåÿèåò?"!½šòÐ䤿Œ -(2 -82XÖÂªßøæÌÊ»óÓ¤ãËÕñîôÒ‡¤´†z"!é’¬¸ç¼‡Óª -(2 -825’ÌíûÍõ÷Æ»óÓ¤ãËÕ®Èùé€ù’±W(2 -82X²¹÷߆ŒÀ¶`»óÓ¤ãËÕ”ÔÆ™Ž¤š¹»"!ûÄߪæþªâÚ -(2 -82W÷ÞŸÉÔ ëÍd»óÓ¤ãËÕÈ‹ÄÂë¢Ä¯Ø" ª–¨Œ×ÿ¹¡k -(2 -82XäÁš´¤·ñì.»óÓ¤ãËÕáž‹â‘Ý»ä"!÷‘–ä–Œ¿¯ -(2 -82YîæÍçâ»óÓ¤ãËÕ°ÔØùΣ‘¯"!Ïä†÷ɯ -(2 -82Y˜²Ì´×³ìóÓ¤ãËÕ³Ö‰Ó¾ ½Ñ"!Ÿ¡ªˆëôùÇ -(2 -82W¾¯†‘§È“¿P»óÓ¤ãËÕ“Ðåÿèåò?"!½šòÐ䤿Œ -(2 -82XÂÀ¶òšË˜‚ð»óÓ¤ãËÕñîôÒ‡¤´†z"!é’¬¸ç¼‡Óª -(2 -82U—îïÛ²¸¸†µ»óÓ¤ãËÕ”ÔÆ™Ž¤š¹»"!ûÄߪæþªâÚ -(2 -82Tû¯ûû²¶ýñ§»óÓ¤ãËÕÈ‹ÄÂë¢Ä¯Ø" ª–¨Œ×ÿ¹¡k -(2 -82T†ñù™ŸÈ¨«»óÓ¤ãËÕáž‹â‘Ý»ä"!÷‘–ä–Œ¿¯ -(2 -82T҈πŸ¯‰ŠD»óÓ¤ãËÕ°ÔØùΣ‘¯"!Ïä†÷ɯ -(2 -82Uš¤Ë„阿ö†»óÓ¤ãËÕ³Ö‰Ó¾ ½Ñ"!Ÿ¡ªˆëôùÇ -(2 -82SË©ËÞ¯öÈÞ&»óÓ¤ãËÕ“Ðåÿèåò?"!½šòÐ䤿Œ -(2 -82TάóýᔺèÞ»óÓ¤ãËÕñîôÒ‡¤´†z"!é’¬¸ç¼‡Óª -(2 -82#àšÉ±ûÿ±»óÓ¤ãËÕ®Èùé€ù’±W(82#ÞžÞïƒ÷“…±œ–±ÃŸ€ÀâFÐÙ•°‹ÁÈy(82"䣞ƒƒ¿Èòrœ–±ÃŸ€ÀâFÐÙ•°‹ÁÈy(82*¦‚ó©ÂÊÓŒ3œ–±ÃŸ€ÀâFÐÙ•°‹ÁÈy(2%82*ÅМ½à®ÄŠœ–±ÃŸ€ÀâFÐÙ•°‹ÁÈy(2%82"‡è‘²†ÙÓÂ+œ–±ÃŸ€ÀâFÐÙ•°‹ÁÈy(82J›»ÜŽáø©ÔӈƄת¶£«ŒÓÜ¢¾ÌÊ"ûÄߪæþªâÚ'(2'82H®Ñä‚Òš„ÝqˆÆ„ת¶£«Ç®Â›ŸÊŸäâ"ª–¨Œ×ÿ¹¡k'(2'82JêýÂÄ…Œ™ÉˆƄת¶£«ÿ‚¾žÃ“’§õ"÷‘–ä–Œ¿¯'(2'82Iâ븛ÚðÖóˆÆ„×ª¶£«˜ý˜‘¢ŒÐÀI"Ïä†÷ɯ'(2'82Iÿ±å¨ ïÉKˆÆ„ת¶£«ì‘áÒîŸÙÍá"Ÿ¡ªˆëôùÇ'(2'82Hý©ëÞ¥é÷“\ˆÆ„ת¶£«ôé‹¿¿¸ßÄ"½šòÐ䤿Œ'(2'82JÁ°Õ즊ý…¯ˆÆ„ת¶£«‰±Öйˆ–Ôõ"é’¬¸ç¼‡Óª'(2'82ƒ˜Êã•ÿÊÙÚ·æúŸÌ½‚¡”‰àÉ®¶f"é’¬¸ç¼‡Óª"½šòÐ䤿Œ"Ÿ¡ªˆëôùÇ(282‚ÊùÑèÂóžàY·æúŸÌ½‚¡”‰àÉ®¶f"é’¬¸ç¼‡Óª"½šòÐ䤿Œ"Ÿ¡ªˆëôùÇ(282MÚËñ´ä‡¼Ä'ª ÿçû¤é刌ÓÜ¢¾ÌÊ"ûÄߪæþªâÚ(2 -82LŽÏ—ꤦݯ5ª ÿçû¤éåˆÇ®Â›ŸÊŸäâ"ª–¨Œ×ÿ¹¡k(2 -82Nø‚ÍÁœÆÚª ÿçû¤éåˆÿ‚¾žÃ“’§õ"÷‘–ä–Œ¿¯(2 -82LŸ¿ŽöåèÆ)ª ÿçû¤é刘ý˜‘¢ŒÐÀI"Ïä†÷ɯ(2 -82NþÝÒÿ§Ù¡±˜ª ÿçû¤éåˆì‘áÒîŸÙÍá"Ÿ¡ªˆëôùÇ(2 -82M³Ñ×饊ÿ¦ª ÿçû¤éåˆôé‹¿¿¸ßÄ"½šòÐ䤿Œ(2 -82M–ÎÜ‹ó¯Ë„ª ÿçû¤é刉±Öйˆ–Ôõ"é’¬¸ç¼‡Óª(2 -82ƒÍê´ýÞÁöª ÿçû¤é刔‰àÉ®¶f"é’¬¸ç¼‡Óª"½šòÐ䤿Œ"Ÿ¡ªˆëôùÇ(282,Ü›ä¤ö¸Åˆºª ÿçû¤éåˆÐÙ•°‹ÁÈy(282+Ù¼‡úýõ‰—êâÓ»ä¾ÙÙ9ÐÙ•°‹ÁÈy(282.ƒÚæË›Õ²“^âÓ»ä¾ÙÙ9ÐÙ•°‹ÁÈy(2 -82/òìšÀ·ÄŒÃ#óª·Žð–äÑŠÐÙ•°‹ÁÈy(2 -82+¢ØþËÄÝßíyóª·Žð–äÑŠÐÙ•°‹ÁÈy(282"йʢäÞ†#½³ úùš±KÐÙ•°‹ÁÈy(82I¾Œ…û±ªÓþν³ úùš±KŒÓÜ¢¾ÌÊ"ûÄߪæþªâÚ(282HÞûÅŠëŽî½³ úùš±KǮ›ŸÊŸäâ"ª–¨Œ×ÿ¹¡k(282HÚÇÍý›ôÍô_½³ úùš±Kÿ‚¾žÃ“’§õ"÷‘–ä–Œ¿¯(282Gá”ܰ§¼Üï{½³ úùš±K˜ý˜‘¢ŒÐÀI"Ïä†÷ɯ(282IŒú’ìÌšŒÿ½³ úùš±Kì‘áÒîŸÙÍá"Ÿ¡ªˆëôùÇ(282HðÒ¬¼ïˆâ½³ úùš±Kôé‹¿¿¸ßÄ"½šòÐ䤿Œ(282Hˆ¼ÂÁŒ‚ªŠE½³ úùš±K‰±Öйˆ–Ôõ"é’¬¸ç¼‡Óª(282 ðÚžºÖèà몖¨Œ×ÿ¹¡k(282"…ïèÙŸåóþÎûÄߪæþªâÚ(282!ÆÉ¨‡âž¯‰÷‘–ä–Œ¿¯(282"Õ™õê×Ì·›‹Ïä†÷ɯ(282!Þíó¾ó¿ÚªˆëôùÇ(282!ž¥ÌÔѾۛL½šòÐ䤿Œ(282!„±ÂææÄÁOé’¬¸ç¼‡Óª(282+¦‚ýª¬¢˜þǪ–¨Œ×ÿ¹¡kÐÙ•°‹ÁÈy(282+Ó®¿Œïɳ=ûÄߪæþªâÚÐÙ•°‹ÁÈy(282+ØŽ„Øø¨²c÷‘–ä–Œ¿¯ÐÙ•°‹ÁÈy(282,ýŒ®ê£ÀËŠÏä†÷ɯÐÙ•°‹ÁÈy(282+’ÐÝç’묩uŸ¡ªˆëôùÇÐÙ•°‹ÁÈy(282,ÌÅ„„ŒúÔÞ ½šòÐ䤿ŒÐÙ•°‹ÁÈy(282,ÿªØ¦ƒ®èý»é’¬¸ç¼‡ÓªÐÙ•°‹ÁÈy(282#©Ô†«¹ÛÈÊœä”Û›ÅËèÄÐÙ•°‹ÁÈy(82,‡¼ã™“ЧӜä”Û›ÅËèÄÐÙ•°‹ÁÈy(282#ëÜÄêÖª©ÞùÃýب°Ï=ÐÙ•°‹ÁÈy(82I¬Í˜ÝïÚÈéùÃýب°Ï=ŒÓÜ¢¾ÌÊ"ûÄߪæþªâÚ(282Hã;ܲÍË®øùÃýب°Ï=Ǯ›ŸÊŸäâ"ª–¨Œ×ÿ¹¡k(282IÌ™¹ý¢ÞúŒ…ùÃýب°Ï=ÿ‚¾žÃ“’§õ"÷‘–ä–Œ¿¯(282GÚ´¯Ðèà £YùÃýب°Ï=˜ý˜‘¢ŒÐÀI"Ïä†÷ɯ(282IƒÈæÑ€¸µ§µùÃýب°Ï=ì‘áÒîŸÙÍá"Ÿ¡ªˆëôùÇ(282Gׇã ÉÈùÃýب°Ï=ôé‹¿¿¸ßÄ"½šòÐ䤿Œ(282HÁµÏ›é¥¸ãUùÃýب°Ï=‰±Öйˆ–Ôõ"é’¬¸ç¼‡Óª(282"§±¿Œ·¾íé„ò݃‰…Ï™sÐÙ•°‹ÁÈy(82I®‰Å„ÿ··„ò݃‰…Ï™sŒÓÜ¢¾ÌÊ"ûÄߪæþªâÚ(282H°ªåÛòÿ™˜„ò݃‰…Ï™sǮ›ŸÊŸäâ"ª–¨Œ×ÿ¹¡k(282HÖïˆÇ÷ðî’ -„ò݃‰…Ï™sÿ‚¾žÃ“’§õ"÷‘–ä–Œ¿¯(282Gæü•о°‹„ò݃‰…Ï™s˜ý˜‘¢ŒÐÀI"Ïä†÷ɯ(282HáŒêË ûÜÝ„ò݃‰…Ï™sì‘áÒîŸÙÍá"Ÿ¡ªˆëôùÇ(282GµüŒºÊ¥„ò݃‰…Ï™sôé‹¿¿¸ßÄ"½šòÐ䤿Œ(282HÏææŸÞñ˜È'„ò݃‰…Ï™s‰±Öйˆ–Ôõ"é’¬¸ç¼‡Óª(282#䪧¸ëð»ÍÖ“®ß®þÏ¿ÐÙ•°‹ÁÈy(82HâÈòŸ‚Ϫ¶;“®ß®þÏ¿ŒÓÜ¢¾ÌÊ"ûÄߪæþªâÚ(282GÅöôŽåËà@“®ß®þϿǮ›ŸÊŸäâ"ª–¨Œ×ÿ¹¡k(282H±¤ñþÜÀ«‡-“®ß®þÏ¿ÿ‚¾žÃ“’§õ"÷‘–ä–Œ¿¯(282GЇ¶ÇéíËI“®ß®þÏ¿˜ý˜‘¢ŒÐÀI"Ïä†÷ɯ(282H¶ÛŽˆÊÜÇÔ -“®ß®þÏ¿ì‘áÒîŸÙÍá"Ÿ¡ªˆëôùÇ(282Gñå°ì’³¯Ç“®ß®þÏ¿ôé‹¿¿¸ßÄ"½šòÐ䤿Œ(282H¥×©ë¹ÖÕÅb“®ß®þÏ¿‰±Öйˆ–Ôõ"é’¬¸ç¼‡Óª(282#¼ù´–ºîª¡Ÿ¤øÝŽ£î“KÐÙ•°‹ÁÈy(82H…üÖ½ßÇЃqŸ¤øÝŽ£î“KŒÓÜ¢¾ÌÊ"ûÄߪæþªâÚ(282GÀê÷ÅÒ×´øŸ¤øÝŽ£î“KǮ›ŸÊŸäâ"ª–¨Œ×ÿ¹¡k(282Iô ¨øÍ¹€Ÿ¤øÝŽ£î“Kÿ‚¾žÃ“’§õ"÷‘–ä–Œ¿¯(282HÍÆýö®£ÏåËŸ¤øÝŽ£î“K˜ý˜‘¢ŒÐÀI"Ïä†÷ɯ(282H¿¾Š “ʽ៤øÝŽ£î“Kì‘áÒîŸÙÍá"Ÿ¡ªˆëôùÇ(282GºÉ¬¦“ÆçÜpŸ¤øÝŽ£î“Kôé‹¿¿¸ßÄ"½šòÐ䤿Œ(282H•úŸþî¯ø6Ÿ¤øÝŽ£î“K‰±Öйˆ–Ôõ"é’¬¸ç¼‡Óª(28:â鿨ŒÙœ®‘*:ÔØ’¯—꓌’ („:ðÿÈ€ÀíÐð© (¬:ì¹ó¢â¯¢ÚÌ (Ü:ôïÆ þ©ƒ„ø (À:õµ…çÈηŽB (¤
:¸Î½Èìù„ (:¶›èõÖð®÷m (è:¨ÆºÀõµÈï¹ (” -:ãï©Ê½˜š¡® (¼:¥Ã¯œí…â¥w (ø -:å½ÈÂÏò“Þó (ˆ:¶Æèܦ™¥ìß( -( -( -( -( -B.ª ÿçû¤éåˆ!ÂŒÝëð ¢Ý›ºô¿Úó¾k2 -B"󄽾؜ߞ}™ºŽÔ–í”"žÈ¶ž•ÞŠè( -B-âÓ»ä¾ÙÙ9!µêЊîÄa鮣öü©µ2 -B,·æúŸÌ½‚¡ûó⿼‘ÿÿƒ«ŸˆÇŽ¿¡ diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml deleted file mode 100644 index 60948a181a1b..000000000000 --- a/cmds/statsd/tools/dogfood/res/values/strings.xml +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> -<resources> - - <string name="app_name">Statsd Dogfood</string> - - <string name="statsd_running">Statsd Running</string> - <string name="statsd_not_running">Statsd NOT Running</string> - - <string name="push_config">Push baseline config</string> - <string name="set_receiver">Set pendingintent</string> - <string name="remove_receiver">Remove pendingintent</string> - - <string name="app_a_foreground">App A foreground</string> - <string name="app_b_foreground">App B foreground</string> - - - <string name="app_a_get_wl1">App A get wl_1</string> - <string name="app_a_release_wl1">App A release wl_1</string> - - <string name="app_a_get_wl2">App A get wl_2</string> - <string name="app_a_release_wl2">App A release wl_2</string> - - <string name="app_b_get_wl1">App B get wl_1</string> - <string name="app_b_release_wl1">App B release wl_1</string> - - <string name="app_b_get_wl2">App B get wl_2</string> - <string name="app_b_release_wl2">App B release wl_2</string> - - <string name="plug">Plug</string> - <string name="unplug">Unplug</string> - - <string name="screen_on">Screen On</string> - <string name="screen_off">Screen Off</string> - - <string name="custom_start">App hook start</string> - <string name="custom_stop">App hook stop</string> - - <string name="dump">DumpReport</string> - <string name="report_header">Report details</string> -</resources> diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java deleted file mode 100644 index b6b16e40a4b2..000000000000 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java +++ /dev/null @@ -1,158 +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. - */ -package com.android.statsd.dogfood; - -import android.text.format.DateFormat; - -import com.android.os.StatsLog; - -import java.util.List; - -public class DisplayProtoUtils { - public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReportList reports) { - sb.append("ConfigKey: "); - if (reports.hasConfigKey()) { - com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey(); - sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getId()) - .append("\n"); - } - - for (StatsLog.ConfigMetricsReport report : reports.getReportsList()) { - sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); - sb.append("Last report time:").append(getDateStr(report.getLastReportElapsedNanos())). - append("\n"); - sb.append("Current report time:").append(getDateStr(report.getCurrentReportElapsedNanos())). - append("\n"); - for (StatsLog.StatsLogReport log : report.getMetricsList()) { - sb.append("\n\n"); - sb.append("metric id: ").append(log.getMetricId()).append("\n"); - - switch (log.getDataCase()) { - case DURATION_METRICS: - sb.append("Duration metric data\n"); - displayDurationMetricData(sb, log); - break; - case EVENT_METRICS: - sb.append("Event metric data\n"); - displayEventMetricData(sb, log); - break; - case COUNT_METRICS: - sb.append("Count metric data\n"); - displayCountMetricData(sb, log); - break; - case GAUGE_METRICS: - sb.append("Gauge metric data\n"); - displayGaugeMetricData(sb, log); - break; - case VALUE_METRICS: - sb.append("Value metric data\n"); - displayValueMetricData(sb, log); - break; - case DATA_NOT_SET: - sb.append("No metric data\n"); - break; - } - } - } - } - - public static String getDateStr(long nanoSec) { - return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); - } - - private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) { - sb.append(dimensionValue.getField()).append(":"); - if (dimensionValue.hasValueBool()) { - sb.append(dimensionValue.getValueBool()); - } else if (dimensionValue.hasValueFloat()) { - sb.append(dimensionValue.getValueFloat()); - } else if (dimensionValue.hasValueInt()) { - sb.append(dimensionValue.getValueInt()); - } else if (dimensionValue.hasValueStr()) { - sb.append(dimensionValue.getValueStr()); - } else if (dimensionValue.hasValueTuple()) { - sb.append("{"); - for (StatsLog.DimensionsValue child : - dimensionValue.getValueTuple().getDimensionsValueList()) { - displayDimension(sb, child); - } - sb.append("}"); - } - sb.append(" "); - } - - public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper - = log.getDurationMetrics(); - sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); - for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { - sb.append("dimension_in_what: "); - displayDimension(sb, duration.getDimensionsInWhat()); - sb.append("\n"); - if (duration.hasDimensionsInCondition()) { - sb.append("dimension_in_condition: "); - displayDimension(sb, duration.getDimensionsInCondition()); - sb.append("\n"); - } - - for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { - sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-") - .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ") - .append(info.getDurationNanos()).append(" ns\n"); - } - } - } - - public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n"); - StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper = - log.getEventMetrics(); - for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) { - sb.append(getDateStr(event.getElapsedTimestampNanos())).append(": "); - sb.append(event.getAtom().getPushedCase().toString()).append("\n"); - } - } - - public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper - = log.getCountMetrics(); - sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); - for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { - sb.append("dimension_in_what: "); - displayDimension(sb, count.getDimensionsInWhat()); - sb.append("\n"); - if (count.hasDimensionsInCondition()) { - sb.append("dimension_in_condition: "); - displayDimension(sb, count.getDimensionsInCondition()); - sb.append("\n"); - } - - for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { - sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-") - .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ") - .append(info.getCount()).append("\n"); - } - } - } - - public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - sb.append("Display me!"); - } - - public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - sb.append("Display me!"); - } -} diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java deleted file mode 100644 index 4f4dd011e419..000000000000 --- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java +++ /dev/null @@ -1,361 +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. - */ -package com.android.statsd.dogfood; - -import android.app.Activity; -import android.app.PendingIntent; -import android.app.IntentService; -import android.app.StatsManager; -import android.app.StatsManager.StatsUnavailableException; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.Bundle; -import android.util.Log; -import android.util.StatsLog; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; -import android.os.IStatsManager; -import android.os.ServiceManager; - -import java.io.InputStream; - -import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport; - -public class MainActivity extends Activity { - private final static String TAG = "StatsdDogfood"; - private final static long CONFIG_ID = 987654321; - - final int[] mUids = {11111111, 2222222}; - StatsManager mStatsManager; - TextView mReportText; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_main); - - findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockAcquire(0, "wl_1"); - } - }); - - findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockAcquire(1, "wl_1"); - } - }); - - findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockAcquire(0, "wl_2"); - } - }); - - findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockAcquire(1, "wl_2"); - } - }); - - findViewById(R.id.app_a_wake_lock_release1).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockRelease(0, "wl_1"); - } - }); - - - findViewById(R.id.app_b_wake_lock_release1).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockRelease(1, "wl_1"); - } - }); - - findViewById(R.id.app_a_wake_lock_release2).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockRelease(0, "wl_2"); - } - }); - - - findViewById(R.id.app_b_wake_lock_release2).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - onWakeLockRelease(1, "wl_2"); - } - }); - - - findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, - StatsLog.PLUGGED_STATE_CHANGED__STATE__BATTERY_PLUGGED_AC); - } - }); - - findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, - StatsLog.PLUGGED_STATE_CHANGED__STATE__BATTERY_PLUGGED_NONE); - } - }); - - findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_ON); - } - }); - - findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF); - } - }); - - findViewById(R.id.custom_start).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatsLog.logStart(8); - } - }); - - findViewById(R.id.custom_stop).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatsLog.logStop(8); - } - }); - - mReportText = (TextView) findViewById(R.id.report_text); - - findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (!statsdRunning()) { - return; - } - if (mStatsManager != null) { - try { - byte[] data = mStatsManager.getReports(CONFIG_ID); - if (data != null) { - displayData(data); - return; - } - } catch (StatsUnavailableException e) { - Log.e(TAG, "Failed to get data from statsd", e); - } - mReportText.setText("Failed!"); - } - } - }); - - findViewById(R.id.push_config).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - try { - if (!statsdRunning()) { - return; - } - Resources res = getResources(); - InputStream inputStream = res.openRawResource(R.raw.statsd_baseline_config); - - byte[] config = new byte[inputStream.available()]; - inputStream.read(config); - if (mStatsManager != null) { - try { - mStatsManager.addConfig(CONFIG_ID, config); - Toast.makeText( - MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show(); - } catch (StatsUnavailableException | IllegalArgumentException e) { - Toast.makeText(MainActivity.this, "Config push FAILED!", - Toast.LENGTH_LONG).show(); - } - } - } catch (Exception e) { - Toast.makeText(MainActivity.this, "failed to read config", Toast.LENGTH_LONG); - } - } - }); - - PendingIntent pi = PendingIntent.getService(this, 0, - new Intent(this, ReceiverIntentService.class), PendingIntent.FLAG_UPDATE_CURRENT); - findViewById(R.id.set_receiver).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - try { - if (!statsdRunning()) { - return; - } - if (mStatsManager != null) { - try { - mStatsManager.setFetchReportsOperation(pi, CONFIG_ID); - Toast.makeText(MainActivity.this, - "Receiver specified to pending intent", Toast.LENGTH_LONG) - .show(); - } catch (StatsUnavailableException e) { - Toast.makeText(MainActivity.this, "Statsd did not set receiver", - Toast.LENGTH_LONG) - .show(); - } - } - } catch (Exception e) { - Toast.makeText(MainActivity.this, "failed to set receiver", Toast.LENGTH_LONG); - } - } - }); - findViewById(R.id.remove_receiver).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - try { - if (!statsdRunning()) { - return; - } - if (mStatsManager != null) { - try { - mStatsManager.setFetchReportsOperation(null, CONFIG_ID); - Toast.makeText(MainActivity.this, "Receiver remove", Toast.LENGTH_LONG) - .show(); - } catch (StatsUnavailableException e) { - Toast.makeText(MainActivity.this, "Statsd did not remove receiver", - Toast.LENGTH_LONG) - .show(); - } - } - } catch (Exception e) { - Toast.makeText( - MainActivity.this, "failed to remove receiver", Toast.LENGTH_LONG); - } - } - }); - mStatsManager = (StatsManager) getSystemService("stats"); - } - - private boolean statsdRunning() { - if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) { - Log.d(TAG, "Statsd not running"); - Toast.makeText(MainActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show(); - return false; - } - return true; - } - - @Override - public void onNewIntent(Intent intent) { - Log.d(TAG, "new intent: " + intent.getIntExtra("pkg", 0)); - int pkg = intent.getIntExtra("pkg", 0); - String name = intent.getStringExtra("name"); - if (intent.hasExtra("acquire")) { - onWakeLockAcquire(pkg, name); - } else if (intent.hasExtra("release")) { - onWakeLockRelease(pkg, name); - } - } - - private void displayData(byte[] data) { - com.android.os.StatsLog.ConfigMetricsReportList reports = null; - boolean good = false; - if (data != null) { - try { - reports = com.android.os.StatsLog.ConfigMetricsReportList.parseFrom(data); - good = true; - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - // display it in the text view. - } - } - int size = data == null ? 0 : data.length; - StringBuilder sb = new StringBuilder(); - sb.append(good ? "Proto parsing OK!" : "Proto parsing Error!"); - sb.append(" size:").append(size).append("\n"); - - if (good && reports != null) { - displayLogReport(sb, reports); - mReportText.setText(sb.toString()); - } - } - - - private void onWakeLockAcquire(int id, String name) { - if (id > 1) { - Log.d(TAG, "invalid pkg id"); - return; - } - int[] uids = new int[]{mUids[id]}; - String[] tags = new String[]{"acquire"}; - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, - StatsLog.WAKELOCK_STATE_CHANGED__TYPE__PARTIAL_WAKE_LOCK, name, - StatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE); - StringBuilder sb = new StringBuilder(); - sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) - .append(", ").append(name).append(", 1);"); - Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show(); - } - - private void onWakeLockRelease(int id, String name) { - if (id > 1) { - Log.d(TAG, "invalid pkg id"); - return; - } - int[] uids = new int[]{mUids[id]}; - String[] tags = new String[]{"release"}; - StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, - StatsLog.WAKELOCK_STATE_CHANGED__TYPE__PARTIAL_WAKE_LOCK, name, - StatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE); - StringBuilder sb = new StringBuilder(); - sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0) - .append(", ").append(name).append(", 0);"); - Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show(); - } - - public static class ReceiverIntentService extends IntentService { - public ReceiverIntentService() { - super("ReceiverIntentService"); - } - - /** - * The IntentService calls this method from the default worker thread with - * the intent that started the service. When this method returns, IntentService - * stops the service, as appropriate. - */ - @Override - protected void onHandleIntent(Intent intent) { - Log.i(TAG, "Received notification that we should call getData"); - } - } -} diff --git a/cmds/statsd/tools/loadtest/Android.bp b/cmds/statsd/tools/loadtest/Android.bp deleted file mode 100644 index bf87fc51dce1..000000000000 --- a/cmds/statsd/tools/loadtest/Android.bp +++ /dev/null @@ -1,37 +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. -// -// - -android_app { - name: "StatsdLoadtest", - platform_apis: true, - - srcs: ["src/**/*.java"], - - resource_dirs: ["res"], - static_libs: [ - "platformprotoslite", - "statsdprotolite", - ], - - certificate: "platform", - privileged: true, - dex_preopt: { - enabled: false, - }, - optimize: { - enabled: false, - }, -} diff --git a/cmds/statsd/tools/loadtest/AndroidManifest.xml b/cmds/statsd/tools/loadtest/AndroidManifest.xml deleted file mode 100644 index 2bf8ca95d846..000000000000 --- a/cmds/statsd/tools/loadtest/AndroidManifest.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.statsd.loadtest" - android:sharedUserId="android.uid.system" - android:versionCode="1" - android:versionName="1.0" > - - <uses-permission android:name="android.permission.DUMP" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <application - android:allowBackup="true" - android:icon="@drawable/ic_launcher" - android:label="@string/app_name" > - <activity - android:name=".LoadtestActivity" - android:label="@string/app_name" - android:launchMode="singleTop" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - <receiver android:name=".LoadtestActivity$PusherAlarmReceiver" /> - <receiver android:name=".LoadtestActivity$StopperAlarmReceiver"/> - <receiver android:name=".PerfData$PerfAlarmReceiver"/> - </application> -</manifest> diff --git a/cmds/statsd/tools/loadtest/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-hdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 55621cc1074f..000000000000 --- a/cmds/statsd/tools/loadtest/res/drawable-hdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/loadtest/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-mdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 11ec2068be19..000000000000 --- a/cmds/statsd/tools/loadtest/res/drawable-mdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/loadtest/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-xhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 7c02b784aa5d..000000000000 --- a/cmds/statsd/tools/loadtest/res/drawable-xhdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/loadtest/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/loadtest/res/drawable-xxhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 915d91441349..000000000000 --- a/cmds/statsd/tools/loadtest/res/drawable-xxhdpi/ic_launcher.png +++ /dev/null diff --git a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml b/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml deleted file mode 100644 index d6f804734385..000000000000 --- a/cmds/statsd/tools/loadtest/res/layout/activity_loadtest.xml +++ /dev/null @@ -1,208 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <LinearLayout - android:id="@+id/outside" - android:clickable="true" - android:focusable="true" - android:focusableInTouchMode="true" - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginRight="10dp" - android:layout_marginLeft="10dp" - android:orientation="vertical"> - <requestFocus /> - - <LinearLayout - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - <TextView - android:textSize="30dp" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="@string/replication_label" /> - <EditText - android:id="@+id/replication" - android:inputType="number" - android:layout_weight="1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:maxLength="4" - android:text="@integer/replication_default" - android:textSize="30dp"/> - </LinearLayout> - - <LinearLayout - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - <TextView - android:textSize="30dp" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="@string/bucket_label" /> - <Spinner - android:id="@+id/bucket_spinner" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:prompt="@string/bucket_label"/> - </LinearLayout> - - <LinearLayout - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - <TextView - android:textSize="30dp" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="@string/period_label" /> - <EditText - android:id="@+id/period" - android:inputType="number" - android:layout_weight="1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:maxLength="3" - android:text="@integer/period_default" - android:textSize="30dp"/> - </LinearLayout> - - <LinearLayout - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - <TextView - android:textSize="30dp" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="@string/burst_label" /> - <EditText - android:id="@+id/burst" - android:inputType="number" - android:layout_weight="1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:maxLength="4" - android:text="@integer/burst_default" - android:textSize="30dp"/> - </LinearLayout> - - <LinearLayout - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - <TextView - android:textSize="30dp" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="@string/duration_label" /> - <EditText - android:id="@+id/duration" - android:inputType="number" - android:layout_weight="1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:maxLength="4" - android:text="@integer/duration_default" - android:textSize="30dp"/> - </LinearLayout> - <CheckBox - android:id="@+id/placebo" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/placebo" - android:checked="false" /> - - <LinearLayout - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <CheckBox - android:id="@+id/include_count" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/count" - android:checked="true"/> - <CheckBox - android:id="@+id/include_duration" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/duration" - android:checked="true"/> - <CheckBox - android:id="@+id/include_event" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/event" - android:checked="true"/> - <CheckBox - android:id="@+id/include_value" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/value" - android:checked="true"/> - <CheckBox - android:id="@+id/include_gauge" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/gauge" - android:checked="true"/> - </LinearLayout> - - <Space - android:layout_width="1dp" - android:layout_height="30dp"/> - - <Button - android:id="@+id/start_stop" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="#ffff0000" - android:text="@string/start" - android:textSize="50dp"/> - - <Space - android:layout_width="1dp" - android:layout_height="30dp"/> - - <Space - android:layout_width="1dp" - android:layout_height="30dp"/> - - <TextView - android:id="@+id/report_text" - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - </LinearLayout> - -</ScrollView> diff --git a/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml b/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml deleted file mode 100644 index b03da06f7a77..000000000000 --- a/cmds/statsd/tools/loadtest/res/layout/spinner_item.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<TextView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textSize="30dp" - android:gravity="left" - android:padding="5dip" - /> diff --git a/cmds/statsd/tools/loadtest/res/raw/loadtest_config b/cmds/statsd/tools/loadtest/res/raw/loadtest_config Binary files differdeleted file mode 100755 index 24221908cbeb..000000000000 --- a/cmds/statsd/tools/loadtest/res/raw/loadtest_config +++ /dev/null diff --git a/cmds/statsd/tools/loadtest/res/values/integers.xml b/cmds/statsd/tools/loadtest/res/values/integers.xml deleted file mode 100644 index c2407d3b85f2..000000000000 --- a/cmds/statsd/tools/loadtest/res/values/integers.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> -<resources> - <integer name="burst_default">1</integer> - <integer name="period_default">2</integer> - <integer name="replication_default">1</integer> - <integer name="duration_default">240</integer> -</resources> diff --git a/cmds/statsd/tools/loadtest/res/values/strings.xml b/cmds/statsd/tools/loadtest/res/values/strings.xml deleted file mode 100644 index e8ae3f82a7e3..000000000000 --- a/cmds/statsd/tools/loadtest/res/values/strings.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> -<resources> - <string name="app_name">Statsd Loadtest</string> - <string name="bucket_label">bucket size (mins): </string> - <string name="burst_label">burst: </string> - <string name="bucket_default">FIVE_MINUTES</string> - <string name="placebo">placebo</string> - <string name="period_label">logging period (secs): </string> - <string name="replication_label">metric replication: </string> - <string name="duration_label">test duration (mins): </string> - <string name="start">  Start  </string> - <string name="stop">  Stop  </string> - <string name="count"> count </string> - <string name="duration"> duration </string> - <string name="event"> event </string> - <string name="value"> value </string> - <string name="gauge"> gauge </string> - -</resources> diff --git a/cmds/statsd/tools/loadtest/run_loadtest.sh b/cmds/statsd/tools/loadtest/run_loadtest.sh deleted file mode 100755 index 3c93a0613183..000000000000 --- a/cmds/statsd/tools/loadtest/run_loadtest.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/sh -# -# Script that measures statsd's PSS under an increasing number of metrics. - -# Globals. -pss="" -pid="" - -# Starts the loadtest. -start_loadtest() { - echo "Starting loadtest" - adb shell am start -n com.android.statsd.loadtest/.LoadtestActivity --es "type" "start" -} - -# Stops the loadtest. -stop_loadtest() { - echo "Stopping loadtest" - adb shell am start -n com.android.statsd.loadtest/.LoadtestActivity --es "type" "stop" -} - -# Sets the metrics replication. -# Arguments: -# $1: The replication factor. -set_replication() { - adb shell am start -n com.android.statsd.loadtest/.LoadtestActivity --es "type" "set_replication" --ei "replication" "${1}" - echo "Replication set to ${1}" -} - -# Reads statsd's pid and PSS. -update_pid_and_pss() { - # Command that reads the PSS for statsd. This also gives us its pid. - get_mem=$(adb shell dumpsys meminfo |grep statsd) - # Looks for statsd's pid. - regex="([0-9,]+)K: statsd \(pid ([0-9]+)\).*" - if [[ $get_mem =~ $regex ]]; then - pss=$(echo "${BASH_REMATCH[1]}" | tr -d , | sed 's/\.//g') - pid=$(echo "${BASH_REMATCH[2]}") - else - echo $cmd doesnt match $regex - fi -} - -# Kills statsd. -# Assumes the pid has been set. -kill_statsd() { - echo "Killing statsd (pid ${pid})" - adb shell kill -9 "${pid}" -} - -# Main loop. -main() { - start_time=$(date +%s) - values=() - stop_loadtest - - echo "" - echo "********************* NEW LOADTEST ************************" - update_pid_and_pss - for replication in 1 2 4 8 16 32 64 128 256 512 1024 2048 4096 - do - echo "**** Starting test at replication ${replication} ****" - - # (1) Restart statsd. This will ensure its state is empty. - kill_statsd - sleep 3 # wait a bit for it to restart - update_pid_and_pss - echo "Before the test, statsd's PSS is ${pss}" - - # (2) Set the replication. - set_replication "${replication}" - sleep 1 # wait a bit - - # (3) Start the loadtest. - start_loadtest - - # (4) Wait several seconds, then read the PSS. - sleep 100 && update_pid_and_pss - echo "During the test, statsd's PSS is ${pss}" - values+=(${pss}) - - echo "Values: ${values[@]}" - - # (5) Stop loadtest. - stop_loadtest - sleep 2 - - echo "" - done - - end_time=$(date +%s) - echo "Completed loadtest in $((${end_time} - ${start_time})) seconds." - - values_as_str=$(IFS=$'\n'; echo "${values[*]}") - echo "The PSS values are:" - echo "${values_as_str}" - echo "" -} - -main diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java deleted file mode 100644 index bab0c1e3f540..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryDataRecorder.java +++ /dev/null @@ -1,56 +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. - */ -package com.android.statsd.loadtest; - -import android.annotation.Nullable; -import android.content.Context; -import android.util.Log; -import com.android.internal.os.StatsdConfigProto.TimeUnit; -import java.text.ParseException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class BatteryDataRecorder extends PerfDataRecorder { - private static final String TAG = "loadtest.BatteryDataRecorder"; - private static final String DUMP_FILENAME = TAG + "_dump.tmp"; - - public BatteryDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs, - int burst, boolean includeCountMetric, boolean includeDurationMetric, - boolean includeEventMetric, boolean includeValueMetric, boolean includeGaugeMetric) { - super(placebo, replication, bucket, periodSecs, burst, includeCountMetric, - includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric); - } - - @Override - public void startRecording(Context context) { - // Reset batterystats. - runDumpsysStats(context, DUMP_FILENAME, "batterystats", "--reset"); - } - - @Override - public void onAlarm(Context context) { - // Nothing to do as for battery, the whole data is in the final dumpsys call. - } - - @Override - public void stopRecording(Context context) { - StringBuilder sb = new StringBuilder(); - // Don't use --checkin. - runDumpsysStats(context, DUMP_FILENAME, "batterystats"); - readDumpData(context, DUMP_FILENAME, new BatteryStatsParser(), sb); - writeData(context, "battery_", "time,battery_level", sb); - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java deleted file mode 100644 index 203d97acefd8..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/BatteryStatsParser.java +++ /dev/null @@ -1,113 +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. - */ -package com.android.statsd.loadtest; - -import android.annotation.Nullable; -import android.util.Log; -import java.text.ParseException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class BatteryStatsParser implements PerfParser { - - private static final Pattern LINE_PATTERN = - Pattern.compile("\\s*\\+*(\\S*)\\s\\(\\d+\\)\\s(\\d\\d\\d)\\s.*"); - private static final Pattern TIME_PATTERN = - Pattern.compile("(\\d+)?(h)?(\\d+)?(m)?(\\d+)?(s)?(\\d+)?(ms)?"); - private static final String TAG = "loadtest.BatteryStatsParser"; - - private boolean mHistoryStarted; - private boolean mHistoryEnded; - - public BatteryStatsParser() { - } - - @Override - @Nullable - public String parseLine(String line) { - if (mHistoryEnded) { - return null; - } - if (!mHistoryStarted) { - if (line.contains("Battery History")) { - mHistoryStarted = true; - } - return null; - } - if (line.isEmpty()) { - mHistoryEnded = true; - return null; - } - Matcher lineMatcher = LINE_PATTERN.matcher(line); - if (lineMatcher.find() && lineMatcher.group(1) != null && lineMatcher.group(2) != null) { - if (lineMatcher.group(1).equals("0")) { - return "0," + lineMatcher.group(2) + "\n"; - } else { - Matcher timeMatcher = TIME_PATTERN.matcher(lineMatcher.group(1)); - if (timeMatcher.find()) { - Long time = getTime(lineMatcher.group(1)); - if (time != null) { - return time + "," + lineMatcher.group(2) + "\n"; - } else { - return null; // bad time - } - } else { - return null; // bad or no time - } - } - } - return null; - } - - @Nullable - private Long getTime(String group) { - if ("0".equals(group)) { - return 0L; - } - Matcher timeMatcher = TIME_PATTERN.matcher(group); - if (!timeMatcher.find()) { - return null; - } - - // Get rid of "ms". - String[] matches = group.split("ms", -1); - if (matches.length > 1) { - group = matches[0]; - } - - long time = 0L; - matches = group.split("h"); - if (matches.length > 1) { - time += Long.parseLong(matches[0]) * 60 * 60 * 1000; // hours - group = matches[1]; - } - matches = group.split("m"); - if (matches.length > 1) { - time += Long.parseLong(matches[0]) * 60 * 1000; // minutes - group = matches[1]; - } - matches = group.split("s"); - if (matches.length > 1) { - time += Long.parseLong(matches[0]) * 1000; // seconds - group = matches[1]; - } - - if (!group.isEmpty()) { - time += Long.parseLong(group); // milliseconds - } - return time; - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java deleted file mode 100644 index 2e0161be8096..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ConfigFactory.java +++ /dev/null @@ -1,314 +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. - */ -package com.android.statsd.loadtest; - -import android.content.Context; -import android.content.res.Resources; -import android.util.Log; - -import com.android.internal.os.StatsdConfigProto.Predicate; -import com.android.internal.os.StatsdConfigProto.CountMetric; -import com.android.internal.os.StatsdConfigProto.DurationMetric; -import com.android.internal.os.StatsdConfigProto.MetricConditionLink; -import com.android.internal.os.StatsdConfigProto.EventMetric; -import com.android.internal.os.StatsdConfigProto.GaugeMetric; -import com.android.internal.os.StatsdConfigProto.ValueMetric; -import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; -import com.android.internal.os.StatsdConfigProto.AtomMatcher; -import com.android.internal.os.StatsdConfigProto.SimplePredicate; -import com.android.internal.os.StatsdConfigProto.StatsdConfig; -import com.android.internal.os.StatsdConfigProto.TimeUnit; - -import java.io.InputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * Creates StatsdConfig protos for loadtesting. - */ -public class ConfigFactory { - public static class ConfigMetadata { - public final byte[] bytes; - public final int numMetrics; - - public ConfigMetadata(byte[] bytes, int numMetrics) { - this.bytes = bytes; - this.numMetrics = numMetrics; - } - } - - public static final long CONFIG_ID = 123456789; - - private static final String TAG = "loadtest.ConfigFactory"; - - private final StatsdConfig mTemplate; - - public ConfigFactory(Context context) { - // Read the config template from the resoures. - Resources res = context.getResources(); - byte[] template = null; - StatsdConfig templateProto = null; - try { - InputStream inputStream = res.openRawResource(R.raw.loadtest_config); - template = new byte[inputStream.available()]; - inputStream.read(template); - templateProto = StatsdConfig.parseFrom(template); - } catch (IOException e) { - Log.e(TAG, "Unable to read or parse loadtest config template. Using an empty config."); - } - mTemplate = templateProto == null ? StatsdConfig.newBuilder().build() : templateProto; - - Log.d(TAG, "Loadtest template config: " + mTemplate); - } - - /** - * Generates a config. - * - * All configs are based on the same template. - * That template is designed to make the most use of the set of atoms that {@code SequencePusher} - * pushes, and to exercise as many of the metrics features as possible. - * Furthermore, by passing a replication factor to this method, one can artificially inflate - * the number of metrics in the config. One can also adjust the bucket size for aggregate - * metrics. - * - * @param replication The number of times each metric is replicated in the config. - * If the config template has n metrics, the generated config will have n * replication - * ones - * @param bucketMillis The bucket size, in milliseconds, for aggregate metrics - * @param placebo If true, only return an empty config - * @return The serialized config and the number of metrics. - */ - public ConfigMetadata getConfig(int replication, TimeUnit bucket, boolean placebo, - boolean includeCount, boolean includeDuration, boolean includeEvent, - boolean includeValue, boolean includeGauge) { - StatsdConfig.Builder config = StatsdConfig.newBuilder() - .setId(CONFIG_ID); - if (placebo) { - replication = 0; // Config will be empty, aside from a name. - } - int numMetrics = 0; - for (int i = 0; i < replication; i++) { - // metrics - if (includeEvent) { - for (EventMetric metric : mTemplate.getEventMetricList()) { - addEventMetric(metric, i, config); - numMetrics++; - } - } - if (includeCount) { - for (CountMetric metric : mTemplate.getCountMetricList()) { - addCountMetric(metric, i, bucket, config); - numMetrics++; - } - } - if (includeDuration) { - for (DurationMetric metric : mTemplate.getDurationMetricList()) { - addDurationMetric(metric, i, bucket, config); - numMetrics++; - } - } - if (includeGauge) { - for (GaugeMetric metric : mTemplate.getGaugeMetricList()) { - addGaugeMetric(metric, i, bucket, config); - numMetrics++; - } - } - if (includeValue) { - for (ValueMetric metric : mTemplate.getValueMetricList()) { - addValueMetric(metric, i, bucket, config); - numMetrics++; - } - } - // predicates - for (Predicate predicate : mTemplate.getPredicateList()) { - addPredicate(predicate, i, config); - } - // matchers - for (AtomMatcher matcher : mTemplate.getAtomMatcherList()) { - addMatcher(matcher, i, config); - } - } - - Log.d(TAG, "Loadtest config is : " + config.build()); - Log.d(TAG, "Generated config has " + numMetrics + " metrics"); - - return new ConfigMetadata(config.build().toByteArray(), numMetrics); - } - - /** - * Creates {@link MetricConditionLink}s that are identical to the one passed to this method, - * except that the names are appended with the provided suffix. - */ - private List<MetricConditionLink> getLinks( - List<MetricConditionLink> links, int suffix) { - List<MetricConditionLink> newLinks = new ArrayList(); - for (MetricConditionLink link : links) { - newLinks.add(link.toBuilder() - .setCondition(link.getCondition() + suffix) - .build()); - } - return newLinks; - } - - /** - * Creates an {@link EventMetric} based on the template. Makes sure that all names are appended - * with the provided suffix. Then adds that metric to the config. - */ - private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) { - EventMetric.Builder metric = template.toBuilder() - .setId(template.getId() + suffix) - .setWhat(template.getWhat() + suffix); - if (template.hasCondition()) { - metric.setCondition(template.getCondition() + suffix); - } - if (template.getLinksCount() > 0) { - List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix); - metric.clearLinks(); - metric.addAllLinks(links); - } - config.addEventMetric(metric); - } - - /** - * Creates a {@link CountMetric} based on the template. Makes sure that all names are appended - * with the provided suffix, and overrides the bucket size. Then adds that metric to the config. - */ - private void addCountMetric(CountMetric template, int suffix, TimeUnit bucket, - StatsdConfig.Builder config) { - CountMetric.Builder metric = template.toBuilder() - .setId(template.getId() + suffix) - .setWhat(template.getWhat() + suffix); - if (template.hasCondition()) { - metric.setCondition(template.getCondition() + suffix); - } - if (template.getLinksCount() > 0) { - List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix); - metric.clearLinks(); - metric.addAllLinks(links); - } - metric.setBucket(bucket); - config.addCountMetric(metric); - } - - /** - * Creates a {@link DurationMetric} based on the template. Makes sure that all names are appended - * with the provided suffix, and overrides the bucket size. Then adds that metric to the config. - */ - private void addDurationMetric(DurationMetric template, int suffix, TimeUnit bucket, - StatsdConfig.Builder config) { - DurationMetric.Builder metric = template.toBuilder() - .setId(template.getId() + suffix) - .setWhat(template.getWhat() + suffix); - if (template.hasCondition()) { - metric.setCondition(template.getCondition() + suffix); - } - if (template.getLinksCount() > 0) { - List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix); - metric.clearLinks(); - metric.addAllLinks(links); - } - metric.setBucket(bucket); - config.addDurationMetric(metric); - } - - /** - * Creates a {@link GaugeMetric} based on the template. Makes sure that all names are appended - * with the provided suffix, and overrides the bucket size. Then adds that metric to the config. - */ - private void addGaugeMetric(GaugeMetric template, int suffix, TimeUnit bucket, - StatsdConfig.Builder config) { - GaugeMetric.Builder metric = template.toBuilder() - .setId(template.getId() + suffix) - .setWhat(template.getWhat() + suffix); - if (template.hasCondition()) { - metric.setCondition(template.getCondition() + suffix); - } - if (template.getLinksCount() > 0) { - List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix); - metric.clearLinks(); - metric.addAllLinks(links); - } - metric.setBucket(bucket); - config.addGaugeMetric(metric); - } - - /** - * Creates a {@link ValueMetric} based on the template. Makes sure that all names are appended - * with the provided suffix, and overrides the bucket size. Then adds that metric to the config. - */ - private void addValueMetric(ValueMetric template, int suffix, TimeUnit bucket, - StatsdConfig.Builder config) { - ValueMetric.Builder metric = template.toBuilder() - .setId(template.getId() + suffix) - .setWhat(template.getWhat() + suffix); - if (template.hasCondition()) { - metric.setCondition(template.getCondition() + suffix); - } - if (template.getLinksCount() > 0) { - List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix); - metric.clearLinks(); - metric.addAllLinks(links); - } - metric.setBucket(bucket); - config.addValueMetric(metric); - } - - /** - * Creates a {@link Predicate} based on the template. Makes sure that all names - * are appended with the provided suffix. Then adds that predicate to the config. - */ - private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) { - Predicate.Builder predicate = template.toBuilder() - .setId(template.getId() + suffix); - if (template.hasCombination()) { - Predicate.Combination.Builder cb = template.getCombination().toBuilder() - .clearPredicate(); - for (long child : template.getCombination().getPredicateList()) { - cb.addPredicate(child + suffix); - } - predicate.setCombination(cb.build()); - } - if (template.hasSimplePredicate()) { - SimplePredicate.Builder sc = template.getSimplePredicate().toBuilder() - .setStart(template.getSimplePredicate().getStart() + suffix) - .setStop(template.getSimplePredicate().getStop() + suffix); - if (template.getSimplePredicate().hasStopAll()) { - sc.setStopAll(template.getSimplePredicate().getStopAll() + suffix); - } - predicate.setSimplePredicate(sc.build()); - } - config.addPredicate(predicate); - } - - /** - * Creates a {@link AtomMatcher} based on the template. Makes sure that all names - * are appended with the provided suffix. Then adds that matcher to the config. - */ - private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) { - AtomMatcher.Builder matcher = template.toBuilder() - .setId(template.getId() + suffix); - if (template.hasCombination()) { - AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder() - .clearMatcher(); - for (long child : template.getCombination().getMatcherList()) { - cb.addMatcher(child + suffix); - } - matcher.setCombination(cb); - } - config.addAtomMatcher(matcher); - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java deleted file mode 100644 index d55f3f31fd9f..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/DisplayProtoUtils.java +++ /dev/null @@ -1,169 +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. - */ -package com.android.statsd.loadtest; - -import android.text.format.DateFormat; - -import com.android.os.StatsLog; - -import java.util.List; - -public class DisplayProtoUtils { - private static final int MAX_NUM_METRICS_TO_DISPLAY = 10; - - public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReportList reports) { - sb.append("******************** Report ********************\n"); - if (reports.hasConfigKey()) { - sb.append("ConfigKey: "); - com.android.os.StatsLog.ConfigMetricsReportList.ConfigKey key = reports.getConfigKey(); - sb.append("\tuid: ").append(key.getUid()).append(" id: ").append(key.getId()) - .append("\n"); - } - - int numMetrics = 0; - for (StatsLog.ConfigMetricsReport report : reports.getReportsList()) { - sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n"); - sb.append("Last report time:").append(getDateStr(report.getLastReportElapsedNanos())). - append("\n"); - sb.append("Current report time:").append(getDateStr(report.getCurrentReportElapsedNanos())). - append("\n"); - for (StatsLog.StatsLogReport log : report.getMetricsList()) { - numMetrics++; - if (numMetrics > MAX_NUM_METRICS_TO_DISPLAY) { - sb.append("... output truncated\n"); - sb.append("************************************************"); - return; - } - sb.append("\n"); - sb.append("metric id: ").append(log.getMetricId()).append("\n"); - - switch (log.getDataCase()) { - case DURATION_METRICS: - sb.append("Duration metric data\n"); - displayDurationMetricData(sb, log); - break; - case EVENT_METRICS: - sb.append("Event metric data\n"); - displayEventMetricData(sb, log); - break; - case COUNT_METRICS: - sb.append("Count metric data\n"); - displayCountMetricData(sb, log); - break; - case GAUGE_METRICS: - sb.append("Gauge metric data\n"); - displayGaugeMetricData(sb, log); - break; - case VALUE_METRICS: - sb.append("Value metric data\n"); - displayValueMetricData(sb, log); - break; - case DATA_NOT_SET: - sb.append("No metric data\n"); - break; - } - } - } - sb.append("************************************************"); - } - - public static String getDateStr(long nanoSec) { - return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString(); - } - - private static void displayDimension(StringBuilder sb, StatsLog.DimensionsValue dimensionValue) { - sb.append(dimensionValue.getField()).append(":"); - if (dimensionValue.hasValueBool()) { - sb.append(dimensionValue.getValueBool()); - } else if (dimensionValue.hasValueFloat()) { - sb.append(dimensionValue.getValueFloat()); - } else if (dimensionValue.hasValueInt()) { - sb.append(dimensionValue.getValueInt()); - } else if (dimensionValue.hasValueStr()) { - sb.append(dimensionValue.getValueStr()); - } else if (dimensionValue.hasValueTuple()) { - sb.append("{"); - for (StatsLog.DimensionsValue child : - dimensionValue.getValueTuple().getDimensionsValueList()) { - displayDimension(sb, child); - } - sb.append("}"); - } - sb.append(" "); - } - - public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper - = log.getDurationMetrics(); - sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n"); - for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) { - sb.append("dimension_in_what: "); - displayDimension(sb, duration.getDimensionsInWhat()); - sb.append("\n"); - if (duration.hasDimensionsInCondition()) { - sb.append("dimension_in_condition: "); - displayDimension(sb, duration.getDimensionsInCondition()); - sb.append("\n"); - } - - for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) { - sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-") - .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ") - .append(info.getDurationNanos()).append(" ns\n"); - } - } - } - - public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n"); - StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper = - log.getEventMetrics(); - for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) { - sb.append(getDateStr(event.getElapsedTimestampNanos())).append(": "); - sb.append(event.getAtom().getPushedCase().toString()).append("\n"); - } - } - - public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper - = log.getCountMetrics(); - sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n"); - for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) { - sb.append("dimension_in_what: "); - displayDimension(sb, count.getDimensionsInWhat()); - sb.append("\n"); - if (count.hasDimensionsInCondition()) { - sb.append("dimension_in_condition: "); - displayDimension(sb, count.getDimensionsInCondition()); - sb.append("\n"); - } - - for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) { - sb.append("\t[").append(getDateStr(info.getStartBucketElapsedNanos())).append("-") - .append(getDateStr(info.getEndBucketElapsedNanos())).append("] -> ") - .append(info.getCount()).append("\n"); - } - } - } - - public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - sb.append("Display me!"); - } - - public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) { - sb.append("Display me!"); - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java deleted file mode 100644 index 769f78c726e8..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java +++ /dev/null @@ -1,756 +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. - */ -package com.android.statsd.loadtest; - -import android.annotation.Nullable; -import android.app.Activity; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.StatsManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.os.Bundle; -import android.os.Handler; -import android.os.IStatsManager; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.util.StatsLog; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.view.MotionEvent; -import android.view.View.OnFocusChangeListener; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.os.StatsLog.ConfigMetricsReport; -import com.android.os.StatsLog.ConfigMetricsReportList; -import com.android.os.StatsLog.StatsdStatsReport; -import com.android.internal.os.StatsdConfigProto.TimeUnit; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Runs a load test for statsd. - * How it works: - * <ul> - * <li> Sets up and pushes a custom config with metrics that exercise a large swath of code paths. - * <li> Periodically logs certain atoms into logd. - * <li> Impact on battery can be printed to logcat, or a bug report can be filed and analyzed - * in battery Historian. - * </ul> - * The load depends on how demanding the config is, as well as how frequently atoms are pushsed - * to logd. Those are all controlled by 4 adjustable parameters: - * <ul> - * <li> The 'replication' parameter artificially multiplies the number of metrics in the config. - * <li> The bucket size controls the time-bucketing the aggregate metrics. - * <li> The period parameter controls how frequently atoms are pushed to logd. - * <li> The 'burst' parameter controls how many atoms are pushed at the same time (per period). - * </ul> - */ -public class LoadtestActivity extends Activity implements AdapterView.OnItemSelectedListener { - - private static final String TAG = "loadtest.LoadtestActivity"; - public static final String TYPE = "type"; - private static final String PUSH_ALARM = "push_alarm"; - public static final String PERF_ALARM = "perf_alarm"; - private static final String SET_REPLICATION = "set_replication"; - private static final String REPLICATION = "replication"; - private static final String START = "start"; - private static final String STOP = "stop"; - private static final Map<String, TimeUnit> TIME_UNIT_MAP = initializeTimeUnitMap(); - private static final List<String> TIME_UNIT_LABELS = initializeTimeUnitLabels(); - - public final static class PusherAlarmReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Intent activityIntent = new Intent(context, LoadtestActivity.class); - activityIntent.putExtra(TYPE, PUSH_ALARM); - context.startActivity(activityIntent); - } - } - - public final static class StopperAlarmReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Intent activityIntent = new Intent(context, LoadtestActivity.class); - activityIntent.putExtra(TYPE, STOP); - context.startActivity(activityIntent); - } - } - - private static Map<String, TimeUnit> initializeTimeUnitMap() { - Map<String, TimeUnit> labels = new HashMap(); - labels.put("1m", TimeUnit.ONE_MINUTE); - labels.put("5m", TimeUnit.FIVE_MINUTES); - labels.put("10m", TimeUnit.TEN_MINUTES); - labels.put("30m", TimeUnit.THIRTY_MINUTES); - labels.put("1h", TimeUnit.ONE_HOUR); - labels.put("3h", TimeUnit.THREE_HOURS); - labels.put("6h", TimeUnit.SIX_HOURS); - labels.put("12h", TimeUnit.TWELVE_HOURS); - labels.put("1d", TimeUnit.ONE_DAY); - labels.put("1s", TimeUnit.CTS); - return labels; - } - - private static List<String> initializeTimeUnitLabels() { - List<String> labels = new ArrayList(); - labels.add("1s"); - labels.add("1m"); - labels.add("5m"); - labels.add("10m"); - labels.add("30m"); - labels.add("1h"); - labels.add("3h"); - labels.add("6h"); - labels.add("12h"); - labels.add("1d"); - return labels; - } - - private AlarmManager mAlarmMgr; - - /** - * Used to periodically log atoms to logd. - */ - private PendingIntent mPushPendingIntent; - - /** - * Used to end the loadtest. - */ - private PendingIntent mStopPendingIntent; - - private Button mStartStop; - private EditText mReplicationText; - private Spinner mBucketSpinner; - private EditText mPeriodText; - private EditText mBurstText; - private EditText mDurationText; - private TextView mReportText; - private CheckBox mPlaceboCheckBox; - private CheckBox mCountMetricCheckBox; - private CheckBox mDurationMetricCheckBox; - private CheckBox mEventMetricCheckBox; - private CheckBox mValueMetricCheckBox; - private CheckBox mGaugeMetricCheckBox; - - /** - * When the load test started. - */ - private long mStartedTimeMillis; - - /** - * For measuring perf data. - */ - private PerfData mPerfData; - - /** - * For communicating with statsd. - */ - private StatsManager mStatsManager; - - private PowerManager mPowerManager; - private WakeLock mWakeLock; - - /** - * If true, we only measure the effect of the loadtest infrastructure. No atom are pushed and - * the configuration is empty. - */ - private boolean mPlacebo; - - /** - * Whether to include CountMetric in the config. - */ - private boolean mIncludeCountMetric; - - /** - * Whether to include DurationMetric in the config. - */ - private boolean mIncludeDurationMetric; - - /** - * Whether to include EventMetric in the config. - */ - private boolean mIncludeEventMetric; - - /** - * Whether to include ValueMetric in the config. - */ - private boolean mIncludeValueMetric; - - /** - * Whether to include GaugeMetric in the config. - */ - private boolean mIncludeGaugeMetric; - - /** - * The burst size. - */ - private int mBurst; - - /** - * The metrics replication. - */ - private int mReplication; - - /** - * The period, in seconds, at which batches of atoms are pushed. - */ - private long mPeriodSecs; - - /** - * The bucket size, in minutes, for aggregate metrics. - */ - private TimeUnit mBucket; - - /** - * The duration, in minutes, of the loadtest. - */ - private long mDurationMins; - - /** - * Whether the loadtest has started. - */ - private boolean mStarted = false; - - /** - * Orchestrates the logging of pushed events into logd. - */ - private SequencePusher mPusher; - - /** - * Generates statsd configs. - */ - private ConfigFactory mFactory; - - /** - * For intra-minute periods. - */ - private final Handler mHandler = new Handler(); - - /** - * Number of metrics in the current config. - */ - private int mNumMetrics; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Log.d(TAG, "Starting loadtest Activity"); - - setContentView(R.layout.activity_loadtest); - mReportText = (TextView) findViewById(R.id.report_text); - initBurst(); - initReplication(); - initBucket(); - initPeriod(); - initDuration(); - initPlacebo(); - initMetricWhitelist(); - - // Hide the keyboard outside edit texts. - findViewById(R.id.outside).setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - InputMethodManager imm = - (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (getCurrentFocus() != null) { - imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); - } - return true; - } - }); - - mStartStop = findViewById(R.id.start_stop); - mStartStop.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mStarted) { - stopLoadtest(); - } else { - startLoadtest(); - } - } - }); - - mAlarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - mStatsManager = (StatsManager) getSystemService("stats"); - mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); - mFactory = new ConfigFactory(this); - stopLoadtest(); - mReportText.setText(""); - } - - @Override - public void onNewIntent(Intent intent) { - String type = intent.getStringExtra(TYPE); - if (type == null) { - return; - } - switch (type) { - case PERF_ALARM: - onPerfAlarm(); - break; - case PUSH_ALARM: - onAlarm(); - break; - case SET_REPLICATION: - if (intent.hasExtra(REPLICATION)) { - setReplication(intent.getIntExtra(REPLICATION, 0)); - } - break; - case START: - startLoadtest(); - break; - case STOP: - stopLoadtest(); - break; - default: - throw new IllegalArgumentException("Unknown type: " + type); - } - } - - @Override - public void onDestroy() { - Log.d(TAG, "Destroying"); - mPerfData.onDestroy(); - stopLoadtest(); - clearConfigs(); - super.onDestroy(); - } - - @Nullable - public StatsdStatsReport getMetadata() { - if (!statsdRunning()) { - return null; - } - if (mStatsManager != null) { - byte[] data; - try { - data = mStatsManager.getStatsMetadata(); - } catch (StatsManager.StatsUnavailableException e) { - Log.e(TAG, "Failed to get data from statsd", e); - return null; - } - if (data != null) { - StatsdStatsReport report = null; - boolean good = false; - try { - return StatsdStatsReport.parseFrom(data); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - Log.d(TAG, "Bad StatsdStatsReport"); - } - } - } - return null; - } - - @Nullable - public List<ConfigMetricsReport> getData() { - if (!statsdRunning()) { - return null; - } - if (mStatsManager != null) { - byte[] data; - try { - data = mStatsManager.getReports(ConfigFactory.CONFIG_ID); - } catch (StatsManager.StatsUnavailableException e) { - Log.e(TAG, "Failed to get data from statsd", e); - return null; - } - if (data != null) { - ConfigMetricsReportList reports = null; - try { - reports = ConfigMetricsReportList.parseFrom(data); - Log.d(TAG, "Num reports: " + reports.getReportsCount()); - StringBuilder sb = new StringBuilder(); - DisplayProtoUtils.displayLogReport(sb, reports); - Log.d(TAG, sb.toString()); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - Log.d(TAG, "Invalid data"); - } - if (reports != null) { - return reports.getReportsList(); - } - } - } - return null; - } - - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - String item = parent.getItemAtPosition(position).toString(); - - mBucket = TIME_UNIT_MAP.get(item); - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - // Another interface callback - } - - private void onPerfAlarm() { - if (mPerfData != null) { - mPerfData.onAlarm(this); - } - // Piggy-back on that alarm to show the elapsed time. - long elapsedTimeMins = (long) Math.floor( - (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000); - mReportText.setText("Loadtest in progress.\n" - + "num metrics =" + mNumMetrics - + "\nElapsed time = " + elapsedTimeMins + " min(s)"); - } - - private void onAlarm() { - Log.d(TAG, "ON ALARM"); - - // Set the next task. - scheduleNext(); - - // Do the work. - mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StatsdLoadTest"); - mWakeLock.acquire(); - if (mPusher != null) { - mPusher.next(); - } - mWakeLock.release(); - mWakeLock = null; - } - - /** - * Schedules the next cycle of pushing atoms into logd. - */ - private void scheduleNext() { - Intent intent = new Intent(this, PusherAlarmReceiver.class); - intent.putExtra(TYPE, PUSH_ALARM); - mPushPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); - long nextTime = SystemClock.elapsedRealtime() + mPeriodSecs * 1000; - mAlarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, mPushPendingIntent); - } - - private synchronized void startLoadtest() { - if (mStarted) { - return; - } - - // Clean up the state. - stopLoadtest(); - - // Prepare to push a sequence of atoms to logd. - mPusher = new SequencePusher(mBurst, mPlacebo); - - // Create a config and push it to statsd. - if (!setConfig(mFactory.getConfig(mReplication, mBucket, mPlacebo, - mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric, - mIncludeValueMetric, mIncludeGaugeMetric))) { - return; - } - - // Remember to stop in the future. - Intent intent = new Intent(this, StopperAlarmReceiver.class); - intent.putExtra(TYPE, STOP); - mStopPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); - long nextTime = SystemClock.elapsedRealtime() + mDurationMins * 60 * 1000; - mAlarmMgr.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, mStopPendingIntent); - - // Log atoms. - scheduleNext(); - - // Start tracking performance. - mPerfData = new PerfData(this, mPlacebo, mReplication, mBucket, mPeriodSecs, mBurst, - mIncludeCountMetric, mIncludeDurationMetric, mIncludeEventMetric, mIncludeValueMetric, - mIncludeGaugeMetric); - mPerfData.startRecording(this); - - mReportText.setText("Loadtest in progress.\nnum metrics =" + mNumMetrics); - mStartedTimeMillis = SystemClock.elapsedRealtime(); - - updateStarted(true); - } - - private synchronized void stopLoadtest() { - if (mPushPendingIntent != null) { - Log.d(TAG, "Canceling pre-existing push alarm"); - mAlarmMgr.cancel(mPushPendingIntent); - mPushPendingIntent = null; - } - if (mStopPendingIntent != null) { - Log.d(TAG, "Canceling pre-existing stop alarm"); - mAlarmMgr.cancel(mStopPendingIntent); - mStopPendingIntent = null; - } - if (mWakeLock != null) { - mWakeLock.release(); - mWakeLock = null; - } - if (mPerfData != null) { - mPerfData.stopRecording(this); - mPerfData.onDestroy(); - mPerfData = null; - } - - // Obtain the latest data and display it. - getData(); - - long elapsedTimeMins = (long) Math.floor( - (SystemClock.elapsedRealtime() - mStartedTimeMillis) / 60 / 1000); - mReportText.setText("Loadtest ended. Elapsed time = " + elapsedTimeMins + " min(s)"); - clearConfigs(); - updateStarted(false); - } - - private synchronized void updateStarted(boolean started) { - mStarted = started; - mStartStop.setBackgroundColor(started ? - Color.parseColor("#FFFF0000") : Color.parseColor("#FF00FF00")); - mStartStop.setText(started ? getString(R.string.stop) : getString(R.string.start)); - updateControlsEnabled(); - } - - private void updateControlsEnabled() { - mBurstText.setEnabled(!mPlacebo && !mStarted); - mReplicationText.setEnabled(!mPlacebo && !mStarted); - mPeriodText.setEnabled(!mStarted); - mBucketSpinner.setEnabled(!mPlacebo && !mStarted); - mDurationText.setEnabled(!mStarted); - mPlaceboCheckBox.setEnabled(!mStarted); - - boolean enabled = !mStarted && !mPlaceboCheckBox.isChecked(); - mCountMetricCheckBox.setEnabled(enabled); - mDurationMetricCheckBox.setEnabled(enabled); - mEventMetricCheckBox.setEnabled(enabled); - mValueMetricCheckBox.setEnabled(enabled); - mGaugeMetricCheckBox.setEnabled(enabled); - } - - private boolean statsdRunning() { - if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) { - Log.d(TAG, "Statsd not running"); - Toast.makeText(LoadtestActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show(); - return false; - } - return true; - } - - private int sanitizeInt(int val, int min, int max) { - if (val > max) { - val = max; - } else if (val < min) { - val = min; - } - return val; - } - - private void clearConfigs() { - // TODO: Clear all configs instead of specific ones. - if (mStatsManager != null) { - if (mStarted) { - try { - mStatsManager.removeConfig(ConfigFactory.CONFIG_ID); - Log.d(TAG, "Removed loadtest statsd configs."); - } catch (StatsManager.StatsUnavailableException e) { - Log.e(TAG, "Failed to remove loadtest configs.", e); - } - } - } - } - - private boolean setConfig(ConfigFactory.ConfigMetadata configData) { - if (mStatsManager != null) { - try { - mStatsManager.addConfig(ConfigFactory.CONFIG_ID, configData.bytes); - mNumMetrics = configData.numMetrics; - Log.d(TAG, "Config pushed to statsd"); - return true; - } catch (StatsManager.StatsUnavailableException | IllegalArgumentException e) { - Log.e(TAG, "Failed to push config to statsd", e); - } - } - return false; - } - - private synchronized void setReplication(int replication) { - if (mStarted) { - return; - } - mReplicationText.setText("" + replication); - } - - private synchronized void setPeriodSecs(long periodSecs) { - mPeriodSecs = periodSecs; - } - - private synchronized void setBurst(int burst) { - mBurst = burst; - } - - private synchronized void setDurationMins(long durationMins) { - mDurationMins = durationMins; - } - - - private void handleFocus(EditText editText) { - /* - editText.setOnFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (!hasFocus && editText.getText().toString().isEmpty()) { - editText.setText("-1", TextView.BufferType.EDITABLE); - } - } - }); - */ - } - - private void initBurst() { - mBurst = getResources().getInteger(R.integer.burst_default); - mBurstText = (EditText) findViewById(R.id.burst); - mBurstText.addTextChangedListener(new NumericalWatcher(mBurstText, 0, 1000) { - @Override - public void onNewValue(int newValue) { - setBurst(newValue); - } - }); - handleFocus(mBurstText); - } - - private void initReplication() { - mReplication = getResources().getInteger(R.integer.replication_default); - mReplicationText = (EditText) findViewById(R.id.replication); - mReplicationText.addTextChangedListener(new NumericalWatcher(mReplicationText, 1, 4096) { - @Override - public void onNewValue(int newValue) { - mReplication = newValue; - } - }); - handleFocus(mReplicationText); - } - - private void initBucket() { - String defaultValue = getResources().getString(R.string.bucket_default); - mBucket = TimeUnit.valueOf(defaultValue); - mBucketSpinner = (Spinner) findViewById(R.id.bucket_spinner); - - ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>( - this, R.layout.spinner_item, TIME_UNIT_LABELS); - - mBucketSpinner.setAdapter(dataAdapter); - mBucketSpinner.setOnItemSelectedListener(this); - - for (String label : TIME_UNIT_MAP.keySet()) { - if (defaultValue.equals(TIME_UNIT_MAP.get(label).toString())) { - mBucketSpinner.setSelection(dataAdapter.getPosition(label)); - } - } - } - - private void initPeriod() { - mPeriodSecs = getResources().getInteger(R.integer.period_default); - mPeriodText = (EditText) findViewById(R.id.period); - mPeriodText.addTextChangedListener(new NumericalWatcher(mPeriodText, 1, 60) { - @Override - public void onNewValue(int newValue) { - setPeriodSecs(newValue); - } - }); - handleFocus(mPeriodText); - } - - private void initDuration() { - mDurationMins = getResources().getInteger(R.integer.duration_default); - mDurationText = (EditText) findViewById(R.id.duration); - mDurationText.addTextChangedListener(new NumericalWatcher(mDurationText, 1, 24 * 60) { - @Override - public void onNewValue(int newValue) { - setDurationMins(newValue); - } - }); - handleFocus(mDurationText); - } - - private void initPlacebo() { - mPlaceboCheckBox = findViewById(R.id.placebo); - mPlacebo = false; - mPlaceboCheckBox.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mPlacebo = mPlaceboCheckBox.isChecked(); - updateControlsEnabled(); - } - }); - } - - private void initMetricWhitelist() { - mCountMetricCheckBox = findViewById(R.id.include_count); - mCountMetricCheckBox.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mIncludeCountMetric = mCountMetricCheckBox.isChecked(); - } - }); - mDurationMetricCheckBox = findViewById(R.id.include_duration); - mDurationMetricCheckBox.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mIncludeDurationMetric = mDurationMetricCheckBox.isChecked(); - } - }); - mEventMetricCheckBox = findViewById(R.id.include_event); - mEventMetricCheckBox.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mIncludeEventMetric = mEventMetricCheckBox.isChecked(); - } - }); - mValueMetricCheckBox = findViewById(R.id.include_value); - mValueMetricCheckBox.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mIncludeValueMetric = mValueMetricCheckBox.isChecked(); - } - }); - mGaugeMetricCheckBox = findViewById(R.id.include_gauge); - mGaugeMetricCheckBox.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked(); - } - }); - - mIncludeCountMetric = mCountMetricCheckBox.isChecked(); - mIncludeDurationMetric = mDurationMetricCheckBox.isChecked(); - mIncludeEventMetric = mEventMetricCheckBox.isChecked(); - mIncludeValueMetric = mValueMetricCheckBox.isChecked(); - mIncludeGaugeMetric = mGaugeMetricCheckBox.isChecked(); - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java deleted file mode 100644 index 01eebf2ad1cf..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemInfoParser.java +++ /dev/null @@ -1,69 +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. - */ -package com.android.statsd.loadtest; - -import android.annotation.Nullable; -import android.os.SystemClock; -import android.util.Log; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** Parses PSS info from dumpsys meminfo */ -public class MemInfoParser implements PerfParser { - - private static final Pattern LINE_PATTERN = - Pattern.compile("\\s*(\\d*,*\\d*)K:\\s(\\S*)\\s\\.*"); - private static final String PSS_BY_PROCESS = "Total PSS by process:"; - private static final String TAG = "loadtest.MemInfoParser"; - - private boolean mPssStarted; - private boolean mPssEnded; - private final long mStartTimeMillis; - - public MemInfoParser(long startTimeMillis) { - mStartTimeMillis = startTimeMillis; - } - - @Override - @Nullable - public String parseLine(String line) { - if (mPssEnded) { - return null; - } - if (!mPssStarted) { - if (line.contains(PSS_BY_PROCESS)) { - mPssStarted = true; - } - return null; - } - if (line.isEmpty()) { - mPssEnded = true; - return null; - } - Matcher lineMatcher = LINE_PATTERN.matcher(line); - if (lineMatcher.find() && lineMatcher.group(1) != null && lineMatcher.group(2) != null) { - if (lineMatcher.group(2).equals("statsd")) { - long timeDeltaMillis = SystemClock.elapsedRealtime() - mStartTimeMillis; - return timeDeltaMillis + "," + convertToPss(lineMatcher.group(1)); - } - } - return null; - } - - private String convertToPss(String input) { - return input.replace(",", ""); - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java deleted file mode 100644 index af7bd4d35966..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/MemoryDataRecorder.java +++ /dev/null @@ -1,53 +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. - */ -package com.android.statsd.loadtest; - -import android.content.Context; -import android.os.SystemClock; -import android.util.Log; -import com.android.internal.os.StatsdConfigProto.TimeUnit; - -public class MemoryDataRecorder extends PerfDataRecorder { - private static final String TAG = "loadtest.MemoryDataDataRecorder"; - private static final String DUMP_FILENAME = TAG + "_dump.tmp"; - - private long mStartTimeMillis; - private StringBuilder mSb; - - public MemoryDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs, - int burst, boolean includeCountMetric, boolean includeDurationMetric, - boolean includeEventMetric, boolean includeValueMetric, boolean includeGaugeMetric) { - super(placebo, replication, bucket, periodSecs, burst, includeCountMetric, - includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric); - } - - @Override - public void startRecording(Context context) { - mStartTimeMillis = SystemClock.elapsedRealtime(); - mSb = new StringBuilder(); - } - - @Override - public void onAlarm(Context context) { - runDumpsysStats(context, DUMP_FILENAME, "meminfo"); - readDumpData(context, DUMP_FILENAME, new MemInfoParser(mStartTimeMillis), mSb); - } - - @Override - public void stopRecording(Context context) { - writeData(context, "meminfo_", "time,pss", mSb); - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java deleted file mode 100644 index 555e6dd2d99d..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/NumericalWatcher.java +++ /dev/null @@ -1,70 +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. - */ -package com.android.statsd.loadtest; - -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.widget.TextView; - -public abstract class NumericalWatcher implements TextWatcher { - - private static final String TAG = "loadtest.NumericalWatcher"; - - private final TextView mTextView; - private final int mMin; - private final int mMax; - private int currentValue = -1; - - public NumericalWatcher(TextView textView, int min, int max) { - mTextView = textView; - mMin = min; - mMax = max; - } - - public abstract void onNewValue(int newValue); - - @Override - final public void afterTextChanged(Editable editable) { - String s = mTextView.getText().toString(); - if (s.isEmpty()) { - return; - } - int unsanitized = Integer.parseInt(s); - int newValue = sanitize(unsanitized); - if (currentValue != newValue || unsanitized != newValue) { - currentValue = newValue; - editable.clear(); - editable.append(newValue + ""); - } - onNewValue(newValue); - } - - @Override - final public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - final public void onTextChanged(CharSequence s, int start, int before, int count) {} - - private int sanitize(int val) { - if (val > mMax) { - val = mMax; - } else if (val < mMin) { - val = mMin; - } - return val; - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java deleted file mode 100644 index 7a01adedfaa4..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfData.java +++ /dev/null @@ -1,116 +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. - */ -package com.android.statsd.loadtest; - -import com.android.internal.os.StatsdConfigProto.TimeUnit; - -import android.annotation.Nullable; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.SystemClock; -import android.util.Log; - -import java.util.HashSet; -import java.util.Set; - -/** Prints some information about the device via Dumpsys in order to evaluate health metrics. */ -public class PerfData extends PerfDataRecorder { - - private static final String TAG = "loadtest.PerfData"; - - /** Polling period for performance snapshots like memory. */ - private static final long POLLING_PERIOD_MILLIS = 1 * 60 * 1000; - - public final static class PerfAlarmReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Intent activityIntent = new Intent(context, LoadtestActivity.class); - activityIntent.putExtra(LoadtestActivity.TYPE, LoadtestActivity.PERF_ALARM); - context.startActivity(activityIntent); - } - } - - private AlarmManager mAlarmMgr; - - /** Used to periodically poll some dumpsys data. */ - private PendingIntent mPendingIntent; - - private final Set<PerfDataRecorder> mRecorders; - - public PerfData(LoadtestActivity loadtestActivity, boolean placebo, int replication, - TimeUnit bucket, long periodSecs, int burst, boolean includeCountMetric, - boolean includeDurationMetric, boolean includeEventMetric, boolean includeValueMetric, - boolean includeGaugeMetric) { - super(placebo, replication, bucket, periodSecs, burst, includeCountMetric, - includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric); - mRecorders = new HashSet(); - mRecorders.add(new BatteryDataRecorder(placebo, replication, bucket, periodSecs, burst, - includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric, - includeGaugeMetric)); - mRecorders.add(new MemoryDataRecorder(placebo, replication, bucket, periodSecs, burst, - includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric, - includeGaugeMetric)); - mRecorders.add(new StatsdStatsRecorder(loadtestActivity, placebo, replication, bucket, - periodSecs, burst, includeCountMetric, includeDurationMetric, includeEventMetric, - includeValueMetric, includeGaugeMetric)); - mRecorders.add(new ValidationRecorder(loadtestActivity, placebo, replication, bucket, - periodSecs, burst, includeCountMetric, includeDurationMetric, includeEventMetric, - includeValueMetric, includeGaugeMetric)); - mAlarmMgr = (AlarmManager) loadtestActivity.getSystemService(Context.ALARM_SERVICE); - } - - public void onDestroy() { - if (mPendingIntent != null) { - mAlarmMgr.cancel(mPendingIntent); - mPendingIntent = null; - } - } - - @Override - public void startRecording(Context context) { - Intent intent = new Intent(context, PerfAlarmReceiver.class); - intent.putExtra(LoadtestActivity.TYPE, LoadtestActivity.PERF_ALARM); - mPendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0); - mAlarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, -1 /* now */, - POLLING_PERIOD_MILLIS, mPendingIntent); - - for (PerfDataRecorder recorder : mRecorders) { - recorder.startRecording(context); - } - } - - @Override - public void onAlarm(Context context) { - for (PerfDataRecorder recorder : mRecorders) { - recorder.onAlarm(context); - } - } - - @Override - public void stopRecording(Context context) { - if (mPendingIntent != null) { - mAlarmMgr.cancel(mPendingIntent); - mPendingIntent = null; - } - - for (PerfDataRecorder recorder : mRecorders) { - recorder.stopRecording(context); - } - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java deleted file mode 100644 index 8613ac1c4796..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/PerfDataRecorder.java +++ /dev/null @@ -1,174 +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. - */ -package com.android.statsd.loadtest; - -import android.annotation.Nullable; -import android.content.Context; -import android.os.Environment; -import android.util.Log; -import android.os.Debug; - -import com.android.internal.os.StatsdConfigProto.TimeUnit; -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.InputStreamReader; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; - -public abstract class PerfDataRecorder { - private static final String TAG = "loadtest.PerfDataRecorder"; - - protected final String mTimeAsString; - protected final String mColumnSuffix; - - protected PerfDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs, - int burst, boolean includeCountMetric, boolean includeDurationMetric, - boolean includeEventMetric, boolean includeValueMetric, boolean includeGaugeMetric) { - mTimeAsString = new SimpleDateFormat("YYYY_MM_dd_HH_mm_ss").format(new Date()); - mColumnSuffix = getColumnSuffix(placebo, replication, bucket, periodSecs, burst, - includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric, - includeGaugeMetric); - } - - /** Starts recording performance data. */ - public abstract void startRecording(Context context); - - /** Called periodically. For the recorder to sample data, if needed. */ - public abstract void onAlarm(Context context); - - /** Stops recording performance data, and writes it to disk. */ - public abstract void stopRecording(Context context); - - /** Runs the dumpsys command. */ - protected void runDumpsysStats(Context context, String dumpFilename, String cmd, - String... args) { - boolean success = false; - // Call dumpsys Dump statistics to a file. - FileOutputStream fo = null; - try { - fo = context.openFileOutput(dumpFilename, Context.MODE_PRIVATE); - if (!Debug.dumpService(cmd, fo.getFD(), args)) { - Log.w(TAG, "Dumpsys failed."); - } - success = true; - } catch (IOException | SecurityException | NullPointerException e) { - // SecurityException may occur when trying to dump multi-user info. - // NPE can occur during dumpService (root cause unknown). - throw new RuntimeException(e); - } finally { - closeQuietly(fo); - } - } - - /** - * Reads a text file and parses each line, one by one. The result of the parsing is stored - * in the passed {@link StringBuffer}. - */ - protected void readDumpData(Context context, String dumpFilename, PerfParser parser, - StringBuilder sb) { - FileInputStream fi = null; - BufferedReader br = null; - try { - fi = context.openFileInput(dumpFilename); - br = new BufferedReader(new InputStreamReader(fi)); - String line = br.readLine(); - while (line != null) { - String recordLine = parser.parseLine(line); - if (recordLine != null) { - sb.append(recordLine).append('\n'); - } - line = br.readLine(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - closeQuietly(br); - } - } - - /** Writes CSV data to a file. */ - protected void writeData(Context context, String filePrefix, String columnPrefix, - StringBuilder sb) { - File dataFile = new File(getStorageDir(), filePrefix + mTimeAsString + ".csv"); - - FileWriter writer = null; - try { - writer = new FileWriter(dataFile); - writer.append(columnPrefix + mColumnSuffix + "\n"); - writer.append(sb.toString()); - writer.flush(); - Log.d(TAG, "Finished writing data at " + dataFile.getAbsolutePath()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - closeQuietly(writer); - } - } - - /** Gets the suffix to use in the column name for perf data. */ - private String getColumnSuffix(boolean placebo, int replication, TimeUnit bucket, - long periodSecs, int burst, boolean includeCountMetric, boolean includeDurationMetric, - boolean includeEventMetric, boolean includeValueMetric, boolean includeGaugeMetric) { - if (placebo) { - return "_placebo_p=" + periodSecs; - } - StringBuilder sb = new StringBuilder() - .append("_r=" + replication) - .append("_bkt=" + bucket) - .append("_p=" + periodSecs) - .append("_bst=" + burst) - .append("_m="); - if (includeCountMetric) { - sb.append("c"); - } - if (includeEventMetric) { - sb.append("e"); - } - if (includeDurationMetric) { - sb.append("d"); - } - if (includeGaugeMetric) { - sb.append("g"); - } - if (includeValueMetric) { - sb.append("v"); - } - return sb.toString(); - } - - private File getStorageDir() { - File file = new File(Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOCUMENTS), "loadtest/" + mTimeAsString); - if (!file.mkdirs()) { - Log.e(TAG, "Directory not created"); - } - return file; - } - - private void closeQuietly(@Nullable Closeable c) { - if (c != null) { - try { - c.close(); - } catch (IOException ignore) { - } - } - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java deleted file mode 100644 index 5dcce9acb401..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/SequencePusher.java +++ /dev/null @@ -1,165 +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. - */ -package com.android.statsd.loadtest; - -import android.util.Log; -import android.util.StatsLog; - -import java.util.ArrayList; -import java.util.List; - -/** - * Manages the pushing of atoms into logd for loadtesting. - * We rely on small number of pushed atoms, and a config with metrics based on those atoms. - * The atoms are: - * <ul> - * <li> BatteryLevelChanged - For EventMetric, CountMetric and GaugeMetric (no dimensions). - * <li> BleScanResultReceived - For CountMetric and ValueMetric, sliced by uid. - * <li> ChargingStateChanged - For DurationMetric (no dimension). - * <li> GpsScanStateChanged - For DurationMetric, sliced by uid. - * <li> ScreenStateChanged - For Conditions with no dimensions. - * <li> AudioStateChanged - For Conditions with dimensions (uid). - * </ul> - * The sequence is played over and over at a given frequency. - */ -public class SequencePusher { - private static final String TAG = "SequencePusher"; - - /** Some atoms are pushed in burst of {@code mBurst} events. */ - private final int mBurst; - - /** If this is true, we don't log anything in logd. */ - private final boolean mPlacebo; - - /** Current state in the automaton. */ - private int mCursor = 0; - - public SequencePusher(int burst, boolean placebo) { - mBurst = burst; - mPlacebo = placebo; - } - - /** - * Pushes the next atom to logd. - * This follows a small automaton which makes the right events and conditions overlap: - * (0) Push a burst of BatteryLevelChanged atoms. - * (1) Push a burst of BleScanResultReceived atoms. - * (2) Push ChargingStateChanged with BATTERY_STATUS_CHARGING once. - * (3) Push a burst of GpsScanStateChanged atoms with ON, with a different uid each time. - * (4) Push ChargingStateChanged with BATTERY_STATUS_NOT_CHARGING once. - * (5) Push a burst GpsScanStateChanged atoms with OFF, with a different uid each time. - * (6) Push ScreenStateChanged with STATE_ON once. - * (7) Push a burst of AudioStateChanged with ON, with a different uid each time. - * (8) Repeat steps (0)-(5). - * (9) Push ScreenStateChanged with STATE_OFF once. - * (10) Push a burst of AudioStateChanged with OFF, with a different uid each time. - * and repeat. - */ - public void next() { - Log.d(TAG, "Next step: " + mCursor); - if (mPlacebo) { - return; - } - switch (mCursor) { - case 0: - case 8: - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, 50 + i /* battery_level */); - } - break; - case 1: - case 9: - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, i /* uid */, - 100 /* num_of_results */); - } - break; - case 2: - case 10: - StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, - StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_CHARGING - /* charging_state */); - break; - case 3: - case 11: - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, i /* uid */, - StatsLog.GPS_SCAN_STATE_CHANGED__STATE__ON /* state */); - } - break; - case 4: - case 12: - StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, - StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_NOT_CHARGING - /* charging_state */); - break; - case 5: - case 13: - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, i /* uid */, - StatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF /* state */); - } - break; - case 6: - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_ON /* display_state */); - break; - case 7: - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, i /* uid */, - StatsLog.AUDIO_STATE_CHANGED__STATE__ON /* state */); - } - break; - case 14: - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF /* display_state */); - break; - case 15: - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, i /* uid */, - StatsLog.AUDIO_STATE_CHANGED__STATE__OFF /* state */); - } - break; - default: - } - mCursor++; - if (mCursor > 15) { - mCursor = 0; - } - } - - /** - * Properly finishes in order to be close all conditions and durations. - */ - public void finish() { - // Screen goes back to off. This will ensure that conditions get back to false. - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, - StatsLog.SCREEN_STATE_CHANGED__STATE__DISPLAY_STATE_OFF /* display_state */); - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, i /* uid */, - StatsLog.AUDIO_STATE_CHANGED__STATE__OFF /* state */); - } - // Stop charging, to ensure the corresponding durations are closed. - StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, - StatsLog.CHARGING_STATE_CHANGED__STATE__BATTERY_STATUS_NOT_CHARGING - /* charging_state */); - // Stop scanning GPS, to ensure the corresponding conditions get back to false. - for (int i = 0; i < mBurst; i++) { - StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, i /* uid */, - StatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF /* state */); - } - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java deleted file mode 100644 index 3939e7e0b2fa..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java +++ /dev/null @@ -1,62 +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. - */ -package com.android.statsd.loadtest; - -import android.content.Context; -import com.android.os.StatsLog.StatsdStatsReport; -import com.android.internal.os.StatsdConfigProto.TimeUnit; - -public class StatsdStatsRecorder extends PerfDataRecorder { - private static final String TAG = "loadtest.StatsdStatsRecorder"; - - private final LoadtestActivity mLoadtestActivity; - - public StatsdStatsRecorder(LoadtestActivity loadtestActivity, boolean placebo, int replication, - TimeUnit bucket, long periodSecs, int burst, boolean includeCountMetric, - boolean includeDurationMetric, boolean includeEventMetric, boolean includeValueMetric, - boolean includeGaugeMetric) { - super(placebo, replication, bucket, periodSecs, burst, includeCountMetric, - includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric); - mLoadtestActivity = loadtestActivity; - } - - @Override - public void startRecording(Context context) { - // Nothing to do. - } - - @Override - public void onAlarm(Context context) { - // Nothing to do. - } - - @Override - public void stopRecording(Context context) { - StatsdStatsReport metadata = mLoadtestActivity.getMetadata(); - if (metadata != null) { - int numConfigs = metadata.getConfigStatsCount(); - StringBuilder sb = new StringBuilder(); - StatsdStatsReport.ConfigStats configStats = metadata.getConfigStats(numConfigs - 1); - sb.append("metric_count,") - .append(configStats.getMetricCount() + "\n") - .append("condition_count,") - .append(configStats.getConditionCount() + "\n") - .append("matcher_count,") - .append(configStats.getMatcherCount() + "\n"); - writeData(context, "statsdstats_", "stat,value", sb); - } - } -} diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java deleted file mode 100644 index d9f0ca9d2461..000000000000 --- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/ValidationRecorder.java +++ /dev/null @@ -1,93 +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. - */ -package com.android.statsd.loadtest; - -import android.content.Context; -import android.util.Log; -import com.android.os.StatsLog.ConfigMetricsReport; -import com.android.os.StatsLog.EventMetricData; -import com.android.os.StatsLog.StatsLogReport; -import com.android.internal.os.StatsdConfigProto.TimeUnit; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Checks the correctness of the stats. - */ -public class ValidationRecorder extends PerfDataRecorder { - private static final String TAG = "loadtest.ValidationRecorder"; - - private final LoadtestActivity mLoadtestActivity; - - public ValidationRecorder(LoadtestActivity loadtestActivity, boolean placebo, int replication, - TimeUnit bucket, long periodSecs, int burst, boolean includeCountMetric, - boolean includeDurationMetric, boolean includeEventMetric, boolean includeValueMetric, - boolean includeGaugeMetric) { - super(placebo, replication, bucket, periodSecs, burst, includeCountMetric, - includeDurationMetric, includeEventMetric, includeValueMetric, includeGaugeMetric); - mLoadtestActivity = loadtestActivity; - } - - @Override - public void startRecording(Context context) { - // Nothing to do. - } - - @Override - public void onAlarm(Context context) { - validateData(); - } - - @Override - public void stopRecording(Context context) { - validateData(); - } - - private void validateData() { - // The code below is commented out because it calls getData, which has the side-effect - // of clearing statsd's data buffer. - /* - List<ConfigMetricsReport> reports = mLoadtestActivity.getData(); - if (reports != null) { - Log.d(TAG, "GOT DATA"); - for (ConfigMetricsReport report : reports) { - for (StatsLogReport logReport : report.getMetricsList()) { - if (!logReport.hasMetricId()) { - Log.e(TAG, "Metric missing name."); - } - } - } - } - */ - } - - private void validateEventBatteryLevelChanges(StatsLogReport logReport) { - Log.d(TAG, "Validating " + logReport.getMetricId()); - if (logReport.hasEventMetrics()) { - Log.d(TAG, "Num events captured: " + logReport.getEventMetrics().getDataCount()); - for (EventMetricData data : logReport.getEventMetrics().getDataList()) { - Log.d(TAG, " Event : " + data.getAtom()); - } - } else { - Log.d(TAG, "Metric is invalid"); - } - } - - private void validateEventBatteryLevelChangesWhileScreenIsOn(StatsLogReport logReport) { - Log.d(TAG, "Validating " + logReport.getMetricId()); - } -} diff --git a/cmds/statsd/tools/localtools/Android.bp b/cmds/statsd/tools/localtools/Android.bp index 75a57a3f3068..69a43a8f4712 100644 --- a/cmds/statsd/tools/localtools/Android.bp +++ b/cmds/statsd/tools/localtools/Android.bp @@ -11,9 +11,8 @@ java_binary_host { ], } -java_binary_host { - name: "statsd_testdrive", - manifest: "testdrive_manifest.txt", +java_library_host { + name: "statsd_testdrive_lib", srcs: [ "src/com/android/statsd/shelltools/testdrive/*.java", "src/com/android/statsd/shelltools/Utils.java", @@ -22,4 +21,26 @@ java_binary_host { "platformprotos", "guava", ], -}
\ No newline at end of file +} + + +java_binary_host { + name: "statsd_testdrive", + manifest: "testdrive_manifest.txt", + static_libs: [ + "statsd_testdrive_lib", + ], +} + +java_test_host { + name: "statsd_testdrive_test", + test_suites: ["general-tests"], + srcs: ["test/com/android/statsd/shelltools/testdrive/*.java"], + static_libs: [ + "statsd_testdrive_lib", + "junit", + "platformprotos", + "guava", + ], +} + diff --git a/cmds/statsd/tools/localtools/TEST_MAPPING b/cmds/statsd/tools/localtools/TEST_MAPPING new file mode 100644 index 000000000000..7c8a3db5c610 --- /dev/null +++ b/cmds/statsd/tools/localtools/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "presubmit": [ + { + "name": "statsd_testdrive_test", + "host": true + } + ] +} diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java index a381f9c4560a..6a74480b505e 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java @@ -17,16 +17,23 @@ package com.android.statsd.shelltools; import com.android.os.StatsLog.ConfigMetricsReportList; +import com.google.common.io.Files; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.logging.ConsoleHandler; import java.util.logging.Formatter; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Utilities for local use of statsd. @@ -80,7 +87,8 @@ public class Utils { * @throws InterruptedException */ public static ConfigMetricsReportList getReportList(long configId, boolean clearData, - boolean useShellUid, Logger logger) throws IOException, InterruptedException { + boolean useShellUid, Logger logger, String deviceSerial) + throws IOException, InterruptedException { try { File outputFile = File.createTempFile("statsdret", ".bin"); outputFile.deleteOnExit(); @@ -88,6 +96,8 @@ public class Utils { outputFile, logger, "adb", + "-s", + deviceSerial, "shell", CMD_DUMP_REPORT, useShellUid ? SHELL_UID : "", @@ -117,12 +127,14 @@ public class Utils { * @throws IOException * @throws InterruptedException */ - public static void logAppBreadcrumb(int label, int state, Logger logger) + public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial) throws IOException, InterruptedException { runCommand( null, logger, "adb", + "-s", + deviceSerial, "shell", CMD_LOG_APP_BREADCRUMB, String.valueOf(label), @@ -145,13 +157,14 @@ public class Utils { * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName)) * If all else fails, assume it will work (letting future commands deal with any errors). */ - public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename) { + public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename, + String deviceSerial) { BufferedReader in = null; try { File outFileSdk = File.createTempFile("shelltools_sdk", "tmp"); outFileSdk.deleteOnExit(); runCommand(outFileSdk, logger, - "adb", "shell", "getprop", "ro.build.version.sdk"); + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk"); in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk))); // If NullPointerException/NumberFormatException/etc., just catch and return true. int sdk = Integer.parseInt(in.readLine().trim()); @@ -162,7 +175,7 @@ public class Utils { File outFileCode = File.createTempFile("shelltools_codename", "tmp"); outFileCode.deleteOnExit(); runCommand(outFileCode, logger, - "adb", "shell", "getprop", "ro.build.version.codename"); + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename"); in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode))); return in.readLine().startsWith(minCodename); } else { @@ -190,4 +203,82 @@ public class Utils { return record.getMessage() + "\n"; } } + + /** + * Parse the result of "adb devices" to return the list of connected devices. + * @param logger Logger to log error messages + * @return List of the serial numbers of the connected devices. + */ + public static List<String> getDeviceSerials(Logger logger) { + try { + ArrayList<String> devices = new ArrayList<>(); + File outFile = File.createTempFile("device_serial", "tmp"); + outFile.deleteOnExit(); + Utils.runCommand(outFile, logger, "adb", "devices"); + List<String> outputLines = Files.readLines(outFile, Charset.defaultCharset()); + Pattern regex = Pattern.compile("^(.*)\tdevice$"); + for (String line : outputLines) { + Matcher m = regex.matcher(line); + if (m.find()) { + devices.add(m.group(1)); + } + } + return devices; + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage()); + } + return null; + } + + /** + * Returns ANDROID_SERIAL environment variable, or null if that is undefined or unavailable. + * @param logger Destination of error messages. + * @return String value of ANDROID_SERIAL environment variable, or null. + */ + public static String getDefaultDevice(Logger logger) { + try { + return System.getenv("ANDROID_SERIAL"); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to check ANDROID_SERIAL environment variable.", + ex); + } + return null; + } + + /** + * Returns the device to use if one can be deduced, or null. + * @param device Command-line specified device, or null. + * @param connectedDevices List of all connected devices. + * @param defaultDevice Environment-variable specified device, or null. + * @param logger Destination of error messages. + * @return Device to use, or null. + */ + public static String chooseDevice(String device, List<String> connectedDevices, + String defaultDevice, Logger logger) { + if (connectedDevices == null || connectedDevices.isEmpty()) { + logger.severe("No connected device."); + return null; + } + if (device != null) { + if (connectedDevices.contains(device)) { + return device; + } + logger.severe("Device not connected: " + device); + return null; + } + if (connectedDevices.size() == 1) { + return connectedDevices.get(0); + } + if (defaultDevice != null) { + if (connectedDevices.contains(defaultDevice)) { + return defaultDevice; + } else { + logger.severe("ANDROID_SERIAL device is not connected: " + defaultDevice); + return null; + } + } + logger.severe("More than one device is connected. Choose one" + + " with -s DEVICE_SERIAL or environment variable ANDROID_SERIAL."); + return null; + } } diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java index 2eb46605b28d..ec3c7df7bfba 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java @@ -26,6 +26,7 @@ import com.google.protobuf.TextFormat; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.List; import java.util.logging.Logger; /** @@ -49,7 +50,7 @@ public class LocalDrive { public static final String HELP_STRING = "Usage:\n\n" + - "statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Uploads the given statsd config file (in binary or human-readable-text format).\n" + " If a config with this id already exists, removes it first.\n" + " CONFIG_FILE Location of config file on host.\n" + @@ -59,12 +60,12 @@ public class LocalDrive { // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Same as upload, but does not remove the old config first (if it already exists).\n" + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + " Prints the output statslog data (in binary or human-readable-text format).\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + " --binary Output should be in binary, instead of default human-readable text.\n" + @@ -75,13 +76,13 @@ public class LocalDrive { // --include_current_bucket --proto "\n" + - "statsd_localdrive remove [CONFIG_ID]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" + " Removes the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive clear [CONFIG_ID]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" + " Clears the data associated with the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID @@ -94,29 +95,51 @@ public class LocalDrive { /** Usage: make statsd_localdrive && statsd_localdrive */ public static void main(String[] args) { Utils.setUpLogger(sLogger, DEBUG); + if (args.length == 0) { + printHelp(); + return; + } + + int remainingArgsLength = args.length; + String deviceSerial = null; + if (args[0].equals("-s")) { + if (args.length == 1) { + printHelp(); + } + deviceSerial = args[1]; + remainingArgsLength -= 2; + } + + List<String> connectedDevices = Utils.getDeviceSerials(sLogger); + deviceSerial = Utils.chooseDevice(deviceSerial, connectedDevices, + Utils.getDefaultDevice(sLogger), sLogger); + if (deviceSerial == null) { + return; + } - if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) { + if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) { sLogger.severe("LocalDrive only works with statsd versions for Android " + MIN_CODENAME + " or higher."); return; } - if (args.length > 0) { - switch (args[0]) { + int idx = args.length - remainingArgsLength; + if (remainingArgsLength > 0) { + switch (args[idx]) { case "clear": - cmdClear(args); + cmdClear(args, idx, deviceSerial); return; case "get-data": - cmdGetData(args); + cmdGetData(args, idx, deviceSerial); return; case "remove": - cmdRemove(args); + cmdRemove(args, idx); return; case "update": - cmdUpdate(args); + cmdUpdate(args, idx, deviceSerial); return; case "upload": - cmdUpload(args); + cmdUpload(args, idx, deviceSerial); return; } } @@ -128,17 +151,18 @@ public class LocalDrive { } // upload CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpload(String[] args) { - return updateConfig(args, true); + private static boolean cmdUpload(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, true, deviceSerial); } // update CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpdate(String[] args) { - return updateConfig(args, false); + private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, false, deviceSerial); } - private static boolean updateConfig(String[] args, boolean removeOldConfig) { - int argCount = args.length - 1; // Used up one for upload/update. + private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig, + String deviceSerial) { + int argCount = args.length - 1 - idx; // Used up one for upload/update. // Get CONFIG_FILE if (argCount < 1) { @@ -146,7 +170,7 @@ public class LocalDrive { printHelp(); return false; } - final String origConfigLocation = args[1]; + final String origConfigLocation = args[idx + 1]; if (!new File(origConfigLocation).exists()) { sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation); return false; @@ -154,13 +178,13 @@ public class LocalDrive { argCount--; // Get --binary - boolean binary = contains(args, 2, BINARY_FLAG); + boolean binary = contains(args, idx + 2, BINARY_FLAG); if (binary) argCount --; // Get CONFIG_ID long configId; try { - configId = getConfigId(argCount < 1, args, 2); + configId = getConfigId(argCount < 1, args, idx + 2); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -174,7 +198,8 @@ public class LocalDrive { try { Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, Utils.SHELL_UID, String.valueOf(configId)); - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger); + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (InterruptedException | IOException e) { sLogger.severe("Failed to remove config: " + e.getMessage()); return false; @@ -218,19 +243,19 @@ public class LocalDrive { } // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map] - private static boolean cmdGetData(String[] args) { - boolean binary = contains(args, 1, BINARY_FLAG); - boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG); - boolean clearData = contains(args, 1, CLEAR_DATA); + private static boolean cmdGetData(String[] args, int idx, String deviceSerial) { + boolean binary = contains(args, idx + 1, BINARY_FLAG); + boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG); + boolean clearData = contains(args, idx + 1, CLEAR_DATA); // Get CONFIG_ID - int argCount = args.length - 1; // Used up one for get-data. + int argCount = args.length - 1 - idx; // Used up one for get-data. if (binary) argCount--; if (noUidMap) argCount--; if (clearData) argCount--; long configId; try { - configId = getConfigId(argCount < 1, args, 1); + configId = getConfigId(argCount < 1, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -243,7 +268,8 @@ public class LocalDrive { // Even if the args request no modifications, we still parse it to make sure it's valid. ConfigMetricsReportList reportList; try { - reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger); + reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (IOException | InterruptedException e) { sLogger.severe("Failed to get report list: " + e.getMessage()); return false; @@ -274,11 +300,11 @@ public class LocalDrive { } // clear [CONFIG_ID] - private static boolean cmdClear(String[] args) { + private static boolean cmdClear(String[] args, int idx, String deviceSerial) { // Get CONFIG_ID long configId; try { - configId = getConfigId(false, args, 1); + configId = getConfigId(false, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -287,7 +313,8 @@ public class LocalDrive { sLogger.fine(String.format("cmdClear with %d", configId)); try { - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger); + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (IOException | InterruptedException e) { sLogger.severe("Failed to get report list: " + e.getMessage()); return false; @@ -296,11 +323,11 @@ public class LocalDrive { } // remove [CONFIG_ID] - private static boolean cmdRemove(String[] args) { + private static boolean cmdRemove(String[] args, int idx) { // Get CONFIG_ID long configId; try { - configId = getConfigId(false, args, 1); + configId = getConfigId(false, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index c97f03562857..51bcad115cc5 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -15,27 +15,34 @@ */ package com.android.statsd.shelltools.testdrive; +import com.android.internal.os.StatsdConfigProto; import com.android.internal.os.StatsdConfigProto.AtomMatcher; import com.android.internal.os.StatsdConfigProto.EventMetric; import com.android.internal.os.StatsdConfigProto.FieldFilter; import com.android.internal.os.StatsdConfigProto.GaugeMetric; +import com.android.internal.os.StatsdConfigProto.PullAtomPackages; import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; import com.android.internal.os.StatsdConfigProto.StatsdConfig; import com.android.internal.os.StatsdConfigProto.TimeUnit; import com.android.os.AtomsProto.Atom; +import com.android.os.StatsLog; import com.android.os.StatsLog.ConfigMetricsReport; import com.android.os.StatsLog.ConfigMetricsReportList; import com.android.os.StatsLog.StatsLogReport; import com.android.statsd.shelltools.Utils; +import com.google.common.annotations.VisibleForTesting; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,190 +56,364 @@ public class TestDrive { private static final int VENDOR_PULLED_ATOM_START_TAG = 150000; private static final long CONFIG_ID = 54321; private static final String[] ALLOWED_LOG_SOURCES = { - "AID_GRAPHICS", - "AID_INCIDENTD", - "AID_STATSD", - "AID_RADIO", - "com.android.systemui", - "com.android.vending", - "AID_SYSTEM", - "AID_ROOT", - "AID_BLUETOOTH", - "AID_LMKD", - "com.android.managedprovisioning", - "AID_MEDIA", - "AID_NETWORK_STACK" + "AID_GRAPHICS", + "AID_INCIDENTD", + "AID_STATSD", + "AID_RADIO", + "com.android.systemui", + "com.android.vending", + "AID_SYSTEM", + "AID_ROOT", + "AID_BLUETOOTH", + "AID_LMKD", + "com.android.managedprovisioning", + "AID_MEDIA", + "AID_NETWORK_STACK", + "com.google.android.providers.media.module", + }; + private static final String[] DEFAULT_PULL_SOURCES = { + "AID_SYSTEM", + "AID_RADIO" }; private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); - private String mAdditionalAllowedPackage; - private final Set<Long> mTrackedMetrics = new HashSet<>(); + @VisibleForTesting + String mDeviceSerial = null; + @VisibleForTesting + Dumper mDumper = new BasicDumper(); public static void main(String[] args) { - TestDrive testDrive = new TestDrive(); - Set<Integer> trackedAtoms = new HashSet<>(); + final Configuration configuration = new Configuration(); + final TestDrive testDrive = new TestDrive(); Utils.setUpLogger(LOGGER, false); - String remoteConfigPath = null; + if (!testDrive.processArgs(configuration, args, + Utils.getDeviceSerials(LOGGER), Utils.getDefaultDevice(LOGGER))) { + return; + } + + final ConfigMetricsReportList reports = testDrive.testDriveAndGetReports( + configuration.createConfig(), configuration.hasPulledAtoms(), + configuration.hasPushedAtoms()); + if (reports != null) { + configuration.dumpMetrics(reports, testDrive.mDumper); + } + } + + boolean processArgs(Configuration configuration, String[] args, List<String> connectedDevices, + String defaultDevice) { if (args.length < 1) { - LOGGER.log(Level.SEVERE, "Usage: ./test_drive [-p additional_allowed_package] " + LOGGER.severe("Usage: ./test_drive [-one] " + + "[-p additional_allowed_package] " + + "[-s DEVICE_SERIAL_NUMBER] " + "<atomId1> <atomId2> ... <atomIdN>"); - return; + return false; } - if (args.length >= 3 && args[0].equals("-p")) { - testDrive.mAdditionalAllowedPackage = args[1]; + int first_arg = 0; + // Consume all flags, which must precede all atoms + for (; first_arg < args.length; ++first_arg) { + String arg = args[first_arg]; + int remaining_args = args.length - first_arg; + if (remaining_args >= 2 && arg.equals("-one")) { + LOGGER.info("Creating one event metric to catch all pushed atoms."); + configuration.mOnePushedAtomEvent = true; + } else if (remaining_args >= 2 && arg.equals("-terse")) { + LOGGER.info("Terse output format."); + mDumper = new TerseDumper(); + } else if (remaining_args >= 3 && arg.equals("-p")) { + configuration.mAdditionalAllowedPackage = args[++first_arg]; + } else if (remaining_args >= 3 && arg.equals("-s")) { + mDeviceSerial = args[++first_arg]; + } else { + break; // Found the atom list + } + } + + mDeviceSerial = Utils.chooseDevice(mDeviceSerial, connectedDevices, defaultDevice, LOGGER); + if (mDeviceSerial == null) { + return false; } - for (int i = testDrive.mAdditionalAllowedPackage == null ? 0 : 2; i < args.length; i++) { + for ( ; first_arg < args.length; ++first_arg) { + String atom = args[first_arg]; try { - int atomId = Integer.valueOf(args[i]); - if (Atom.getDescriptor().findFieldByNumber(atomId) == null) { - LOGGER.log(Level.SEVERE, "No such atom found: " + args[i]); - continue; - } - trackedAtoms.add(atomId); + configuration.addAtom(Integer.valueOf(atom)); } catch (NumberFormatException e) { - LOGGER.log(Level.SEVERE, "Bad atom id provided: " + args[i]); - continue; + LOGGER.severe("Bad atom id provided: " + atom); } } + return configuration.hasPulledAtoms() || configuration.hasPushedAtoms(); + } + + private ConfigMetricsReportList testDriveAndGetReports(StatsdConfig config, + boolean hasPulledAtoms, boolean hasPushedAtoms) { + if (config == null) { + LOGGER.severe("Failed to create valid config."); + return null; + } + + String remoteConfigPath = null; try { - StatsdConfig config = testDrive.createConfig(trackedAtoms); - if (config == null) { - LOGGER.log(Level.SEVERE, "Failed to create valid config."); - return; - } - remoteConfigPath = testDrive.pushConfig(config); - LOGGER.info("Pushed the following config to statsd:"); + remoteConfigPath = pushConfig(config, mDeviceSerial); + LOGGER.info("Pushed the following config to statsd on device '" + mDeviceSerial + + "':"); LOGGER.info(config.toString()); - if (!hasPulledAtom(trackedAtoms)) { + if (hasPushedAtoms) { + LOGGER.info("Now please play with the device to trigger the event."); + } + if (!hasPulledAtoms) { LOGGER.info( - "Now please play with the device to trigger the event. All events should " - + "be dumped after 1 min ..."); + "All events should be dumped after 1 min ..."); Thread.sleep(60_000); } else { - LOGGER.info("Now wait for 1.5 minutes ..."); + LOGGER.info("All events should be dumped after 1.5 minutes ..."); Thread.sleep(15_000); - Utils.logAppBreadcrumb(0, 0, LOGGER); + Utils.logAppBreadcrumb(0, 0, LOGGER, mDeviceSerial); Thread.sleep(75_000); } - testDrive.dumpMetrics(); + return Utils.getReportList(CONFIG_ID, true, false, LOGGER, + mDeviceSerial); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e); } finally { - testDrive.removeConfig(); + removeConfig(mDeviceSerial); if (remoteConfigPath != null) { try { - Utils.runCommand(null, LOGGER, "adb", "shell", "rm", remoteConfigPath); + Utils.runCommand(null, LOGGER, + "adb", "-s", mDeviceSerial, "shell", "rm", + remoteConfigPath); } catch (Exception e) { LOGGER.log(Level.WARNING, "Unable to remove remote config file: " + remoteConfigPath, e); } } } + return null; } - private void dumpMetrics() throws Exception { - ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER); - // We may get multiple reports. Take the last one. - ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); - for (StatsLogReport statsLog : report.getMetricsList()) { - if (mTrackedMetrics.contains(statsLog.getMetricId())) { - LOGGER.info(statsLog.toString()); + static class Configuration { + boolean mOnePushedAtomEvent = false; + @VisibleForTesting + Set<Integer> mPushedAtoms = new TreeSet<>(); + @VisibleForTesting + Set<Integer> mPulledAtoms = new TreeSet<>(); + @VisibleForTesting + String mAdditionalAllowedPackage = null; + private final Set<Long> mTrackedMetrics = new HashSet<>(); + + private void dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper) { + // We may get multiple reports. Take the last one. + ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); + for (StatsLogReport statsLog : report.getMetricsList()) { + if (isTrackedMetric(statsLog.getMetricId())) { + dumper.dump(statsLog); + } } } - } - private StatsdConfig createConfig(Set<Integer> atomIds) { - long metricId = METRIC_ID_BASE; - long atomMatcherId = ATOM_MATCHER_ID_BASE; + boolean isTrackedMetric(long metricId) { + return mTrackedMetrics.contains(metricId); + } - ArrayList<String> allowedSources = new ArrayList<>(); - Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES); - if (mAdditionalAllowedPackage != null) { - allowedSources.add(mAdditionalAllowedPackage); + static boolean isPulledAtom(int atomId) { + return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG + || atomId >= VENDOR_PULLED_ATOM_START_TAG; } - StatsdConfig.Builder builder = StatsdConfig.newBuilder(); - builder - .addAllAllowedLogSource(allowedSources) - .setHashStringsInMetricReport(false); + void addAtom(Integer atom) { + if (Atom.getDescriptor().findFieldByNumber(atom) == null) { + LOGGER.severe("No such atom found: " + atom); + return; + } + if (isPulledAtom(atom)) { + mPulledAtoms.add(atom); + } else { + mPushedAtoms.add(atom); + } + } - if (hasPulledAtom(atomIds)) { - builder.addAtomMatcher( - createAtomMatcher( - Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, APP_BREADCRUMB_MATCHER_ID)); + private boolean hasPulledAtoms() { + return !mPulledAtoms.isEmpty(); } - for (int atomId : atomIds) { - if (isPulledAtom(atomId)) { + private boolean hasPushedAtoms() { + return !mPushedAtoms.isEmpty(); + } + + StatsdConfig createConfig() { + long metricId = METRIC_ID_BASE; + long atomMatcherId = ATOM_MATCHER_ID_BASE; + + StatsdConfig.Builder builder = baseBuilder(); + + if (hasPulledAtoms()) { + builder.addAtomMatcher( + createAtomMatcher( + Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + APP_BREADCRUMB_MATCHER_ID)); + } + + for (int atomId : mPulledAtoms) { builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId)); GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); gaugeMetricBuilder - .setId(metricId) - .setWhat(atomMatcherId) - .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID) - .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) - .setBucket(TimeUnit.ONE_MINUTE) - .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) - .setMaxNumGaugeAtomsPerBucket(100); + .setId(metricId) + .setWhat(atomMatcherId) + .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID) + .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) + .setBucket(TimeUnit.ONE_MINUTE) + .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) + .setMaxNumGaugeAtomsPerBucket(100); builder.addGaugeMetric(gaugeMetricBuilder.build()); - } else { + atomMatcherId++; + mTrackedMetrics.add(metricId++); + } + + // A simple atom matcher for each pushed atom. + List<AtomMatcher> simpleAtomMatchers = new ArrayList<>(); + for (int atomId : mPushedAtoms) { + final AtomMatcher atomMatcher = createAtomMatcher(atomId, atomMatcherId++); + simpleAtomMatchers.add(atomMatcher); + builder.addAtomMatcher(atomMatcher); + } + + if (mOnePushedAtomEvent) { + // Create a union event metric, using an matcher that matches all pulled atoms. + AtomMatcher unionAtomMatcher = createUnionMatcher(simpleAtomMatchers, + atomMatcherId); + builder.addAtomMatcher(unionAtomMatcher); EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); - eventMetricBuilder - .setId(metricId) - .setWhat(atomMatcherId); + eventMetricBuilder.setId(metricId).setWhat(unionAtomMatcher.getId()); builder.addEventMetric(eventMetricBuilder.build()); - builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId)); + mTrackedMetrics.add(metricId++); + } else { + // Create multiple event metrics, one per pulled atom. + for (AtomMatcher atomMatcher : simpleAtomMatchers) { + EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); + eventMetricBuilder + .setId(metricId) + .setWhat(atomMatcher.getId()); + builder.addEventMetric(eventMetricBuilder.build()); + mTrackedMetrics.add(metricId++); + } } - atomMatcherId++; - mTrackedMetrics.add(metricId++); + + return builder.build(); + } + + private static AtomMatcher createAtomMatcher(int atomId, long matcherId) { + AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); + atomMatcherBuilder + .setId(matcherId) + .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId)); + return atomMatcherBuilder.build(); + } + + private AtomMatcher createUnionMatcher(List<AtomMatcher> simpleAtomMatchers, + long atomMatcherId) { + AtomMatcher.Combination.Builder combinationBuilder = + AtomMatcher.Combination.newBuilder(); + combinationBuilder.setOperation(StatsdConfigProto.LogicalOperation.OR); + for (AtomMatcher matcher : simpleAtomMatchers) { + combinationBuilder.addMatcher(matcher.getId()); + } + AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); + atomMatcherBuilder.setId(atomMatcherId).setCombination(combinationBuilder.build()); + return atomMatcherBuilder.build(); + } + + private StatsdConfig.Builder baseBuilder() { + ArrayList<String> allowedSources = new ArrayList<>(); + Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES); + if (mAdditionalAllowedPackage != null) { + allowedSources.add(mAdditionalAllowedPackage); + } + return StatsdConfig.newBuilder() + .addAllAllowedLogSource(allowedSources) + .addAllDefaultPullPackages(Arrays.asList(DEFAULT_PULL_SOURCES)) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.GPU_STATS_GLOBAL_INFO_FIELD_NUMBER) + .addPackages("AID_GPU_SERVICE")) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.GPU_STATS_APP_INFO_FIELD_NUMBER) + .addPackages("AID_GPU_SERVICE")) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER) + .addPackages("AID_STATSD")) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER) + .addPackages("com.google.android.providers.media.module")) + .setHashStringsInMetricReport(false); + } + } + + interface Dumper { + void dump(StatsLogReport report); + } + + static class BasicDumper implements Dumper { + @Override + public void dump(StatsLogReport report) { + System.out.println(report.toString()); } - return builder.build(); } - private static AtomMatcher createAtomMatcher(int atomId, long matcherId) { - AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); - atomMatcherBuilder - .setId(matcherId) - .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId)); - return atomMatcherBuilder.build(); + static class TerseDumper extends BasicDumper { + @Override + public void dump(StatsLogReport report) { + if (report.hasGaugeMetrics()) { + dumpGaugeMetrics(report); + } + if (report.hasEventMetrics()) { + dumpEventMetrics(report); + } + } + void dumpEventMetrics(StatsLogReport report) { + final List<StatsLog.EventMetricData> data = report.getEventMetrics().getDataList(); + if (data.isEmpty()) { + return; + } + long firstTimestampNanos = data.get(0).getElapsedTimestampNanos(); + for (StatsLog.EventMetricData event : data) { + final double deltaSec = (event.getElapsedTimestampNanos() - firstTimestampNanos) + / 1e9; + System.out.println( + String.format("+%.3fs: %s", deltaSec, event.getAtom().toString())); + } + } + void dumpGaugeMetrics(StatsLogReport report) { + final List<StatsLog.GaugeMetricData> data = report.getGaugeMetrics().getDataList(); + if (data.isEmpty()) { + return; + } + for (StatsLog.GaugeMetricData gauge : data) { + System.out.println(gauge.toString()); + } + } } - private static String pushConfig(StatsdConfig config) throws IOException, InterruptedException { + private static String pushConfig(StatsdConfig config, String deviceSerial) + throws IOException, InterruptedException { File configFile = File.createTempFile("statsdconfig", ".config"); configFile.deleteOnExit(); Files.write(config.toByteArray(), configFile); String remotePath = "/data/local/tmp/" + configFile.getName(); - Utils.runCommand(null, LOGGER, "adb", "push", configFile.getAbsolutePath(), remotePath); - Utils.runCommand(null, LOGGER, - "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "push", configFile.getAbsolutePath(), remotePath); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, String.valueOf(CONFIG_ID)); return remotePath; } - private static void removeConfig() { + private static void removeConfig(String deviceSerial) { try { - Utils.runCommand(null, LOGGER, - "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Failed to remove config: " + e.getMessage()); - } - } - - private static boolean isPulledAtom(int atomId) { - return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG - || atomId >= VENDOR_PULLED_ATOM_START_TAG; - } - - private static boolean hasPulledAtom(Set<Integer> atoms) { - for (Integer i : atoms) { - if (isPulledAtom(i)) { - return true; - } + LOGGER.severe("Failed to remove config: " + e.getMessage()); } - return false; } } diff --git a/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java new file mode 100644 index 000000000000..b1cc60f74993 --- /dev/null +++ b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/ConfigurationTest.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2020 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 com.android.statsd.shelltools.testdrive; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.android.internal.os.StatsdConfigProto; +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.os.AtomsProto; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Tests for {@link TestDrive} + */ +public class ConfigurationTest { + + private StatsdConfigProto.AtomMatcher findAndRemoveAtomMatcherById( + List<StatsdConfigProto.AtomMatcher> atomMatchers, long id) { + int numMatches = 0; + StatsdConfigProto.AtomMatcher match = null; + for (StatsdConfigProto.AtomMatcher atomMatcher : atomMatchers) { + if (id == atomMatcher.getId()) { + ++numMatches; + match = atomMatcher; + } + } + if (numMatches == 1) { + atomMatchers.remove(match); + return match; + } + return null; // Too many, or not found + } + + private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration(); + + @Test + public void testOnePushed() { + final int atom = 90; + assertFalse(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + StatsdConfig config = mConfiguration.createConfig(); + + //event_metric { + // id: 1111 + // what: 1234567 + //} + //atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 90 + // } + //} + + assertEquals(1, config.getEventMetricCount()); + assertEquals(0, config.getGaugeMetricCount()); + + assertTrue(mConfiguration.isTrackedMetric(config.getEventMetric(0).getId())); + + final List<StatsdConfigProto.AtomMatcher> atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + assertEquals(atom, + findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(0, atomMatchers.size()); + } + + @Test + public void testOnePulled() { + final int atom = 10022; + assertTrue(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + StatsdConfig config = mConfiguration.createConfig(); + + //gauge_metric { + // id: 1111 + // what: 1234567 + // gauge_fields_filter { + // include_all: true + // } + // bucket: ONE_MINUTE + // sampling_type: FIRST_N_SAMPLES + // max_num_gauge_atoms_per_bucket: 100 + // trigger_event: 1111111 + //} + //atom_matcher { + // id: 1111111 + // simple_atom_matcher { + // atom_id: 47 + // } + //} + //atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 10022 + // } + //} + + assertEquals(0, config.getEventMetricCount()); + assertEquals(1, config.getGaugeMetricCount()); + + assertTrue(mConfiguration.isTrackedMetric(config.getGaugeMetric(0).getId())); + + final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); + assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); + + final List<StatsdConfigProto.AtomMatcher> atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + assertEquals(atom, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(0, atomMatchers.size()); + } + + @Test + public void testOnePulledTwoPushed() { + final int pulledAtom = 10022; + assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom)); + mConfiguration.addAtom(pulledAtom); + + Integer[] pushedAtoms = new Integer[]{244, 245}; + for (int atom : pushedAtoms) { + assertFalse(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + } + StatsdConfig config = mConfiguration.createConfig(); + + // event_metric { + // id: 1111 + // what: 1234567 + // } + // event_metric { + // id: 1112 + // what: 1234568 + // } + // gauge_metric { + // id: 1114 + // what: 1234570 + // gauge_fields_filter { + // include_all: true + // } + // bucket: ONE_MINUTE + // sampling_type: FIRST_N_SAMPLES + // max_num_gauge_atoms_per_bucket: 100 + // trigger_event: 1111111 + // } + // atom_matcher { + // id: 1111111 + // simple_atom_matcher { + // atom_id: 47 + // } + // } + // atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 244 + // } + // } + // atom_matcher { + // id: 1234568 + // simple_atom_matcher { + // atom_id: 245 + // } + // } + // atom_matcher { + // id: 1234570 + // simple_atom_matcher { + // atom_id: 10022 + // } + // } + + assertEquals(2, config.getEventMetricCount()); + assertEquals(1, config.getGaugeMetricCount()); + + final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); + assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId())); + assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); + for (StatsdConfigProto.EventMetric eventMetric : config.getEventMetricList()) { + assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId())); + } + + final List<StatsdConfigProto.AtomMatcher> atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + + assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) + .getSimpleAtomMatcher().getAtomId()); + + Integer[] actualAtoms = new Integer[]{ + findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(0).getWhat()) + .getSimpleAtomMatcher().getAtomId(), + findAndRemoveAtomMatcherById(atomMatchers, config.getEventMetric(1).getWhat()) + .getSimpleAtomMatcher().getAtomId()}; + Arrays.sort(actualAtoms); + assertArrayEquals(pushedAtoms, actualAtoms); + + assertEquals(0, atomMatchers.size()); + } + + @Test + public void testOnePulledTwoPushedTogether() { + mConfiguration.mOnePushedAtomEvent = true; // Use one event grabbing all pushed atoms + + final int pulledAtom = 10022; + assertTrue(TestDrive.Configuration.isPulledAtom(pulledAtom)); + mConfiguration.addAtom(pulledAtom); + + Integer[] pushedAtoms = new Integer[]{244, 245}; + for (int atom : pushedAtoms) { + assertFalse(TestDrive.Configuration.isPulledAtom(atom)); + mConfiguration.addAtom(atom); + } + StatsdConfig config = mConfiguration.createConfig(); + + // event_metric { + // id: 1112 + // what: 1234570 + // } + // gauge_metric { + // id: 1111 + // what: 1234567 + // gauge_fields_filter { + // include_all: true + // } + // bucket: ONE_MINUTE + // sampling_type: FIRST_N_SAMPLES + // max_num_gauge_atoms_per_bucket: 100 + // trigger_event: 1111111 + // } + // atom_matcher { + // id: 1111111 + // simple_atom_matcher { + // atom_id: 47 + // } + // } + // atom_matcher { + // id: 1234567 + // simple_atom_matcher { + // atom_id: 10022 + // } + // } + // atom_matcher { + // id: 1234568 + // simple_atom_matcher { + // atom_id: 244 + // } + // } + // atom_matcher { + // id: 1234569 + // simple_atom_matcher { + // atom_id: 245 + // } + // } + // atom_matcher { + // id: 1234570 + // combination { + // operation: OR + // matcher: 1234568 + // matcher: 1234569 + // } + // } + + assertEquals(1, config.getEventMetricCount()); + assertEquals(1, config.getGaugeMetricCount()); + + final StatsdConfigProto.GaugeMetric gaugeMetric = config.getGaugeMetric(0); + assertTrue(mConfiguration.isTrackedMetric(gaugeMetric.getId())); + assertTrue(gaugeMetric.getGaugeFieldsFilter().getIncludeAll()); + + StatsdConfigProto.EventMetric eventMetric = config.getEventMetric(0); + assertTrue(mConfiguration.isTrackedMetric(eventMetric.getId())); + + final List<StatsdConfigProto.AtomMatcher> atomMatchers = + new ArrayList<>(config.getAtomMatcherList()); + + assertEquals(pulledAtom, findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getWhat()) + .getSimpleAtomMatcher().getAtomId()); + assertEquals(AtomsProto.Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, + findAndRemoveAtomMatcherById(atomMatchers, gaugeMetric.getTriggerEvent()) + .getSimpleAtomMatcher().getAtomId()); + + StatsdConfigProto.AtomMatcher unionMatcher = findAndRemoveAtomMatcherById(atomMatchers, + eventMetric.getWhat()); + assertNotNull(unionMatcher.getCombination()); + assertEquals(2, unionMatcher.getCombination().getMatcherCount()); + + Integer[] actualAtoms = new Integer[]{ + findAndRemoveAtomMatcherById(atomMatchers, + unionMatcher.getCombination().getMatcher(0)) + .getSimpleAtomMatcher().getAtomId(), + findAndRemoveAtomMatcherById(atomMatchers, + unionMatcher.getCombination().getMatcher(1)) + .getSimpleAtomMatcher().getAtomId()}; + Arrays.sort(actualAtoms); + assertArrayEquals(pushedAtoms, actualAtoms); + + assertEquals(0, atomMatchers.size()); + } +} diff --git a/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java new file mode 100644 index 000000000000..363fac0c78ba --- /dev/null +++ b/cmds/statsd/tools/localtools/test/com/android/statsd/shelltools/testdrive/TestDriveTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2020 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 com.android.statsd.shelltools.testdrive; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Tests for {@link TestDrive} + */ +@RunWith(Parameterized.class) +public class TestDriveTest { + /** + * Expected results of a single iteration of the paramerized test. + */ + static class Expect { + public boolean success; + public Integer[] atoms; + public boolean onePushedAtomEvent = false; + public String extraPackage = null; + public String target; + public boolean terse = false; + + static Expect success(Integer... atoms) { + return new Expect(true, atoms, + TARGET); + } + Expect(boolean success, Integer[] atoms, String target) { + this.success = success; + this.atoms = atoms; + this.target = target; + } + static final Expect FAILURE = new Expect(false, null, null); + Expect onePushedAtomEvent() { + this.onePushedAtomEvent = true; + return this; + } + Expect extraPackage() { + this.extraPackage = TestDriveTest.PACKAGE; + return this; + } + Expect terse() { + this.terse = true; + return this; + } + } + + @Parameterized.Parameter(0) + public String[] mArgs; + + @Parameterized.Parameter(1) + public List<String> mConnectedDevices; + + @Parameterized.Parameter(2) + public String mDefaultDevice; + + @Parameterized.Parameter(3) + public Expect mExpect; + + private static final String TARGET = "target"; + private static final List<String> TARGET_AND_OTHER = Arrays.asList("otherDevice", + TARGET); + private static final List<String> TWO_OTHER_DEVICES = Arrays.asList( + "other1", "other2"); + private static final List<String> TARGET_ONLY = Collections.singletonList(TARGET); + private static final List<String> NOT_TARGET = Collections.singletonList("other"); + private static final List<String> NO_DEVICES = Collections.emptyList(); + private static final String PACKAGE = "extraPackage"; + + @Parameterized.Parameters + public static Collection<Object[]> data() { + return Arrays.asList( + new Object[]{new String[]{}, null, null, + Expect.FAILURE}, // Usage explanation + new Object[]{new String[]{"244", "245"}, null, null, + Expect.FAILURE}, // Failure looking up connected devices + new Object[]{new String[]{"244", "245"}, NO_DEVICES, null, + Expect.FAILURE}, // No connected devices + new Object[]{new String[]{"-s", TARGET, "244", "245"}, NOT_TARGET, null, + Expect.FAILURE}, // Wrong device connected + new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, null, + Expect.FAILURE}, // Wrong devices connected + new Object[]{new String[]{"244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245)}, // If only one device connected, guess that one + new Object[]{new String[]{"244", "not_an_atom"}, TARGET_ONLY, null, + Expect.success(244)}, // Ignore non-atoms + new Object[]{new String[]{"not_an_atom"}, TARGET_ONLY, null, + Expect.FAILURE}, // Require at least one atom + new Object[]{new String[]{"244", "245"}, TWO_OTHER_DEVICES, TARGET, + Expect.FAILURE}, // ANDROID_SERIAL specifies non-connected target + new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, TARGET, + Expect.success(244, 245)}, // ANDROID_SERIAL specifies a valid target + new Object[]{new String[]{"244", "245"}, TARGET_AND_OTHER, null, + Expect.FAILURE}, // Two connected devices, no indication of which to use + new Object[]{new String[]{"-one", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).onePushedAtomEvent()}, + new Object[]{new String[]{"-terse", "-one", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-terse", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-p", PACKAGE, "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).extraPackage()}, + new Object[]{new String[]{"-p", PACKAGE, "-one", "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "244", "245"}, TARGET_ONLY, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-s", TARGET, "-one", "-p", PACKAGE, "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-one", "-s", TARGET, "-p", PACKAGE, "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent()}, + new Object[]{new String[]{"-terse", "-one", "-p", PACKAGE, "-s", TARGET, + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-terse", "-p", PACKAGE, "-s", TARGET, + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "-terse", "-s", TARGET, + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()}, + new Object[]{new String[]{"-one", "-p", PACKAGE, "-s", TARGET, "-terse", + "244", "245"}, + TARGET_AND_OTHER, null, + Expect.success(244, 245).extraPackage().onePushedAtomEvent().terse()} + ); + } + + private final TestDrive.Configuration mConfiguration = new TestDrive.Configuration(); + private final TestDrive mTestDrive = new TestDrive(); + + private static Integer[] collectAtoms(TestDrive.Configuration configuration) { + Integer[] result = new Integer[configuration.mPulledAtoms.size() + + configuration.mPushedAtoms.size()]; + int result_index = 0; + for (Integer atom : configuration.mPushedAtoms) { + result[result_index++] = atom; + } + for (Integer atom : configuration.mPulledAtoms) { + result[result_index++] = atom; + } + Arrays.sort(result); + return result; + } + + @Test + public void testProcessArgs() { + boolean result = mTestDrive.processArgs(mConfiguration, mArgs, mConnectedDevices, + mDefaultDevice); + if (mExpect.success) { + assertTrue(result); + assertArrayEquals(mExpect.atoms, collectAtoms(mConfiguration)); + assertEquals(mExpect.onePushedAtomEvent, mConfiguration.mOnePushedAtomEvent); + assertEquals(mExpect.target, mTestDrive.mDeviceSerial); + if (mExpect.terse) { + assertEquals(TestDrive.TerseDumper.class, mTestDrive.mDumper.getClass()); + } else { + assertEquals(TestDrive.BasicDumper.class, mTestDrive.mDumper.getClass()); + } + } else { + assertFalse(result); + } + } +} diff --git a/cmds/svc/src/com/android/commands/svc/Svc.java b/cmds/svc/src/com/android/commands/svc/Svc.java index de6e8857c1e5..2ed2678bc877 100644 --- a/cmds/svc/src/com/android/commands/svc/Svc.java +++ b/cmds/svc/src/com/android/commands/svc/Svc.java @@ -93,7 +93,7 @@ public class Svc { public static final Command[] COMMANDS = new Command[] { COMMAND_HELP, new PowerCommand(), - new WifiCommand(), + // `svc wifi` has been migrated to WifiShellCommand new UsbCommand(), new NfcCommand(), new BluetoothCommand(), diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java index 3893be49e739..cd751f4e2d20 100644 --- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java +++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java @@ -42,7 +42,9 @@ public class UsbCommand extends Svc.Command { + " Sets the functions which, if the device was charging, become current on" + "screen unlock. If function is blank, turn off this feature.\n" + " svc usb getFunctions\n" - + " Gets the list of currently enabled functions\n\n" + + " Gets the list of currently enabled functions\n" + + " svc usb resetUsbGadget\n" + + " Reset usb gadget\n\n" + "possible values of [function] are any of 'mtp', 'ptp', 'rndis', 'midi'\n"; } @@ -75,6 +77,13 @@ public class UsbCommand extends Svc.Command { System.err.println("Error communicating with UsbManager: " + e); } return; + } else if ("resetUsbGadget".equals(args[1])) { + try { + usbMgr.resetUsbGadget(); + } catch (RemoteException e) { + System.err.println("Error communicating with UsbManager: " + e); + } + return; } } System.err.println(longHelp()); diff --git a/cmds/svc/src/com/android/commands/svc/WifiCommand.java b/cmds/svc/src/com/android/commands/svc/WifiCommand.java deleted file mode 100644 index e31cb5381afc..000000000000 --- a/cmds/svc/src/com/android/commands/svc/WifiCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.commands.svc; - -import android.os.ServiceManager; -import android.os.RemoteException; -import android.net.wifi.IWifiManager; -import android.content.Context; - -public class WifiCommand extends Svc.Command { - public WifiCommand() { - super("wifi"); - } - - public String shortHelp() { - return "Control the Wi-Fi manager"; - } - - public String longHelp() { - return shortHelp() + "\n" - + "\n" - + "usage: svc wifi [enable|disable]\n" - + " Turn Wi-Fi on or off.\n\n"; - } - - public void run(String[] args) { - boolean validCommand = false; - if (args.length >= 2) { - boolean flag = false; - if ("enable".equals(args[1])) { - flag = true; - validCommand = true; - } else if ("disable".equals(args[1])) { - flag = false; - validCommand = true; - } - if (validCommand) { - IWifiManager wifiMgr - = IWifiManager.Stub.asInterface(ServiceManager.getService(Context.WIFI_SERVICE)); - if (wifiMgr == null) { - System.err.println("Wi-Fi service is not ready"); - return; - } - try { - wifiMgr.setWifiEnabled("com.android.shell", flag); - } - catch (RemoteException e) { - System.err.println("Wi-Fi operation failed: " + e); - } - return; - } - } - System.err.println(longHelp()); - } -} diff --git a/cmds/svc/svc b/cmds/svc/svc index 7431aea4dc14..95265e817c1b 100755 --- a/cmds/svc/svc +++ b/cmds/svc/svc @@ -1,5 +1,24 @@ #!/system/bin/sh +# `svc wifi` has been migrated to WifiShellCommand, +# simply perform translation to `cmd wifi set-wifi-enabled` here. +if [ "x$1" == "xwifi" ]; then + # `cmd wifi` by convention uses enabled/disabled + # instead of enable/disable + if [ "x$2" == "xenable" ]; then + exec cmd wifi set-wifi-enabled enabled + elif [ "x$2" == "xdisable" ]; then + exec cmd wifi set-wifi-enabled disabled + else + echo "Control the Wi-Fi manager" + echo "" + echo "usage: svc wifi [enable|disable]" + echo " Turn Wi-Fi on or off." + echo "" + fi + exit 1 +fi + if [ "x$1" == "xdata" ]; then if [ "x$2" == "xenable" ]; then exec cmd phone data enable @@ -16,3 +35,4 @@ fi export CLASSPATH=/system/framework/svc.jar exec app_process /system/bin com.android.commands.svc.Svc "$@" + diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 24d65fb125ec..5f13a5ce3aae 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -46,6 +46,10 @@ public final class Telecom extends BaseCommand { * @param args The command-line arguments */ public static void main(String[] args) { + // Initialize the telephony module. + // TODO: Do it in zygote and RuntimeInit. b/148897549 + ActivityThread.initializeMainlineModules(); + (new Telecom()).run(args); } diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java index c35f7fc3fcc8..3b14be7327f7 100644 --- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java +++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java @@ -16,6 +16,7 @@ package com.android.commands.uiautomator; +import android.accessibilityservice.AccessibilityServiceInfo; import android.app.UiAutomation; import android.graphics.Point; import android.hardware.display.DisplayManagerGlobal; @@ -61,11 +62,14 @@ public class DumpCommand extends Command { public void run(String[] args) { File dumpFile = DEFAULT_DUMP_FILE; boolean verboseMode = true; + boolean allWindows = false; for (String arg : args) { if (arg.equals("--compressed")) verboseMode = false; - else if (!arg.startsWith("-")) { + else if (arg.equals("--windows")) { + allWindows = true; + } else if (!arg.startsWith("-")) { dumpFile = new File(arg); } } @@ -85,18 +89,28 @@ public class DumpCommand extends Command { try { UiAutomation uiAutomation = automationWrapper.getUiAutomation(); uiAutomation.waitForIdle(1000, 1000 * 10); - AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow(); - if (info == null) { - System.err.println("ERROR: null root node returned by UiTestAutomationBridge."); - return; - } + if (allWindows) { + AccessibilityServiceInfo info = uiAutomation.getServiceInfo(); + info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; + uiAutomation.setServiceInfo(info); + AccessibilityNodeInfoDumper.dumpWindowsToFile( + uiAutomation.getWindowsOnAllDisplays(), dumpFile, + DisplayManagerGlobal.getInstance()); + } else { + AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow(); + if (info == null) { + System.err.println("ERROR: null root node returned by UiTestAutomationBridge."); + return; + } - Display display = - DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); - int rotation = display.getRotation(); - Point size = new Point(); - display.getSize(size); - AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y); + Display display = + DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); + int rotation = display.getRotation(); + Point size = new Point(); + display.getSize(size); + AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, + size.y); + } } catch (TimeoutException re) { System.err.println("ERROR: could not get idle state."); return; diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java index 63c51e84d74a..ab198b319e27 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java @@ -16,11 +16,17 @@ package com.android.uiautomator.core; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.display.DisplayManagerGlobal; import android.os.Environment; import android.os.SystemClock; import android.util.Log; +import android.util.SparseArray; import android.util.Xml; +import android.view.Display; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import org.xmlpull.v1.XmlSerializer; @@ -28,6 +34,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.StringWriter; +import java.util.List; /** * @@ -98,6 +105,95 @@ public class AccessibilityNodeInfoDumper { Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms"); } + /** + * Using {@link AccessibilityWindowInfo} this method will dump some window information and + * then walk the layout hierarchy of it's + * and generates an xml dump to the location specified by <code>dumpFile</code> + * @param allWindows All windows indexed by display-id. + * @param dumpFile The file to dump to. + */ + public static void dumpWindowsToFile(SparseArray<List<AccessibilityWindowInfo>> allWindows, + File dumpFile, DisplayManagerGlobal displayManager) { + if (allWindows.size() == 0) { + return; + } + final long startTime = SystemClock.uptimeMillis(); + try { + FileWriter writer = new FileWriter(dumpFile); + XmlSerializer serializer = Xml.newSerializer(); + StringWriter stringWriter = new StringWriter(); + serializer.setOutput(stringWriter); + serializer.startDocument("UTF-8", true); + serializer.startTag("", "displays"); + for (int d = 0, nd = allWindows.size(); d < nd; ++d) { + int displayId = allWindows.keyAt(d); + Display display = displayManager.getRealDisplay(displayId); + if (display == null) { + continue; + } + final List<AccessibilityWindowInfo> windows = allWindows.valueAt(d); + if (windows.isEmpty()) { + continue; + } + serializer.startTag("", "display"); + serializer.attribute("", "id", Integer.toString(displayId)); + int rotation = display.getRotation(); + Point size = new Point(); + display.getSize(size); + for (int i = 0, n = windows.size(); i < n; ++i) { + dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation); + } + serializer.endTag("", "display"); + } + serializer.endTag("", "displays"); + serializer.endDocument(); + writer.write(stringWriter.toString()); + writer.close(); + } catch (IOException e) { + Log.e(LOGTAG, "failed to dump window to file", e); + } + final long endTime = SystemClock.uptimeMillis(); + Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms"); + } + + private static void dumpWindowRec(AccessibilityWindowInfo winfo, XmlSerializer serializer, + int index, int width, int height, int rotation) throws IOException { + serializer.startTag("", "window"); + serializer.attribute("", "index", Integer.toString(index)); + final CharSequence title = winfo.getTitle(); + serializer.attribute("", "title", title != null ? title.toString() : ""); + final Rect tmpBounds = new Rect(); + winfo.getBoundsInScreen(tmpBounds); + serializer.attribute("", "bounds", tmpBounds.toShortString()); + serializer.attribute("", "active", Boolean.toString(winfo.isActive())); + serializer.attribute("", "focused", Boolean.toString(winfo.isFocused())); + serializer.attribute("", "accessibility-focused", + Boolean.toString(winfo.isAccessibilityFocused())); + serializer.attribute("", "id", Integer.toString(winfo.getId())); + serializer.attribute("", "layer", Integer.toString(winfo.getLayer())); + serializer.attribute("", "type", AccessibilityWindowInfo.typeToString(winfo.getType())); + int count = winfo.getChildCount(); + for (int i = 0; i < count; ++i) { + AccessibilityWindowInfo child = winfo.getChild(i); + if (child == null) { + Log.i(LOGTAG, String.format("Null window child %d/%d, parent: %s", i, count, + winfo.getTitle())); + continue; + } + dumpWindowRec(child, serializer, i, width, height, rotation); + child.recycle(); + } + AccessibilityNodeInfo root = winfo.getRoot(); + if (root != null) { + serializer.startTag("", "hierarchy"); + serializer.attribute("", "rotation", Integer.toString(rotation)); + dumpNodeRec(root, serializer, 0, width, height); + root.recycle(); + serializer.endTag("", "hierarchy"); + } + serializer.endTag("", "window"); + } + private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,int index, int width, int height) throws IOException { serializer.startTag("", "node"); diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java index 455e4bbc0b76..b23bf5da5c8d 100644 --- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java +++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java @@ -67,7 +67,7 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { throw new IllegalStateException("Could not find provider: " + providerName); } provider = holder.provider; - cursor = provider.query(null, Settings.Secure.CONTENT_URI, + cursor = provider.query(null, null, Settings.Secure.CONTENT_URI, new String[] { Settings.Secure.VALUE }, |