From d4a77e8a1a9bcb778c01738b0dce640ff7d4e8a5 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Wed, 6 Sep 2017 19:40:24 -0700 Subject: lshal: remove ListCommand addLine Remove obnoxious addLine(...) in ListCommand.cpp by moving the feature of selecting columns into the "Table" class. Test: lshal Test: lshal -m Test: lshal -d (shows debug info for context hub) Test: lshal_test Bug: 35389839 Change-Id: Ieb4a6e544ef39c9f1a63b046a44b6a8e1416ea62 --- cmds/lshal/TableEntry.cpp | 156 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 cmds/lshal/TableEntry.cpp (limited to 'cmds/lshal/TableEntry.cpp') diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp new file mode 100644 index 0000000000..dc6ccaeda5 --- /dev/null +++ b/cmds/lshal/TableEntry.cpp @@ -0,0 +1,156 @@ +/* + * 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. + */ +#define LOG_TAG "lshal" +#include + +#include "TableEntry.h" + +#include "TextTable.h" +#include "utils.h" + +namespace android { +namespace lshal { + +static const std::string &getArchString(Architecture arch) { + static const std::string sStr64 = "64"; + static const std::string sStr32 = "32"; + static const std::string sStrBoth = "32+64"; + static const std::string sStrUnknown = ""; + switch (arch) { + case ARCH64: + return sStr64; + case ARCH32: + return sStr32; + case ARCH_BOTH: + return sStrBoth; + case ARCH_UNKNOWN: // fall through + default: + return sStrUnknown; + } +} + +static std::string getTitle(TableColumnType type) { + switch (type) { + case TableColumnType::INTERFACE_NAME: { + return "Interface"; + } break; + case TableColumnType::TRANSPORT: { + return "Transport"; + } break; + case TableColumnType::SERVER_PID: { + return "Server"; + } break; + case TableColumnType::SERVER_CMD: { + return "Server CMD"; + } + case TableColumnType::SERVER_ADDR: { + return "PTR"; + } break; + case TableColumnType::CLIENT_PIDS: { + return "Clients"; + } break; + case TableColumnType::CLIENT_CMDS: { + return "Clients CMD"; + } break; + case TableColumnType::ARCH: { + return "Arch"; + } break; + case TableColumnType::THREADS: { + return "Thread Use"; + } break; + default: { + LOG(FATAL) << "Should not reach here."; + return ""; + } + } +} + +std::string TableEntry::getField(TableColumnType type) const { + switch (type) { + case TableColumnType::INTERFACE_NAME: { + return interfaceName; + } break; + case TableColumnType::TRANSPORT: { + return transport; + } break; + case TableColumnType::SERVER_PID: { + return serverPid == NO_PID ? "N/A" : std::to_string(serverPid); + } break; + case TableColumnType::SERVER_CMD: { + return serverCmdline; + } break; + case TableColumnType::SERVER_ADDR: { + return serverObjectAddress == NO_PTR ? "N/A" : toHexString(serverObjectAddress); + } break; + case TableColumnType::CLIENT_PIDS: { + return join(clientPids, " "); + } break; + case TableColumnType::CLIENT_CMDS: { + return join(clientCmdlines, ";"); + } break; + case TableColumnType::ARCH: { + return getArchString(arch); + } break; + case TableColumnType::THREADS: { + return getThreadUsage(); + } break; + default: { + LOG(FATAL) << "Should not reach here."; + return ""; + } + } +} + +TextTable Table::createTextTable(bool neat, + const std::function& emitDebugInfo) const { + + TextTable textTable; + std::vector row; + if (!neat) { + textTable.add(mDescription); + + row.clear(); + for (TableColumnType type : mSelectedColumns) { + row.push_back(getTitle(type)); + } + textTable.add(std::move(row)); + } + + for (const auto& entry : mEntries) { + row.clear(); + for (TableColumnType type : mSelectedColumns) { + row.push_back(entry.getField(type)); + } + textTable.add(std::move(row)); + + if (emitDebugInfo) { + std::string debugInfo = emitDebugInfo(entry.interfaceName); + if (!debugInfo.empty()) textTable.add(debugInfo); + } + } + return textTable; +} + +TextTable MergedTable::createTextTable() { + TextTable textTable; + for (const Table* table : mTables) { + textTable.addAll(table->createTextTable()); + } + return textTable; +} + +} // namespace lshal +} // namespace android -- cgit v1.2.3-59-g8ed1b From 8bf7316ebc75f9e5b3a6349bbc9c7140e6e90234 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Thu, 7 Sep 2017 18:06:13 -0700 Subject: lshal: add tests for ListCommand::fetch* and dumpVintf Test: lshal_test Change-Id: I9e519ec93709ba4dfa7f95e4c5fff60cbda36134 --- cmds/lshal/Android.bp | 1 + cmds/lshal/ListCommand.cpp | 4 +- cmds/lshal/ListCommand.h | 16 ++- cmds/lshal/TableEntry.cpp | 22 ++++ cmds/lshal/TableEntry.h | 4 + cmds/lshal/test.cpp | 297 ++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 335 insertions(+), 9 deletions(-) (limited to 'cmds/lshal/TableEntry.cpp') diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index fab7bfd0e8..47814688a9 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -61,6 +61,7 @@ cc_test { "libgmock" ], shared_libs: [ + "libvintf", "android.hardware.tests.baz@1.0" ], srcs: [ diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp index 99048afe1c..4550e410a0 100644 --- a/cmds/lshal/ListCommand.cpp +++ b/cmds/lshal/ListCommand.cpp @@ -55,7 +55,7 @@ NullableOStream ListCommand::err() const { return mLshal.err(); } -std::string getCmdline(pid_t pid) { +std::string ListCommand::parseCmdline(pid_t pid) const { std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline"); std::string cmdline; if (!ifs.is_open()) { @@ -70,7 +70,7 @@ const std::string &ListCommand::getCmdline(pid_t pid) { if (pair != mCmdlines.end()) { return pair->second; } - mCmdlines[pid] = ::android::lshal::getCmdline(pid); + mCmdlines[pid] = parseCmdline(pid); return mCmdlines[pid]; } diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h index 64d33ca0fe..9833d43de5 100644 --- a/cmds/lshal/ListCommand.h +++ b/cmds/lshal/ListCommand.h @@ -36,9 +36,16 @@ namespace lshal { class Lshal; +struct PidInfo { + std::map refPids; // pids that are referenced + uint32_t threadUsage; // number of threads in use + uint32_t threadCount; // number of threads total +}; + class ListCommand { public: ListCommand(Lshal &lshal); + virtual ~ListCommand() = default; Status main(const std::string &command, const Arg &arg); protected: Status parseArgs(const std::string &command, const Arg &arg); @@ -50,12 +57,7 @@ protected: Status fetchBinderized(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); Status fetchAllLibraries(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); - struct PidInfo { - std::map refPids; // pids that are referenced - uint32_t threadUsage; // number of threads in use - uint32_t threadCount; // number of threads total - }; - bool getPidInfo(pid_t serverPid, PidInfo *info) const; + virtual bool getPidInfo(pid_t serverPid, PidInfo *info) const; void dumpTable(const NullableOStream& out) const; void dumpVintf(const NullableOStream& out) const; @@ -64,6 +66,8 @@ protected: const std::string &serverCmdline, const std::string &address, const std::string &clients, const std::string &clientCmdlines) const; void addLine(TextTable *table, const TableEntry &entry); + // Read and return /proc/{pid}/cmdline. + virtual std::string parseCmdline(pid_t pid) const; // Return /proc/{pid}/cmdline if it exists, else empty string. const std::string &getCmdline(pid_t pid); // Call getCmdline on all pid in pids. If it returns empty string, the process might diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp index dc6ccaeda5..eac0f2144a 100644 --- a/cmds/lshal/TableEntry.cpp +++ b/cmds/lshal/TableEntry.cpp @@ -152,5 +152,27 @@ TextTable MergedTable::createTextTable() { return textTable; } +bool TableEntry::operator==(const TableEntry& other) const { + if (this == &other) { + return true; + } + return interfaceName == other.interfaceName && transport == other.transport && + serverPid == other.serverPid && threadUsage == other.threadUsage && + threadCount == other.threadCount && serverCmdline == other.serverCmdline && + serverObjectAddress == other.serverObjectAddress && clientPids == other.clientPids && + clientCmdlines == other.clientCmdlines && arch == other.arch; +} + +std::string TableEntry::to_string() const { + std::stringstream ss; + ss << "name=" << interfaceName << ";transport=" << transport << ";thread=" << getThreadUsage() + << ";server=" << serverPid + << "(" << serverObjectAddress << ";" << serverCmdline << ");clients=[" + << join(clientPids, ";") << "](" << join(clientCmdlines, ";") << ");arch=" + << getArchString(arch); + return ss.str(); + +} + } // namespace lshal } // namespace android diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h index dac8b5e3b7..7a3b22ea67 100644 --- a/cmds/lshal/TableEntry.h +++ b/cmds/lshal/TableEntry.h @@ -85,6 +85,9 @@ struct TableEntry { } std::string getField(TableColumnType type) const; + + bool operator==(const TableEntry& other) const; + std::string to_string() const; }; using SelectedColumns = std::vector; @@ -97,6 +100,7 @@ public: Entries::const_iterator begin() const { return mEntries.begin(); } Entries::iterator end() { return mEntries.end(); } Entries::const_iterator end() const { return mEntries.end(); } + size_t size() const { return mEntries.size(); } void add(TableEntry&& entry) { mEntries.push_back(std::move(entry)); } diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp index 6cdec29ad3..44b196e11f 100644 --- a/cmds/lshal/test.cpp +++ b/cmds/lshal/test.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "ListCommand.h" #include "Lshal.h" @@ -34,6 +35,7 @@ using namespace testing; +using ::android::hidl::base::V1_0::DebugInfo; using ::android::hidl::base::V1_0::IBase; using ::android::hidl::manager::V1_0::IServiceManager; using ::android::hidl::manager::V1_0::IServiceNotification; @@ -42,6 +44,8 @@ using ::android::hardware::hidl_handle; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; +using InstanceDebugInfo = IServiceManager::InstanceDebugInfo; + namespace android { namespace hardware { namespace tests { @@ -77,7 +81,6 @@ struct Quux : android::hardware::tests::baz::V1_0::IQuux { namespace lshal { - class MockServiceManager : public IServiceManager { public: template @@ -181,9 +184,17 @@ public: MockListCommand(Lshal* lshal) : ListCommand(*lshal) {} Status parseArgs(const Arg& arg) { return ListCommand::parseArgs("", arg); } + Status main(const Arg& arg) { return ListCommand::main("", arg); } void forEachTable(const std::function &f) const { return ListCommand::forEachTable(f); } + Status fetch() { return ListCommand::fetch(); } + void dumpVintf(const NullableOStream& out) { + return ListCommand::dumpVintf(out); + } + + MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, PidInfo*)); + MOCK_CONST_METHOD1(parseCmdline, std::string(pid_t)); }; class ListParseArgsTest : public ::testing::Test { @@ -233,6 +244,290 @@ TEST_F(ListParseArgsTest, DebugAndNeat) { EXPECT_THAT(output.str(), StrNe("")); } +/// Fetch Test + +// A set of deterministic functions to generate fake debug infos. +static uint64_t getPtr(pid_t serverId) { return 10000 + serverId; } +static std::vector getClients(pid_t serverId) { + return {serverId + 1, serverId + 3}; +} +static PidInfo getPidInfoFromId(pid_t serverId) { + PidInfo info; + info.refPids[getPtr(serverId)] = getClients(serverId); + info.threadUsage = 10 + serverId; + info.threadCount = 20 + serverId; + return info; +} +static std::string getInterfaceName(pid_t serverId) { + return "a.h.foo" + std::to_string(serverId) + "@" + std::to_string(serverId) + ".0::IFoo"; +} +static std::string getInstanceName(pid_t serverId) { + return std::to_string(serverId); +} +static pid_t getIdFromInstanceName(const hidl_string& instance) { + return atoi(instance.c_str()); +} +static std::string getFqInstanceName(pid_t serverId) { + return getInterfaceName(serverId) + "/" + getInstanceName(serverId); +} +static std::string getCmdlineFromId(pid_t serverId) { + if (serverId == NO_PID) return ""; + return "command_line_" + std::to_string(serverId); +} + +// Fake service returned by mocked IServiceManager::get. +class TestService : public IBase { +public: + TestService(DebugInfo&& info) : mInfo(std::move(info)) {} + hardware::Return getDebugInfo(getDebugInfo_cb cb) override { + cb(mInfo); + return hardware::Void(); + } +private: + DebugInfo mInfo; +}; + +class ListTest : public ::testing::Test { +public: + void SetUp() override { + initMockServiceManager(); + lshal = std::make_unique(out, err, serviceManager, passthruManager); + initMockList(); + } + + void initMockList() { + mockList = std::make_unique>(lshal.get()); + ON_CALL(*mockList, getPidInfo(_,_)).WillByDefault(Invoke( + [](pid_t serverPid, PidInfo* info) { + *info = getPidInfoFromId(serverPid); + return true; + })); + ON_CALL(*mockList, parseCmdline(_)).WillByDefault(Invoke(&getCmdlineFromId)); + } + + void initMockServiceManager() { + serviceManager = new testing::NiceMock(); + passthruManager = new testing::NiceMock(); + using A = DebugInfo::Architecture; + ON_CALL(*serviceManager, list(_)).WillByDefault(Invoke( + [] (IServiceManager::list_cb cb) { + cb({ getFqInstanceName(1), getFqInstanceName(2) }); + return hardware::Void(); + })); + + ON_CALL(*serviceManager, get(_, _)).WillByDefault(Invoke( + [&](const hidl_string&, const hidl_string& instance) { + int id = getIdFromInstanceName(instance); + return sp(new TestService({ id /* pid */, getPtr(id), A::IS_64BIT })); + })); + + ON_CALL(*serviceManager, debugDump(_)).WillByDefault(Invoke( + [] (IServiceManager::debugDump_cb cb) { + cb({InstanceDebugInfo{getInterfaceName(3), getInstanceName(3), 3, + getClients(3), A::IS_32BIT}, + InstanceDebugInfo{getInterfaceName(4), getInstanceName(4), 4, + getClients(4), A::IS_32BIT}}); + return hardware::Void(); + })); + + ON_CALL(*passthruManager, debugDump(_)).WillByDefault(Invoke( + [] (IServiceManager::debugDump_cb cb) { + cb({InstanceDebugInfo{getInterfaceName(5), getInstanceName(5), 5, + getClients(5), A::IS_32BIT}, + InstanceDebugInfo{getInterfaceName(6), getInstanceName(6), 6, + getClients(6), A::IS_32BIT}}); + return hardware::Void(); + })); + } + + std::stringstream err; + std::stringstream out; + std::unique_ptr lshal; + std::unique_ptr mockList; + sp serviceManager; + sp passthruManager; +}; + +TEST_F(ListTest, Fetch) { + EXPECT_EQ(0u, mockList->fetch()); + std::array transports{{"hwbinder", "hwbinder", "passthrough", + "passthrough", "passthrough", "passthrough"}}; + std::array archs{{ARCH64, ARCH64, ARCH32, ARCH32, ARCH32, ARCH32}}; + int id = 1; + mockList->forEachTable([&](const Table& table) { + ASSERT_EQ(2u, table.size()); + for (const auto& entry : table) { + const auto& transport = transports[id - 1]; + TableEntry expected{ + .interfaceName = getFqInstanceName(id), + .transport = transport, + .serverPid = transport == "hwbinder" ? id : NO_PID, + .threadUsage = transport == "hwbinder" ? getPidInfoFromId(id).threadUsage : 0, + .threadCount = transport == "hwbinder" ? getPidInfoFromId(id).threadCount : 0, + .serverCmdline = {}, + .serverObjectAddress = transport == "hwbinder" ? getPtr(id) : NO_PTR, + .clientPids = getClients(id), + .clientCmdlines = {}, + .arch = archs[id - 1], + }; + EXPECT_EQ(expected, entry) << expected.to_string() << " vs. " << entry.to_string(); + + ++id; + } + }); + +} + +TEST_F(ListTest, DumpVintf) { + const std::string expected = + "\n" + "\n" + " \n" + " a.h.foo1\n" + " hwbinder\n" + " 1.0\n" + " \n" + " IFoo\n" + " 1\n" + " \n" + " \n" + " \n" + " a.h.foo2\n" + " hwbinder\n" + " 2.0\n" + " \n" + " IFoo\n" + " 2\n" + " \n" + " \n" + " \n" + " a.h.foo3\n" + " passthrough\n" + " 3.0\n" + " \n" + " IFoo\n" + " 3\n" + " \n" + " \n" + " \n" + " a.h.foo4\n" + " passthrough\n" + " 4.0\n" + " \n" + " IFoo\n" + " 4\n" + " \n" + " \n" + " \n" + " a.h.foo5\n" + " passthrough\n" + " 5.0\n" + " \n" + " \n" + " a.h.foo6\n" + " passthrough\n" + " 6.0\n" + " \n" + " \n" + " 0.0\n" + " \n" + "\n"; + + optind = 1; // mimic Lshal::parseArg() + EXPECT_EQ(0u, mockList->main(createArg({"lshal", "--init-vintf"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); + + vintf::HalManifest m; + EXPECT_EQ(true, vintf::gHalManifestConverter(&m, out.str())) + << "--init-vintf does not emit valid HAL manifest: " + << vintf::gHalManifestConverter.lastError(); +} + +TEST_F(ListTest, Dump) { + const std::string expected = + "All binderized services (registered services through hwservicemanager)\n" + "Interface Transport Arch Thread Use Server PTR Clients\n" + "a.h.foo1@1.0::IFoo/1 hwbinder 64 11/21 1 0000000000002711 2 4\n" + "a.h.foo2@2.0::IFoo/2 hwbinder 64 12/22 2 0000000000002712 3 5\n" + "\n" + "All interfaces that getService() has ever return as a passthrough interface;\n" + "PIDs / processes shown below might be inaccurate because the process\n" + "might have relinquished the interface or might have died.\n" + "The Server / Server CMD column can be ignored.\n" + "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n" + "the library and successfully fetched the passthrough implementation.\n" + "Interface Transport Arch Thread Use Server PTR Clients\n" + "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A N/A 4 6\n" + "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A N/A 5 7\n" + "\n" + "All available passthrough implementations (all -impl.so files)\n" + "Interface Transport Arch Thread Use Server PTR Clients\n" + "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A N/A 6 8\n" + "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A N/A 7 9\n" + "\n"; + + optind = 1; // mimic Lshal::parseArg() + EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); +} + +TEST_F(ListTest, DumpCmdline) { + const std::string expected = + "All binderized services (registered services through hwservicemanager)\n" + "Interface Transport Arch Thread Use Server CMD PTR Clients CMD\n" + "a.h.foo1@1.0::IFoo/1 hwbinder 64 11/21 command_line_1 0000000000002711 command_line_2;command_line_4\n" + "a.h.foo2@2.0::IFoo/2 hwbinder 64 12/22 command_line_2 0000000000002712 command_line_3;command_line_5\n" + "\n" + "All interfaces that getService() has ever return as a passthrough interface;\n" + "PIDs / processes shown below might be inaccurate because the process\n" + "might have relinquished the interface or might have died.\n" + "The Server / Server CMD column can be ignored.\n" + "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n" + "the library and successfully fetched the passthrough implementation.\n" + "Interface Transport Arch Thread Use Server CMD PTR Clients CMD\n" + "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A command_line_4;command_line_6\n" + "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A command_line_5;command_line_7\n" + "\n" + "All available passthrough implementations (all -impl.so files)\n" + "Interface Transport Arch Thread Use Server CMD PTR Clients CMD\n" + "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A command_line_6;command_line_8\n" + "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A command_line_7;command_line_9\n" + "\n"; + + optind = 1; // mimic Lshal::parseArg() + EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepacm"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); +} + +TEST_F(ListTest, DumpNeat) { + const std::string expected = + "a.h.foo1@1.0::IFoo/1 11/21 1 2 4\n" + "a.h.foo2@2.0::IFoo/2 12/22 2 3 5\n" + "a.h.foo3@3.0::IFoo/3 N/A N/A 4 6\n" + "a.h.foo4@4.0::IFoo/4 N/A N/A 5 7\n" + "a.h.foo5@5.0::IFoo/5 N/A N/A 6 8\n" + "a.h.foo6@6.0::IFoo/6 N/A N/A 7 9\n"; + + optind = 1; // mimic Lshal::parseArg() + EXPECT_EQ(0u, mockList->main(createArg({"lshal", "--neat"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); +} } // namespace lshal } // namespace android -- cgit v1.2.3-59-g8ed1b