diff options
author | 2018-01-05 13:15:49 -0800 | |
---|---|---|
committer | 2018-01-05 13:15:49 -0800 | |
commit | 20cf5036c1f373c1acfbb95295f118b7ff6c2227 (patch) | |
tree | b170bcce3339241f716cafff57f933345a7a3f34 | |
parent | cdd574e12df39e0921a4b2f3f34955f49b9a3d40 (diff) |
Add bugreport section progress reporter
- Allow dumpstatelisteners to monitor section size and duration and errors while the
bugreport is generated. Data will be used to write smoke tests for bugreport.
- Refactor main function to allow test to call dumpstate main function. Required until
bugreport api is completed
- Restore stdout and stderr fds before exiting dumpstate
Bug: 70154685
Test: mmm -j56 frameworks/native/cmds/dumpstate && \
adb sync data && \
adb shell /data/nativetest64/dumpstate_test/dumpstate_test && \
printf "\n\n#### ALL TESTS PASSED ####\n"
Change-Id: I7e0938baf6e055f14dce2348d0fe99f261870bf1
-rw-r--r-- | cmds/dumpstate/Android.bp | 2 | ||||
-rw-r--r-- | cmds/dumpstate/DumpstateSectionReporter.cpp | 42 | ||||
-rw-r--r-- | cmds/dumpstate/DumpstateSectionReporter.h | 65 | ||||
-rw-r--r-- | cmds/dumpstate/DumpstateService.cpp | 2 | ||||
-rw-r--r-- | cmds/dumpstate/DumpstateService.h | 1 | ||||
-rw-r--r-- | cmds/dumpstate/binder/android/os/IDumpstate.aidl | 5 | ||||
-rw-r--r-- | cmds/dumpstate/binder/android/os/IDumpstateListener.aidl | 12 | ||||
-rw-r--r-- | cmds/dumpstate/dumpstate.cpp | 11 | ||||
-rw-r--r-- | cmds/dumpstate/dumpstate.h | 6 | ||||
-rw-r--r-- | cmds/dumpstate/main.cpp | 21 | ||||
-rw-r--r-- | cmds/dumpstate/tests/dumpstate_test.cpp | 26 |
11 files changed, 184 insertions, 9 deletions
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp index ce3a6aad7a..35cff5f62e 100644 --- a/cmds/dumpstate/Android.bp +++ b/cmds/dumpstate/Android.bp @@ -83,7 +83,9 @@ cc_binary { ], srcs: [ "DumpstateInternal.cpp", + "DumpstateSectionReporter.cpp", "DumpstateService.cpp", + "main.cpp", "utils.cpp", "dumpstate.cpp", ], diff --git a/cmds/dumpstate/DumpstateSectionReporter.cpp b/cmds/dumpstate/DumpstateSectionReporter.cpp new file mode 100644 index 0000000000..f814bde26d --- /dev/null +++ b/cmds/dumpstate/DumpstateSectionReporter.cpp @@ -0,0 +1,42 @@ +/* + * 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 LOG_TAG "dumpstate" + +#include "DumpstateSectionReporter.h" + +namespace android { +namespace os { +namespace dumpstate { + +DumpstateSectionReporter::DumpstateSectionReporter(const std::string& title, + sp<android::os::IDumpstateListener> listener, + bool sendReport) + : title_(title), listener_(listener), sendReport_(sendReport), status_(OK), size_(-1) { + started_ = std::chrono::steady_clock::now(); +} + +DumpstateSectionReporter::~DumpstateSectionReporter() { + if ((listener_ != nullptr) && (sendReport_)) { + auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::steady_clock::now() - started_); + listener_->onSectionComplete(title_, status_, size_, (int32_t)elapsed.count()); + } +} + +} // namespace dumpstate +} // namespace os +} // namespace android diff --git a/cmds/dumpstate/DumpstateSectionReporter.h b/cmds/dumpstate/DumpstateSectionReporter.h new file mode 100644 index 0000000000..e971de84c5 --- /dev/null +++ b/cmds/dumpstate/DumpstateSectionReporter.h @@ -0,0 +1,65 @@ +/* + * 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 ANDROID_OS_DUMPSTATESECTIONREPORTER_H_ +#define ANDROID_OS_DUMPSTATESECTIONREPORTER_H_ + +#include <android/os/IDumpstateListener.h> +#include <utils/StrongPointer.h> + +namespace android { +namespace os { +namespace dumpstate { + + +/* + * Helper class used to report per section details to a listener. + * + * Typical usage: + * + * DumpstateSectionReporter sectionReporter(title, listener, sendReport); + * sectionReporter.setSize(5000); + * + */ +class DumpstateSectionReporter { + public: + DumpstateSectionReporter(const std::string& title, sp<android::os::IDumpstateListener> listener, + bool sendReport); + + ~DumpstateSectionReporter(); + + void setStatus(status_t status) { + status_ = status; + } + + void setSize(int size) { + size_ = size; + } + + private: + std::string title_; + android::sp<android::os::IDumpstateListener> listener_; + bool sendReport_; + status_t status_; + int size_; + std::chrono::time_point<std::chrono::steady_clock> started_; +}; + +} // namespace dumpstate +} // namespace os +} // namespace android + +#endif // ANDROID_OS_DUMPSTATESECTIONREPORTER_H_ diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp index efe0466d07..49a78e751b 100644 --- a/cmds/dumpstate/DumpstateService.cpp +++ b/cmds/dumpstate/DumpstateService.cpp @@ -52,6 +52,7 @@ status_t DumpstateService::Start() { binder::Status DumpstateService::setListener(const std::string& name, const sp<IDumpstateListener>& listener, + bool getSectionDetails, sp<IDumpstateToken>* returned_token) { *returned_token = nullptr; if (name.empty()) { @@ -70,6 +71,7 @@ binder::Status DumpstateService::setListener(const std::string& name, ds_.listener_name_ = name; ds_.listener_ = listener; + ds_.report_section_ = getSectionDetails; *returned_token = new DumpstateToken(); return binder::Status::ok(); diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h index 4352d3dacf..7bca24ae33 100644 --- a/cmds/dumpstate/DumpstateService.h +++ b/cmds/dumpstate/DumpstateService.h @@ -38,6 +38,7 @@ class DumpstateService : public BinderService<DumpstateService>, public BnDumpst status_t dump(int fd, const Vector<String16>& args) override; binder::Status setListener(const std::string& name, const sp<IDumpstateListener>& listener, + bool getSectionDetails, sp<IDumpstateToken>* returned_token) override; private: diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl index 4becccfc6d..9b11b960c5 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl @@ -30,6 +30,9 @@ interface IDumpstate { * * Returns a token used to monitor dumpstate death, or `nullptr` if the listener was already * set (the listener behaves like a Highlander: There Can be Only One). + * Set {@code getSectionDetails} to true in order to receive callbacks with per section + * progress details */ - IDumpstateToken setListener(@utf8InCpp String name, IDumpstateListener listener); + IDumpstateToken setListener(@utf8InCpp String name, IDumpstateListener listener, + boolean getSectionDetails); } diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl index 32717f4f87..030d69d16e 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl @@ -24,4 +24,16 @@ package android.os; interface IDumpstateListener { void onProgressUpdated(int progress); void onMaxProgressUpdated(int maxProgress); + + /** + * Called after every section is complete. + * @param name section name + * @param status values from status_t + * {@code OK} section completed successfully + * {@code TIMEOUT} dump timed out + * {@code != OK} error + * @param size size in bytes, may be invalid if status != OK + * @param durationMs duration in ms + */ + void onSectionComplete(@utf8InCpp String name, int status, int size, int durationMs); } diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 143192eba0..eb9079b0f1 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -1545,7 +1545,8 @@ static void Vibrate(int duration_ms) { // clang-format on } -int main(int argc, char *argv[]) { +/** Main entry point for dumpstate. */ +int run_main(int argc, char* argv[]) { int do_add_date = 0; int do_zip_file = 0; int do_vibrate = 1; @@ -1558,6 +1559,8 @@ int main(int argc, char *argv[]) { bool show_header_only = false; bool do_start_service = false; bool telephony_only = false; + int dup_stdout_fd; + int dup_stderr_fd; /* set as high priority, and protect from OOM killer */ setpriority(PRIO_PROCESS, 0, -20); @@ -1829,11 +1832,13 @@ int main(int argc, char *argv[]) { } if (is_redirecting) { + TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr))); redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str())); if (chown(ds.log_path_.c_str(), AID_SHELL, AID_SHELL)) { MYLOGE("Unable to change ownership of dumpstate log file %s: %s\n", ds.log_path_.c_str(), strerror(errno)); } + TEMP_FAILURE_RETRY(dup_stdout_fd = dup(fileno(stdout))); /* TODO: rather than generating a text file now and zipping it later, it would be more efficient to redirect stdout to the zip entry directly, but the libziparchive doesn't support that option yet. */ @@ -1907,7 +1912,7 @@ int main(int argc, char *argv[]) { /* close output if needed */ if (is_redirecting) { - fclose(stdout); + TEMP_FAILURE_RETRY(dup2(dup_stdout_fd, fileno(stdout))); } /* rename or zip the (now complete) .tmp file to its final location */ @@ -2038,7 +2043,7 @@ int main(int argc, char *argv[]) { MYLOGI("done (id %d)\n", ds.id_); if (is_redirecting) { - fclose(stderr); + TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr))); } if (use_control_socket && ds.control_socket_fd_ != -1) { diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 8db23a94f4..843c545e50 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -341,9 +341,10 @@ class Dumpstate { // Pointer to the zip structure. std::unique_ptr<ZipWriter> zip_writer_; - // Binder object listing to progress. + // Binder object listening to progress. android::sp<android::os::IDumpstateListener> listener_; std::string listener_name_; + bool report_section_; // Notification title and description std::string notification_title; @@ -433,6 +434,9 @@ void dump_emmc_ecsd(const char *ext_csd_path); /** Gets command-line arguments. */ void format_args(int argc, const char *argv[], std::string *args); +/** Main entry point for dumpstate. */ +int run_main(int argc, char* argv[]); + #ifdef __cplusplus } #endif diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp new file mode 100644 index 0000000000..78aad1137b --- /dev/null +++ b/cmds/dumpstate/main.cpp @@ -0,0 +1,21 @@ +/* + * 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 "dumpstate.h" + +int main(int argc, char* argv[]) { + return run_main(argc, argv); +} diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index a2e94538c2..838b385b1b 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -58,6 +58,8 @@ class DumpstateListenerMock : public IDumpstateListener { public: MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress)); MOCK_METHOD1(onMaxProgressUpdated, binder::Status(int32_t max_progress)); + MOCK_METHOD4(onSectionComplete, binder::Status(const ::std::string& name, int32_t status, + int32_t size, int32_t durationMs)); protected: MOCK_METHOD0(onAsBinder, IBinder*()); @@ -601,27 +603,43 @@ class DumpstateServiceTest : public DumpstateBaseTest { TEST_F(DumpstateServiceTest, SetListenerNoName) { sp<DumpstateListenerMock> listener(new DumpstateListenerMock()); sp<IDumpstateToken> token; - EXPECT_TRUE(dss.setListener("", listener, &token).isOk()); + EXPECT_TRUE(dss.setListener("", listener, /* getSectionDetails = */ false, &token).isOk()); ASSERT_THAT(token, IsNull()); } TEST_F(DumpstateServiceTest, SetListenerNoPointer) { sp<IDumpstateToken> token; - EXPECT_TRUE(dss.setListener("whatever", nullptr, &token).isOk()); + EXPECT_TRUE( + dss.setListener("whatever", nullptr, /* getSectionDetails = */ false, &token).isOk()); ASSERT_THAT(token, IsNull()); } TEST_F(DumpstateServiceTest, SetListenerTwice) { sp<DumpstateListenerMock> listener(new DumpstateListenerMock()); sp<IDumpstateToken> token; - EXPECT_TRUE(dss.setListener("whatever", listener, &token).isOk()); + EXPECT_TRUE( + dss.setListener("whatever", listener, /* getSectionDetails = */ false, &token).isOk()); ASSERT_THAT(token, NotNull()); EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever")); + EXPECT_FALSE(Dumpstate::GetInstance().report_section_); token.clear(); - EXPECT_TRUE(dss.setListener("whatsoever", listener, &token).isOk()); + EXPECT_TRUE( + dss.setListener("whatsoever", listener, /* getSectionDetails = */ false, &token).isOk()); ASSERT_THAT(token, IsNull()); EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever")); + EXPECT_FALSE(Dumpstate::GetInstance().report_section_); +} + +TEST_F(DumpstateServiceTest, SetListenerWithSectionDetails) { + sp<DumpstateListenerMock> listener(new DumpstateListenerMock()); + sp<IDumpstateToken> token; + Dumpstate::GetInstance().listener_ = nullptr; + EXPECT_TRUE( + dss.setListener("whatever", listener, /* getSectionDetails = */ true, &token).isOk()); + ASSERT_THAT(token, NotNull()); + EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever")); + EXPECT_TRUE(Dumpstate::GetInstance().report_section_); } class ProgressTest : public DumpstateBaseTest { |