From 6de357e4d10fa5977ab9a6c665dc858765e95d34 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 9 May 2012 13:33:52 -0700 Subject: Recover from Throwable in FileRotator, dump. In rewriteSingle(), catch Throwable to rollback to backup file, instead of just IOException. Also add dumpAll() to pack up contents for later debugging, and use it when encountering bad stats. Bug: 6467868 Change-Id: Ic8e287cf5a235706811a304a88d71d11d3a79cd4 --- .../com/android/internal/util/FileRotator.java | 63 +++++++++++++++++----- .../com/android/internal/util/FileRotatorTest.java | 4 +- .../android/server/net/NetworkStatsRecorder.java | 37 ++++++++++++- .../android/server/net/NetworkStatsService.java | 4 +- 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java index 6cfb97dd2a5a..26235f13deab 100644 --- a/core/java/com/android/internal/util/FileRotator.java +++ b/core/java/com/android/internal/util/FileRotator.java @@ -19,8 +19,6 @@ package com.android.internal.util; import android.os.FileUtils; import android.util.Slog; -import com.android.internal.util.FileRotator.Rewriter; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; @@ -29,8 +27,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import libcore.io.IoUtils; +import libcore.io.Streams; /** * Utility that rotates files over time, similar to {@code logrotate}. There is @@ -137,10 +138,38 @@ public class FileRotator { public void deleteAll() { final FileInfo info = new FileInfo(mPrefix); for (String name : mBasePath.list()) { - if (!info.parse(name)) continue; + if (info.parse(name)) { + // delete each file that matches parser + new File(mBasePath, name).delete(); + } + } + } + + /** + * Dump all files managed by this rotator for debugging purposes. + */ + public void dumpAll(OutputStream os) throws IOException { + final ZipOutputStream zos = new ZipOutputStream(os); + try { + final FileInfo info = new FileInfo(mPrefix); + for (String name : mBasePath.list()) { + if (info.parse(name)) { + final ZipEntry entry = new ZipEntry(name); + zos.putNextEntry(entry); - // delete each file that matches parser - new File(mBasePath, name).delete(); + final File file = new File(mBasePath, name); + final FileInputStream is = new FileInputStream(file); + try { + Streams.copy(is, zos); + } finally { + IoUtils.closeQuietly(is); + } + + zos.closeEntry(); + } + } + } finally { + IoUtils.closeQuietly(zos); } } @@ -159,22 +188,22 @@ public class FileRotator { public void combineActive(final Reader reader, final Writer writer, long currentTimeMillis) throws IOException { rewriteActive(new Rewriter() { - /** {@inheritDoc} */ + @Override public void reset() { // ignored } - /** {@inheritDoc} */ + @Override public void read(InputStream in) throws IOException { reader.read(in); } - /** {@inheritDoc} */ + @Override public boolean shouldWrite() { return true; } - /** {@inheritDoc} */ + @Override public void write(OutputStream out) throws IOException { writer.write(out); } @@ -224,11 +253,11 @@ public class FileRotator { // write success, delete backup backupFile.delete(); - } catch (IOException e) { + } catch (Throwable t) { // write failed, delete file and restore backup file.delete(); backupFile.renameTo(file); - throw e; + throw rethrowAsIoException(t); } } else { @@ -241,11 +270,11 @@ public class FileRotator { // write success, delete empty backup backupFile.delete(); - } catch (IOException e) { + } catch (Throwable t) { // write failed, delete file and empty backup file.delete(); backupFile.delete(); - throw e; + throw rethrowAsIoException(t); } } } @@ -353,6 +382,14 @@ public class FileRotator { } } + private static IOException rethrowAsIoException(Throwable t) throws IOException { + if (t instanceof IOException) { + throw (IOException) t; + } else { + throw new IOException(t.getMessage(), t); + } + } + /** * Details for a rotated file, either parsed from an existing filename, or * ready to be built into a new filename. diff --git a/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java b/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java index 94d1cb6b5b89..95f0e67c0f7a 100644 --- a/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java +++ b/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java @@ -187,12 +187,12 @@ public class FileRotatorTest extends AndroidTestCase { rotate.combineActive(reader, new Writer() { public void write(OutputStream out) throws IOException { new DataOutputStream(out).writeUTF("bar"); - throw new ProtocolException("yikes"); + throw new NullPointerException("yikes"); } }, currentTime); fail("woah, somehow able to write exception"); - } catch (ProtocolException e) { + } catch (IOException e) { // expected from above } diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java index 2ce777113556..c3ecf5495132 100644 --- a/services/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -26,6 +26,7 @@ import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; +import android.os.DropBoxManager; import android.util.Log; import android.util.MathUtils; import android.util.Slog; @@ -34,6 +35,7 @@ import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; import com.google.android.collect.Sets; +import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; @@ -43,6 +45,8 @@ import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Map; +import libcore.io.IoUtils; + /** * Logic to record deltas between periodic {@link NetworkStats} snapshots into * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. @@ -56,8 +60,14 @@ public class NetworkStatsRecorder { private static final boolean LOGD = false; private static final boolean LOGV = false; + private static final String TAG_NETSTATS_DUMP = "netstats_dump"; + + /** Dump before deleting in {@link #recoverFromWtf()}. */ + private static final boolean DUMP_BEFORE_DELETE = true; + private final FileRotator mRotator; private final NonMonotonicObserver mObserver; + private final DropBoxManager mDropBox; private final String mCookie; private final long mBucketDuration; @@ -74,9 +84,10 @@ public class NetworkStatsRecorder { private WeakReference mComplete; public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver observer, - String cookie, long bucketDuration, boolean onlyTags) { + DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) { mRotator = checkNotNull(rotator, "missing FileRotator"); mObserver = checkNotNull(observer, "missing NonMonotonicObserver"); + mDropBox = checkNotNull(dropBox, "missing DropBoxManager"); mCookie = cookie; mBucketDuration = bucketDuration; @@ -122,6 +133,7 @@ public class NetworkStatsRecorder { mComplete = new WeakReference(complete); } catch (IOException e) { Log.wtf(TAG, "problem completely reading network stats", e); + recoverFromWtf(); } } return complete; @@ -212,6 +224,7 @@ public class NetworkStatsRecorder { mPending.reset(); } catch (IOException e) { Log.wtf(TAG, "problem persisting pending stats", e); + recoverFromWtf(); } } } @@ -226,6 +239,7 @@ public class NetworkStatsRecorder { mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid)); } catch (IOException e) { Log.wtf(TAG, "problem removing UID " + uid, e); + recoverFromWtf(); } // clear UID from current stats snapshot @@ -355,4 +369,25 @@ public class NetworkStatsRecorder { mSinceBoot.dump(pw); } } + + /** + * Recover from {@link FileRotator} failure by dumping state to + * {@link DropBoxManager} and deleting contents. + */ + private void recoverFromWtf() { + if (DUMP_BEFORE_DELETE) { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + mRotator.dumpAll(os); + } catch (IOException e) { + // ignore partial contents + os.reset(); + } finally { + IoUtils.closeQuietly(os); + } + mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0); + } + + mRotator.deleteAll(); + } } diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 1a56b80d7e7d..e710b3389ed1 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -338,9 +338,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private NetworkStatsRecorder buildRecorder( String prefix, NetworkStatsSettings.Config config, boolean includeTags) { + final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService( + Context.DROPBOX_SERVICE); return new NetworkStatsRecorder(new FileRotator( mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis), - mNonMonotonicObserver, prefix, config.bucketDuration, includeTags); + mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags); } private void shutdownLocked() { -- cgit v1.2.3-59-g8ed1b