Add a cmd line tool to StatsService to parse log files from Dropbox

Test: adb shell cmd stats all-logs
Change-Id: I7803c9c021a971619f60fbf6bdfabd33d2f476ef
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 0e6d292..4c097f0 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -50,6 +50,8 @@
     src/stats_log.proto \
     src/statsd_config.proto \
     src/stats_constants.proto \
+    src/DropboxReader.cpp \
+
 
 LOCAL_CFLAGS += \
     -Wall \
@@ -83,6 +85,7 @@
         libselinux \
         libutils \
         libservices \
+        libandroidfw \
 
 LOCAL_MODULE_CLASS := EXECUTABLES
 
diff --git a/cmds/statsd/src/DropboxReader.cpp b/cmds/statsd/src/DropboxReader.cpp
new file mode 100644
index 0000000..187f4ad
--- /dev/null
+++ b/cmds/statsd/src/DropboxReader.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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 <android/os/DropBoxManager.h>
+#include <android-base/file.h>
+#include <cutils/log.h>
+#include <androidfw/ZipUtils.h>
+#include <stdio.h>
+
+#include "DropboxReader.h"
+
+using android::sp;
+using android::String16;
+using android::binder::Status;
+using android::base::unique_fd;
+using android::os::DropBoxManager;
+using android::os::statsd::StatsLogEntry;
+using android::ZipUtils;
+using std::vector;
+
+status_t DropboxReader::readStatsLogs(FILE* out, const string& tag, long msec) {
+    sp<DropBoxManager> dropbox = new DropBoxManager();
+    StatsLogList logList;
+
+    long timestamp = msec;
+    // instead of while(true), put a hard limit 1000. Dropbox won't have more than 1000 files.
+    for(int i = 0; i < 1000; i++ ) {
+        DropBoxManager::Entry entry;
+        Status status = dropbox->getNextEntry(String16(tag.c_str()),
+                timestamp, &entry);
+        if (!status.isOk()) {
+            ALOGD("No more entries, or failed to read. We can't tell unfortunately.");
+            return android::OK;
+        }
+
+        const unique_fd& fd = entry.getFd();
+
+        // use this timestamp for next query.
+        timestamp = entry.getTimestamp();
+
+        if (entry.getFlags() & DropBoxManager::IS_GZIPPED) {
+            if (!parseFromGzipFile(fd, logList)) {
+                // Failed to parse from the file. Continue to fetch the next entry.
+                continue;
+            }
+        } else {
+            if (!parseFromFile(fd, logList)) {
+                // Failed to parse from the file. Continue to fetch the next entry.
+                continue;
+            }
+        }
+
+        printLog(out, logList);
+    }
+    return android::OK;
+}
+
+bool DropboxReader::parseFromGzipFile(const unique_fd& fd, StatsLogList& list) {
+    FILE *file = fdopen(fd, "r");
+    bool result = false;
+    bool scanResult;
+    int method;
+    long compressedLen;
+    long uncompressedLen;
+    unsigned long crc32;
+    scanResult = ZipUtils::examineGzip(file, &method, &uncompressedLen,
+            &compressedLen, &crc32);
+    if (scanResult && method == kCompressDeflated) {
+        vector<uint8_t> buf(uncompressedLen);
+        if (ZipUtils::inflateToBuffer(file, &buf[0], uncompressedLen, compressedLen)) {
+            if (list.ParseFromArray(&buf[0], uncompressedLen)) {
+                result = true;
+            }
+        }
+    } else {
+        ALOGE("This isn't a valid deflated gzip file");
+    }
+    fclose(file);
+    return result;
+}
+
+// parse a non zipped file.
+bool DropboxReader::parseFromFile(const unique_fd& fd, StatsLogList& list) {
+    string content;
+    if (!android::base::ReadFdToString(fd, &content)) {
+        ALOGE("Failed to read file");
+        return false;
+    }
+    if (!list.ParseFromString(content)) {
+        ALOGE("failed to parse log entry from data");
+        return false;
+    }
+    return true;
+}
+
+void DropboxReader::printLog(FILE* out, const StatsLogList& list) {
+    for (int i = 0; i < list.stats_log_entry_size(); i++) {
+        const StatsLogEntry entry = list.stats_log_entry(i);
+        // TODO: print pretty
+        fprintf(out, "time_msec=%lld, type=%d, aggregate_type=%d, uid=%d, pid=%d ",
+                entry.start_report_millis(), entry.type(), entry.aggregate_type(),
+                entry.uid(), entry.pid());
+        for (int j = 0; j < entry.pairs_size(); j++) {
+            fprintf(out, "msg=%s ", entry.pairs(j).value_str().c_str());
+        }
+        fprintf(out, "\n");
+    }
+}
diff --git a/cmds/statsd/src/DropboxReader.h b/cmds/statsd/src/DropboxReader.h
new file mode 100644
index 0000000..a62ffde
--- /dev/null
+++ b/cmds/statsd/src/DropboxReader.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef DROPBOX_READER_H
+#define DROPBOX_READER_H
+
+#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
+
+#include <stdint.h>
+#include <stdio.h>
+
+using android::base::unique_fd;
+using android::os::statsd::StatsLogList;
+using android::status_t;
+using std::string;
+
+class DropboxReader {
+public:
+    // msec is the start timestamp.
+    static status_t readStatsLogs(FILE* out, const string& tag, long msec);
+
+private:
+    static bool parseFromFile(const unique_fd& fd, StatsLogList& list);
+    static bool parseFromGzipFile(const unique_fd& fd, StatsLogList& list);
+    static void printLog(FILE* out, const StatsLogList& list);
+    enum {
+      kCompressStored = 0,    // no compression
+      kCompressDeflated = 8,  // standard deflate
+    };
+};
+
+#endif //DROPBOX_READER_H
\ No newline at end of file
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 5ee07b4..030c760 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "statsd"
 
 #include "StatsService.h"
+#include "DropboxReader.h"
 
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
@@ -27,6 +28,7 @@
 
 #include <unistd.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 using namespace android;
 
@@ -118,15 +120,13 @@
 status_t
 StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args)
 {
-    fprintf(out, "StatsService::command:");
-    ALOGD("StatsService::command:");
-    const int N = args.size();
-    for (int i=0; i<N; i++) {
-        fprintf(out, " %s", String8(args[i]).string());
-        ALOGD("   %s", String8(args[i]).string());
+    if (args.size() > 0) {
+        if (!args[0].compare(String8("print-stats-log")) && args.size() > 1) {
+            return doPrintStatsLog(out, args);
+        }
     }
-    fprintf(out, "\n");
 
+    printCmdHelp(out);
     return NO_ERROR;
 }
 
@@ -144,3 +144,18 @@
     return Status::ok();
 }
 
+status_t
+StatsService::doPrintStatsLog(FILE* out, const Vector<String8>& args) {
+    long msec = 0;
+
+    if (args.size() > 2) {
+        msec = strtol(args[2].string(), NULL, 10);
+    }
+    return DropboxReader::readStatsLogs(out, args[1].string(), msec);
+}
+
+void
+StatsService::printCmdHelp(FILE* out) {
+    fprintf(out, "Usage:\n");
+    fprintf(out, "\t print-stats-log [tag_required] [timestamp_nsec_optional]\n");
+}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 5dd9299..556b07b 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -45,6 +45,10 @@
     virtual status_t command(FILE* in, FILE* out, FILE* err, Vector<String8>& args);
 
     virtual Status systemRunning();
+
+private:
+    status_t doPrintStatsLog(FILE* out, const Vector<String8>& args);
+    void printCmdHelp(FILE* out);
 };
 
 #endif // STATS_SERVICE_H
diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h
index 8717178..2ed203d 100644
--- a/libs/services/include/android/os/DropBoxManager.h
+++ b/libs/services/include/android/os/DropBoxManager.h
@@ -57,7 +57,7 @@
     // and a handle will be passed to the system process, so no additional permissions
     // are required from the system process.  Returns NULL if the file can't be opened.
     Status addFile(const String16& tag, const string& filename, int flags);
-    
+
     class Entry : public virtual RefBase, public Parcelable {
     public:
         Entry();
@@ -65,7 +65,12 @@
 
         virtual status_t writeToParcel(Parcel* out) const;
         virtual status_t readFromParcel(const Parcel* in);
-        
+
+        const vector<uint8_t>& getData() const;
+        const unique_fd& getFd() const;
+        int32_t getFlags() const;
+        int64_t getTimestamp() const;
+
     private:
         Entry(const String16& tag, int32_t flags);
         Entry(const String16& tag, int32_t flags, int fd);
@@ -80,6 +85,9 @@
         friend class DropBoxManager;
     };
 
+    // Get the next entry from the drop box after the specified time.
+    Status getNextEntry(const String16& tag, long msec, Entry* entry);
+
 private:
     enum {
         HAS_BYTE_ARRAY = 8
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index bbb45f0..1c760e8 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -143,6 +143,29 @@
     return NO_ERROR;
 }
 
+const vector<uint8_t>&
+DropBoxManager::Entry::getData() const
+{
+    return mData;
+}
+
+const unique_fd&
+DropBoxManager::Entry::getFd() const
+{
+    return mFd;
+}
+
+int32_t
+DropBoxManager::Entry::getFlags() const
+{
+    return mFlags;
+}
+
+int64_t
+DropBoxManager::Entry::getTimestamp() const
+{
+    return mTimeMillis;
+}
 
 DropBoxManager::DropBoxManager()
 {
@@ -195,5 +218,16 @@
     return service->add(entry);
 }
 
+Status
+DropBoxManager::getNextEntry(const String16& tag, long msec, Entry* entry)
+{
+    sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
+        defaultServiceManager()->getService(android::String16("dropbox")));
+    if (service == NULL) {
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
+    }
+    return service->getNextEntry(tag, msec, entry);
+}
+
 }} // namespace android::os