Put metadata or stats into each dropbox incident report.

Bug: 65451198
Test: atest incidentd_test
Change-Id: Ib406b177ad7f1b4bda7fef2e606fc66a9836e060
This commit is contained in:
Yi Jin
2018-02-09 16:47:47 -08:00
parent 85a6db68f1
commit 329130b7e4
12 changed files with 208 additions and 31 deletions

View File

@@ -56,6 +56,7 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libincident \
liblog \
libprotobuf-cpp-lite \
libprotoutil \
libselinux \
libservices \

View File

@@ -73,11 +73,6 @@ PrivacySpec PrivacySpec::new_spec(int dest)
case android::os::DEST_LOCAL:
return PrivacySpec(dest);
default:
return PrivacySpec();
return PrivacySpec(android::os::DEST_AUTOMATIC);
}
}
PrivacySpec PrivacySpec::get_default_dropbox_spec()
{
return PrivacySpec(android::os::DEST_AUTOMATIC);
}

View File

@@ -75,7 +75,6 @@ public:
// Constructs spec using static methods below.
static PrivacySpec new_spec(int dest);
static PrivacySpec get_default_dropbox_spec();
private:
PrivacySpec(uint8_t dest) : dest(dest) {}
};

View File

@@ -18,6 +18,7 @@
#include "Reporter.h"
#include "Privacy.h"
#include "report_directory.h"
#include "section_list.h"
@@ -65,7 +66,9 @@ ReportRequestSet::ReportRequestSet()
:mRequests(),
mSections(),
mMainFd(-1),
mMainDest(-1)
mMainDest(-1),
mMetadata(),
mSectionStats()
{
}
@@ -79,18 +82,32 @@ ReportRequestSet::add(const sp<ReportRequest>& request)
{
mRequests.push_back(request);
mSections.merge(request->args);
mMetadata.set_request_size(mMetadata.request_size() + 1);
}
void
ReportRequestSet::setMainFd(int fd)
{
mMainFd = fd;
mMetadata.set_use_dropbox(fd > 0);
}
void
ReportRequestSet::setMainDest(int dest)
{
mMainDest = dest;
PrivacySpec spec = PrivacySpec::new_spec(dest);
switch (spec.dest) {
case android::os::DEST_AUTOMATIC:
mMetadata.set_dest(IncidentMetadata_Destination_AUTOMATIC);
break;
case android::os::DEST_EXPLICIT:
mMetadata.set_dest(IncidentMetadata_Destination_EXPLICIT);
break;
case android::os::DEST_LOCAL:
mMetadata.set_dest(IncidentMetadata_Destination_LOCAL);
break;
}
}
bool
@@ -98,6 +115,16 @@ ReportRequestSet::containsSection(int id) {
return mSections.containsSection(id);
}
IncidentMetadata::SectionStats*
ReportRequestSet::sectionStats(int id) {
if (mSectionStats.find(id) == mSectionStats.end()) {
auto stats = mMetadata.add_sections();
stats->set_id(id);
mSectionStats[id] = stats;
}
return mSectionStats[id];
}
// ================================================================================
Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; };
@@ -128,12 +155,12 @@ Reporter::~Reporter()
Reporter::run_report_status_t
Reporter::runReport()
{
status_t err = NO_ERROR;
bool needMainFd = false;
int mainFd = -1;
int mainDest = -1;
HeaderSection headers;
MetadataSection metadataSection;
// See if we need the main file
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
@@ -182,7 +209,7 @@ Reporter::runReport()
const int id = (*section)->id;
if (this->batch.containsSection(id)) {
ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string());
// Notify listener of starting
// Notify listener of starting.
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
(*it)->listener->onReportSectionStatus(id,
@@ -191,14 +218,20 @@ Reporter::runReport()
}
// Execute - go get the data and write it into the file descriptors.
auto stats = batch.sectionStats(id);
int64_t startTime = uptimeMillis();
err = (*section)->Execute(&batch);
int64_t endTime = uptimeMillis();
stats->set_success(err == NO_ERROR);
stats->set_exec_duration_ms(endTime - startTime);
if (err != NO_ERROR) {
ALOGW("Incident section %s (%d) failed: %s. Stopping report.",
(*section)->name.string(), id, strerror(-err));
goto DONE;
}
// Notify listener of starting
// Notify listener of ending.
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
(*it)->listener->onReportSectionStatus(id,
@@ -210,6 +243,9 @@ Reporter::runReport()
}
DONE:
// Reports the metdadata when taking the incident report.
if (!isTest) metadataSection.Execute(&batch);
// Close the file.
if (mainFd >= 0) {
close(mainFd);

View File

@@ -17,9 +17,12 @@
#ifndef REPORTER_H
#define REPORTER_H
#include "frameworks/base/libs/incident/proto/android/os/metadata.pb.h"
#include <android/os/IIncidentReportStatusListener.h>
#include <android/os/IncidentReportArgs.h>
#include <map>
#include <string>
#include <vector>
@@ -61,13 +64,20 @@ public:
iterator end() { return mRequests.end(); }
int mainFd() { return mMainFd; }
bool containsSection(int id);
int mainDest() { return mMainDest; }
IncidentMetadata& metadata() { return mMetadata; }
bool containsSection(int id);
IncidentMetadata::SectionStats* sectionStats(int id);
private:
vector<sp<ReportRequest>> mRequests;
IncidentReportArgs mSections;
int mMainFd;
int mMainDest;
IncidentMetadata mMetadata;
map<int, IncidentMetadata::SectionStats*> mSectionStats;
};
// ================================================================================
@@ -81,8 +91,8 @@ public:
ReportRequestSet batch;
Reporter();
Reporter(const char* directory);
Reporter(); // PROD must use this constructor.
Reporter(const char* directory); // For testing purpose only.
virtual ~Reporter();
// Run the report as described in the batch and args parameters.

View File

@@ -45,6 +45,7 @@ using namespace std;
// special section ids
const int FIELD_ID_INCIDENT_HEADER = 1;
const int FIELD_ID_INCIDENT_METADATA = 2;
// incident section parameters
const int WAIT_MAX = 5;
@@ -149,6 +150,12 @@ write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* re
EncodedBuffer::iterator data = buffer.data();
PrivacyBuffer privacyBuffer(get_privacy_of_section(id), data);
int writeable = 0;
auto stats = requests->sectionStats(id);
stats->set_dump_size_bytes(data.size());
stats->set_dump_duration_ms(buffer.durationMs());
stats->set_timed_out(buffer.timedOut());
stats->set_is_truncated(buffer.truncated());
// The streaming ones, group requests by spec in order to save unnecessary strip operations
map<PrivacySpec, vector<sp<ReportRequest>>> requestsBySpec;
@@ -182,9 +189,7 @@ write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* re
// The dropbox file
if (requests->mainFd() >= 0) {
PrivacySpec spec = requests->mainDest() < 0 ?
PrivacySpec::get_default_dropbox_spec() :
PrivacySpec::new_spec(requests->mainDest());
PrivacySpec spec = PrivacySpec::new_spec(requests->mainDest());
err = privacyBuffer.strip(spec);
if (err != NO_ERROR) return err; // the buffer data is corrupted.
if (privacyBuffer.size() == 0) goto DONE;
@@ -196,6 +201,7 @@ write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* re
writeable++;
ALOGD("Section %d flushed %zu bytes to dropbox %d with spec %d", id,
privacyBuffer.size(), requests->mainFd(), spec.dest);
stats->set_report_size_bytes(privacyBuffer.size());
}
DONE:
@@ -236,7 +242,7 @@ HeaderSection::Execute(ReportRequestSet* requests) const
// So the idea is only requests with negative fd are written to dropbox file.
int fd = request->fd >= 0 ? request->fd : requests->mainFd();
write_section_header(fd, FIELD_ID_INCIDENT_HEADER, buf->size());
write_section_header(fd, id, buf->size());
write_all(fd, (uint8_t const*)buf->data(), buf->size());
// If there was an error now, there will be an error later and we will remove
// it from the list then.
@@ -244,7 +250,35 @@ HeaderSection::Execute(ReportRequestSet* requests) const
}
return NO_ERROR;
}
// ================================================================================
MetadataSection::MetadataSection()
:Section(FIELD_ID_INCIDENT_METADATA, 0)
{
}
MetadataSection::~MetadataSection()
{
}
status_t
MetadataSection::Execute(ReportRequestSet* requests) const
{
std::string metadataBuf;
requests->metadata().SerializeToString(&metadataBuf);
for (ReportRequestSet::iterator it=requests->begin(); it!=requests->end(); it++) {
const sp<ReportRequest> request = *it;
if (metadataBuf.empty() || request->fd < 0 || request->err != NO_ERROR) {
continue;
}
write_section_header(request->fd, id, metadataBuf.size());
write_all(request->fd, (uint8_t const*)metadataBuf.data(), metadataBuf.size());
}
if (requests->mainFd() >= 0 && !metadataBuf.empty()) {
write_section_header(requests->mainFd(), id, metadataBuf.size());
write_all(requests->mainFd(), (uint8_t const*)metadataBuf.data(), metadataBuf.size());
}
return NO_ERROR;
}
// ================================================================================
FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
:Section(id, timeoutMs),

View File

@@ -58,6 +58,18 @@ public:
virtual status_t Execute(ReportRequestSet* requests) const;
};
/**
* Section that generates incident metadata.
*/
class MetadataSection : public Section
{
public:
MetadataSection();
virtual ~MetadataSection();
virtual status_t Execute(ReportRequestSet* requests) const;
};
/**
* Section that reads in a file.
*/

View File

@@ -180,3 +180,18 @@ TEST_F(ReporterTest, RunReportToGivenDirectory) {
ASSERT_EQ((int)results.size(), 1);
EXPECT_EQ(results[0], "\n\x2" "\b\f\n\x6" "\x12\x4" "abcd");
}
TEST_F(ReporterTest, ReportMetadata) {
IncidentReportArgs args;
args.addSection(1);
args.setDest(android::os::DEST_EXPLICIT);
sp<ReportRequest> r = new ReportRequest(args, l, -1);
reporter->batch.add(r);
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
auto metadata = reporter->batch.metadata();
EXPECT_EQ(IncidentMetadata_Destination_EXPLICIT, metadata.dest());
EXPECT_EQ(1, metadata.request_size());
EXPECT_TRUE(metadata.use_dropbox());
EXPECT_EQ(0, metadata.sections_size());
}

View File

@@ -18,6 +18,7 @@
#include <android-base/file.h>
#include <android-base/test_utils.h>
#include <android/os/IncidentReportArgs.h>
#include <frameworks/base/libs/incident/proto/android/os/header.pb.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -89,6 +90,18 @@ TEST(SectionTest, HeaderSection) {
EXPECT_THAT(content, StrEq("\n\x05\x12\x03pup"));
}
TEST(SectionTest, MetadataSection) {
MetadataSection ms;
ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
requests.sectionStats(1)->set_success(true);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ms.Execute(&requests));
EXPECT_THAT(GetCapturedStdout(), StrEq("\x12\b\x18\x1\"\x4\b\x1\x10\x1"));
}
TEST(SectionTest, FileSection) {
TemporaryFile tf;
FileSection fs(REVERSE_PARSER, tf.path);
@@ -243,8 +256,9 @@ TEST(SectionTest, TestMultipleRequests) {
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
args1.setDest(0); // LOCAL
args2.setAll(true); // default to explicit
args1.setDest(android::os::DEST_LOCAL);
args2.setAll(true);
args2.setDest(android::os::DEST_EXPLICIT);
sp<SimpleListener> l = new SimpleListener();
requests.add(new ReportRequest(args1, l, output1.fd));
requests.add(new ReportRequest(args2, l, output2.fd));
@@ -283,10 +297,12 @@ TEST(SectionTest, TestMultipleRequestsBySpec) {
ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
IncidentReportArgs args1, args2, args3, args4;
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
args1.setDest(android::os::DEST_EXPLICIT);
args2.setAll(true);
args4.setAll(true);
args2.setDest(android::os::DEST_EXPLICIT);
args3.setAll(true);
sp<SimpleListener> l = new SimpleListener();
requests.add(new ReportRequest(args1, l, output1.fd));
requests.add(new ReportRequest(args2, l, output2.fd));
@@ -307,7 +323,8 @@ TEST(SectionTest, TestMultipleRequestsBySpec) {
EXPECT_TRUE(ReadFileToString(output2.path, &content));
EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
// because args3 doesn't set section, so it should receive nothing
// output3 has only auto field
c = (char) STRING_FIELD_2.size();
EXPECT_TRUE(ReadFileToString(output3.path, &content));
EXPECT_THAT(content, StrEq(""));
EXPECT_THAT(content, StrEq(string("\x02") + c + STRING_FIELD_2));
}

View File

@@ -46,18 +46,12 @@ import "frameworks/base/core/proto/android/service/usb.proto";
import "frameworks/base/core/proto/android/util/event_log_tags.proto";
import "frameworks/base/core/proto/android/util/log.proto";
import "frameworks/base/libs/incident/proto/android/os/header.proto";
import "frameworks/base/libs/incident/proto/android/os/metadata.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/libs/incident/proto/android/section.proto";
package android.os;
// This field contains internal metadata associated with an incident report,
// such as the section ids and privacy policy specs from caller as well as how long
// and how many bytes a section takes, etc.
message IncidentMetadata {
}
// privacy field options must not be set at this level because all
// the sections are able to be controlled and configured by section ids.
// Instead privacy field options need to be configured in each section proto message.

View File

@@ -33,6 +33,7 @@ LOCAL_SRC_FILES := \
../../core/java/android/os/IIncidentManager.aidl \
../../core/java/android/os/IIncidentReportStatusListener.aidl \
proto/android/os/header.proto \
proto/android/os/metadata.proto \
src/IncidentReportArgs.cpp
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include

View File

@@ -0,0 +1,63 @@
/*
* 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.
*/
syntax = "proto2";
option java_multiple_files = true;
package android.os;
// This field contains internal metadata associated with an incident report,
// such as the section ids and privacy policy specs from caller as well as how long
// and how many bytes a section takes, etc.
message IncidentMetadata {
// privacy level of the incident report.
enum Destination {
AUTOMATIC = 0;
EXPLICIT = 1;
LOCAL = 2;
}
optional Destination dest = 1;
optional int32 request_size = 2;
optional bool use_dropbox = 3;
// stats of each section taken in this incident report.
message SectionStats {
// section id.
optional int32 id = 1;
// if the section is successfully taken.
optional bool success = 2;
// number of bytes in the report after filtering.
optional int32 report_size_bytes = 3;
// the total duration to execute the section.
optional int64 exec_duration_ms = 4;
// number of bytes dumped from the section directly.
optional int32 dump_size_bytes = 5;
// duration of the dump takes.
optional int64 dump_duration_ms = 6;
// true if the section timed out.
optional bool timed_out = 7;
// true if the section is truncated.
optional bool is_truncated = 8;
// Next Tag: 9
}
repeated SectionStats sections = 4;
}