Skip to content

Commit d47313f

Browse files
DataSync: Added signal-triggered watched paths logging utility
- Introduced a mechanism to handle SIGUSR1 signal for debugging purposes. - Added support to read a list of paths from a user defined file `/tmp/data_sync_check_watched_paths.txt` and log to file `/tmp/data_sync_check_output.txt`, whether each path is currently being watched or not. - To trigger send the SIGUSR1 signal to `phosphor-rbmc-data-sync-mgr` - On receiving the SIGUSR1 signal, if user defined file is absent, then the system logs all currently watched paths to `/tmp/data_sync_current_watched_paths.json`. - Helps in live debugging and verification of inotify watch status without restarting services. Tested - Added test cases. - Verified on simics. If `/tmp/data_sync_check_watched_paths.txt` present, Example output file generated: ```bash $ kill -10 1156 $ cat data_sync_check_output.txt Individual user entered paths results: Watching path: /var/lib Not Watching path: ntest ``` If `/tmp/data_sync_check_watched_paths.txt` absent, Example JSON file generated: ```bash $ kill -10 1156 $ cat /tmp/data_sync_current_watched_paths.json [ "/var/lib", "/var/lib/phosphor-state-manager" ] ``` Change-Id: I251fcf15c5307a2ceca5e464d0bd2ba1b98f7974 Signed-off-by: Harsh Agarwal <[email protected]>
1 parent 585f626 commit d47313f

File tree

6 files changed

+282
-0
lines changed

6 files changed

+282
-0
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,24 @@ BMC applications.
1010
meson setup builddir
1111
meson compile -C builddir
1212
```
13+
14+
### Signal-Triggered Watched Paths Logging (Debug Utility)
15+
16+
Inspect watched paths using the `SIGUSR1` signal. This feature is helpful for
17+
verifying which paths are currently being monitored by the system.
18+
19+
If you want to check if some particular paths are being monitored or not, create
20+
a file `/tmp/data_sync_check_watched_paths.txt` and enter the paths line by
21+
line. Then send the signal, you can see the output in
22+
`/tmp/data_sync_check_output.txt`.
23+
24+
If the user input file is not present, then on receiving the signal, it will
25+
write all the currently monitored path to
26+
`/tmp/data_sync_current_watched_paths.json`.
27+
28+
To trigger the utility, send a `SIGUSR1` signal to the
29+
`phosphor-rbmc-data-sync-mgr` process:
30+
31+
```bash
32+
kill -10 <pid_of_phosphor-rbmc-data-sync-mgr>
33+
```

src/data_watcher.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ DataWatcher::DataWatcher(sdbusplus::async::context& ctx, const int inotifyFlags,
1919
std::make_unique<sdbusplus::async::fdio>(ctx, _inotifyFileDescriptor()))
2020
{
2121
createWatchers(_dataPathToWatch);
22+
std::lock_guard<std::mutex> lock(getMutex());
23+
getAllWatchers().push_back(this);
2224
}
2325

2426
DataWatcher::~DataWatcher()
@@ -32,6 +34,9 @@ DataWatcher::~DataWatcher()
3234
}
3335
});
3436
}
37+
auto& instances = getAllWatchers();
38+
instances.erase(std::remove(instances.begin(), instances.end(), this),
39+
instances.end());
3540
}
3641

3742
int DataWatcher::inotifyInit() const

src/data_watcher.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <filesystem>
1111
#include <map>
12+
#include <mutex>
1213

1314
namespace data_sync::watch::inotify
1415
{
@@ -127,6 +128,42 @@ class DataWatcher
127128
*/
128129
sdbusplus::async::task<DataOperations> onDataChange();
129130

131+
/**
132+
* @brief API to get all the instances of the DataWatcher
133+
*
134+
* @returns std::vector<DataWatcher*> - A reference to the vector containing
135+
* all instances of this class
136+
*/
137+
static std::vector<DataWatcher*>& getAllWatchers()
138+
{
139+
static std::vector<DataWatcher*> instances;
140+
return instances;
141+
}
142+
143+
/**
144+
* @brief API to get the reference to the static mutex to guard
145+
* concurrent access to shared data (i.e., the vector of DataWatcher
146+
* instances)
147+
*
148+
* @returns std::mutex& - A reference to the static mutex
149+
*/
150+
static std::mutex& getMutex()
151+
{
152+
static std::mutex mtx;
153+
return mtx;
154+
}
155+
156+
/**
157+
* @brief API to get the file descriptor of the inotify instance
158+
*
159+
* @returns std::map<WD, fs::path>& - A reference to the map of WD and their
160+
* associated file paths.
161+
*/
162+
const std::map<WD, fs::path>& getWatchDescriptors() const
163+
{
164+
return _watchDescriptors;
165+
}
166+
130167
private:
131168
/**
132169
* @brief inotify flags

src/manager.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <nlohmann/json.hpp>
88
#include <phosphor-logging/lg2.hpp>
99

10+
#include <csignal>
1011
#include <cstdlib>
1112
#include <exception>
1213
#include <fstream>
@@ -46,9 +47,76 @@ sdbusplus::async::task<> Manager::init()
4647
co_await startFullSync();
4748
}
4849

50+
std::signal(SIGUSR1, logSignalHandler);
4951
co_return co_await startSyncEvents();
5052
}
5153

54+
std::unordered_set<std::string> Manager::collectAllWatchedPaths()
55+
{
56+
std::unordered_set<std::string> watchedPaths;
57+
for (const auto* watcher : watch::inotify::DataWatcher::getAllWatchers())
58+
{
59+
std::ranges::transform(
60+
watcher->getWatchDescriptors(),
61+
std::inserter(watchedPaths, watchedPaths.end()),
62+
[](const auto& wdPathPair) { return wdPathPair.second.string(); });
63+
}
64+
return watchedPaths;
65+
}
66+
67+
void Manager::saveWatchedPathsToFile()
68+
{
69+
std::string storeWatchedPath = "/tmp/data_sync_current_watched_paths.json";
70+
try
71+
{
72+
data_sync::persist::util::writeFile(
73+
nlohmann::json(collectAllWatchedPaths()), storeWatchedPath);
74+
lg2::info("Wrote all watched paths to file: {FILE}", "FILE",
75+
storeWatchedPath);
76+
}
77+
catch (const std::exception& e)
78+
{
79+
lg2::error("Error writing all watched paths to file: {ERROR}", "ERROR",
80+
e);
81+
}
82+
}
83+
84+
void Manager::watchedPathCheck(std::ifstream& infile)
85+
{
86+
std::string outputFilePath = "/tmp/data_sync_check_output.txt";
87+
std::unordered_set<std::string> watchedPaths = collectAllWatchedPaths();
88+
if (std::ofstream outfile(outputFilePath); outfile)
89+
{
90+
outfile << "Individual user entered paths results:\n";
91+
for (std::string line; std::getline(infile, line);)
92+
{
93+
std::string_view status =
94+
watchedPaths.contains(line) ? "Watching" : "Not Watching";
95+
outfile << status << " path: " << line << '\n';
96+
}
97+
}
98+
else
99+
{
100+
lg2::error("Failed to write to {FILE}", "FILE", outputFilePath);
101+
}
102+
}
103+
104+
void Manager::logSignalHandler(int signal)
105+
{
106+
lg2::info("Received signal: {SIGNAL}", "SIGNAL", signal);
107+
std::string checkForWatchedPaths = "/tmp/data_sync_check_watched_paths.txt";
108+
if (std::ifstream infile{checkForWatchedPaths}; infile)
109+
{
110+
watchedPathCheck(infile);
111+
}
112+
else
113+
{
114+
lg2::info("Unable to open user input {FILE}, logging all watched paths",
115+
"FILE", checkForWatchedPaths);
116+
saveWatchedPathsToFile();
117+
}
118+
}
119+
52120
// NOLINTNEXTLINE
53121
sdbusplus::async::task<> Manager::parseConfiguration()
54122
{

src/manager.hpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <filesystem>
1111
#include <ranges>
12+
#include <unordered_set>
1213
#include <vector>
1314

1415
namespace data_sync
@@ -201,6 +202,39 @@ class Manager
201202
*/
202203
bool isSyncEligible(const config::DataSyncConfig& dataSyncCfg);
203204

205+
/**
206+
* @brief A helper function which saves all currently watched paths to a
207+
* JSON file.
208+
*/
209+
static void saveWatchedPathsToFile();
210+
211+
/**
212+
* @brief A helper function which reads paths from the given input file
213+
* stream and compares each path against currently watched paths, then
214+
* writes the result for each path to `/tmp/data_sync_check_output.txt`.
215+
*
216+
* @param[in] infile - Input file stream containing one path per line to
217+
* check.
218+
*/
219+
static void watchedPathCheck(std::ifstream& infile);
220+
221+
/**
222+
* @brief A helper function to handle logging when a SIGUSR1 signal is
223+
* received.
224+
*
225+
* @param[in] signal - The signal value that triggered the handler.
226+
*/
227+
static void logSignalHandler(int signal);
228+
229+
/**
230+
* @brief A helper API to collect all the paths currently being watched for
231+
* immediate sync.
232+
*
233+
* @returns std::unordered_set<std::string> - A set containing all the paths
234+
* being watched.
235+
*/
236+
static std::unordered_set<std::string> collectAllWatchedPaths();
237+
204238
/**
205239
* @brief The async context object used to perform operations asynchronously
206240
* as required.

test/manager_test.cpp

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: Apache-2.0
22
#include "manager_test.hpp"
33

4+
#include <csignal>
5+
46
std::filesystem::path ManagerTest::dataSyncCfgDir;
57
std::filesystem::path ManagerTest::tmpDataSyncDataDir;
68
nlohmann::json ManagerTest::commonJsonData;
@@ -208,3 +210,118 @@ TEST_F(ManagerTest, testDBusDataPersistency)
208210

209211
ctx.run();
210212
}
213+
214+
TEST_F(ManagerTest, testSignalReceiverLogic)
215+
{
216+
using namespace std::literals;
217+
namespace ed = data_sync::ext_data;
218+
219+
std::unique_ptr<ed::ExternalDataIFaces> extDataIface =
220+
std::make_unique<ed::MockExternalDataIFaces>();
221+
222+
ed::MockExternalDataIFaces* mockExtDataIfaces =
223+
dynamic_cast<ed::MockExternalDataIFaces*>(extDataIface.get());
224+
225+
ON_CALL(*mockExtDataIfaces, fetchBMCRedundancyMgrProps())
226+
// NOLINTNEXTLINE
227+
.WillByDefault([&mockExtDataIfaces]() -> sdbusplus::async::task<> {
228+
mockExtDataIfaces->setBMCRole(ed::BMCRole::Active);
229+
mockExtDataIfaces->setBMCRedundancy(true);
230+
co_return;
231+
});
232+
233+
EXPECT_CALL(*mockExtDataIfaces, fetchSiblingBmcIP())
234+
// NOLINTNEXTLINE
235+
.WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; });
236+
237+
EXPECT_CALL(*mockExtDataIfaces, fetchRbmcCredentials())
238+
// NOLINTNEXTLINE
239+
.WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; });
240+
241+
nlohmann::json jsonData = {
242+
{"Files",
243+
{
244+
{{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile1"},
245+
{"DestinationPath",
246+
ManagerTest::tmpDataSyncDataDir.string() + "/destDir/"},
247+
{"Description", "FullSync from Active to Passive bmc"},
248+
{"SyncDirection", "Active2Passive"},
249+
{"SyncType", "Immediate"}},
250+
{{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile2"},
251+
{"DestinationPath",
252+
ManagerTest::tmpDataSyncDataDir.string() + "/destDir/"},
253+
{"Description", "FullSync from Active to Passive bmc"},
254+
{"SyncDirection", "Active2Passive"},
255+
{"SyncType", "Immediate"}},
256+
}}};
257+
258+
fs::path srcFile1{jsonData["Files"][0]["Path"]};
259+
fs::path srcFile2{jsonData["Files"][1]["Path"]};
260+
261+
fs::path destDir1{jsonData["Files"][0]["DestinationPath"]};
262+
fs::path destDir2{jsonData["Files"][1]["DestinationPath"]};
263+
264+
writeConfig(jsonData);
265+
std::filesystem::remove("/tmp/data_sync_check_watched_paths.txt");
266+
sdbusplus::async::context ctx;
267+
268+
std::string data1{"Data written on the file1\n"};
269+
std::string data2{"Data written on the file2\n"};
270+
271+
ManagerTest::writeData(srcFile1, data1);
272+
ManagerTest::writeData(srcFile2, data2);
273+
274+
ASSERT_EQ(ManagerTest::readData(srcFile1), data1);
275+
ASSERT_EQ(ManagerTest::readData(srcFile2), data2);
276+
277+
data_sync::Manager manager{ctx, std::move(extDataIface),
278+
ManagerTest::dataSyncCfgDir};
279+
280+
auto waitingForFullSyncToFinish =
281+
// NOLINTNEXTLINE
282+
[&](sdbusplus::async::context& ctx) -> sdbusplus::async::task<void> {
283+
auto status = manager.getFullSyncStatus();
284+
285+
while (status != FullSyncStatus::FullSyncCompleted &&
286+
status != FullSyncStatus::FullSyncFailed)
287+
{
288+
co_await sdbusplus::async::sleep_for(ctx,
289+
std::chrono::milliseconds(50));
290+
status = manager.getFullSyncStatus();
291+
}
292+
293+
EXPECT_EQ(status, FullSyncStatus::FullSyncCompleted)
294+
<< "FullSync status is not Completed!";
295+
296+
// Raising signal after full sync completes, so that sync events starts
297+
// and the watchers are being created.
298+
sdbusplus::async::sleep_for(ctx, 0.1s);
299+
// Raising a signal to trigger the signal handler logic.
300+
std::raise(SIGUSR1);
301+
// waiting to write all the watched paths to a file as per
302+
// signal handler logic.
303+
sdbusplus::async::sleep_for(ctx, 0.5s);
304+
305+
ctx.request_stop();
306+
307+
// Forcing to trigger inotify events so that all running immediate
308+
// sync tasks will resume and stop since the context is requested to
309+
// stop in the above.
310+
ManagerTest::writeData(srcFile1, data1);
311+
ManagerTest::writeData(srcFile2, data2);
312+
313+
// Reading the file where all the watched paths are written by signal
314+
// handler logic.
315+
auto watchersF =
316+
ManagerTest::readData("/tmp/data_sync_current_watched_paths.json");
317+
auto parsed = nlohmann::json::parse(watchersF);
318+
EXPECT_TRUE(parsed.is_array());
319+
// Checking if the srcFile1 and srcFile2 are present in the parsed json
320+
EXPECT_NE(watchersF.find(srcFile1), std::string::npos);
321+
EXPECT_NE(watchersF.find(srcFile2), std::string::npos);
322+
co_return;
323+
};
324+
325+
ctx.spawn(waitingForFullSyncToFinish(ctx));
326+
ctx.run();
327+
}

0 commit comments

Comments
 (0)