public void testStatsBucketResize() throws Exception { NetworkStatsHistory history = null; assertStatsFilesExist(false); // pretend that wifi network comes online; service should ask about full // network state, and poll any existing interfaces before updating. expectCurrentTime(); expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); expectNetworkState(buildWifiState()); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); expectNetworkStatsPoll(); replay(); mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); verifyAndReset(); // modify some number on wifi, and trigger poll event incrementCurrentTime(2 * HOUR_IN_MILLIS); expectCurrentTime(); expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); expectNetworkStatsSummary( new NetworkStats(getElapsedRealtime(), 1).addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L)); expectNetworkStatsUidDetail(buildEmptyStats()); expectNetworkStatsPoll(); replay(); mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL)); // verify service recorded history history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL); assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0); assertEquals(HOUR_IN_MILLIS, history.getBucketDuration()); assertEquals(2, history.size()); verifyAndReset(); // now change bucket duration setting and trigger another poll with // exact same values, which should resize existing buckets. expectCurrentTime(); expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); expectNetworkStatsPoll(); replay(); mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL)); // verify identical stats, but spread across 4 buckets now history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL); assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0); assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration()); assertEquals(4, history.size()); verifyAndReset(); }
public long getMaxVisible() { final long maxVisible = mEstimateVisible ? mMaxEstimate : mMax; if (maxVisible <= 0 && mStats != null) { // haven't generated path yet; fall back to raw data final NetworkStatsHistory.Entry entry = mStats.getValues(mStart, mEnd, null); return entry.rxBytes + entry.txBytes; } else { return maxVisible; } }
private static void assertValues( NetworkStatsHistory stats, long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) { final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null); assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); assertEquals("unexpected txBytes", txBytes, entry.txBytes); assertEquals("unexpected txPackets", txPackets, entry.txPackets); assertEquals("unexpected operations", operations, entry.operations); }
/** * Erase any existing {@link Path} and generate series outline based on currently bound {@link * NetworkStatsHistory} data. */ private void generatePath() { if (LOGD) Log.d(TAG, "generatePath()"); mMax = 0; mPathStroke.reset(); mPathFill.reset(); mPathEstimate.reset(); mPathValid = true; // bail when not enough stats to render if (mStats == null || mStats.size() < 2) { return; } final int width = getWidth(); final int height = getHeight(); boolean started = false; float lastX = 0; float lastY = height; long lastTime = mHoriz.convertToValue(lastX); // move into starting position mPathStroke.moveTo(lastX, lastY); mPathFill.moveTo(lastX, lastY); // TODO: count fractional data from first bucket crossing start; // currently it only accepts first full bucket. long totalData = 0; NetworkStatsHistory.Entry entry = null; final int start = mStats.getIndexBefore(mStart); final int end = mStats.getIndexAfter(mEnd); for (int i = start; i <= end; i++) { entry = mStats.getValues(i, entry); final long startTime = entry.bucketStart; final long endTime = startTime + entry.bucketDuration; final float startX = mHoriz.convertToPoint(startTime); final float endX = mHoriz.convertToPoint(endTime); // skip until we find first stats on screen if (endX < 0) continue; // increment by current bucket total totalData += entry.rxBytes + entry.txBytes; final float startY = lastY; final float endY = mVert.convertToPoint(totalData); if (lastTime != startTime) { // gap in buckets; line to start of current bucket mPathStroke.lineTo(startX, startY); mPathFill.lineTo(startX, startY); } // always draw to end of current bucket mPathStroke.lineTo(endX, endY); mPathFill.lineTo(endX, endY); lastX = endX; lastY = endY; lastTime = endTime; } // when data falls short, extend to requested end time if (lastTime < mEndTime) { lastX = mHoriz.convertToPoint(mEndTime); mPathStroke.lineTo(lastX, lastY); mPathFill.lineTo(lastX, lastY); } if (LOGD) { final RectF bounds = new RectF(); mPathFill.computeBounds(bounds, true); Log.d( TAG, "onLayout() rendered with bounds=" + bounds.toString() + " and totalData=" + totalData); } // drop to bottom of graph from current location mPathFill.lineTo(lastX, height); mPathFill.lineTo(0, height); mMax = totalData; // build estimated data mPathEstimate.moveTo(lastX, lastY); final long now = System.currentTimeMillis(); final long bucketDuration = mStats.getBucketDuration(); // long window is average over two weeks entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry); final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration / entry.bucketDuration; long futureTime = 0; while (lastX < width) { futureTime += bucketDuration; // short window is day average last week final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS); entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry); final long shortWindow = (entry.rxBytes + entry.txBytes) * bucketDuration / entry.bucketDuration; totalData += (longWindow * 7 + shortWindow * 3) / 10; lastX = mHoriz.convertToPoint(lastTime + futureTime); lastY = mVert.convertToPoint(totalData); mPathEstimate.lineTo(lastX, lastY); } mMaxEstimate = totalData; invalidate(); }