Skip to content

Commit 7611cf4

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_paths_info.lsv` and log to same file 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_paths_info.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_paths_info.lsv` present, Example output file generated: ```bash $ kill -10 1156 $ cat data_sync_paths_info.lsv Individual user entered paths results: Watching path: /var/lib Not Watching path: ntest ``` If `/tmp/data_sync_paths_info.lsv` absent, Example JSON file generated: ```bash $ kill -10 1156 $ cat /tmp/data_sync_paths_info.json [ "/var/lib", "/var/lib/phosphor-state-manager" ] ``` Change-Id: I251fcf15c5307a2ceca5e464d0bd2ba1b98f7974 Signed-off-by: Harsh Agarwal <[email protected]>
1 parent d1ef350 commit 7611cf4

File tree

6 files changed

+315
-0
lines changed

6 files changed

+315
-0
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,45 @@ BMC applications.
1010
meson setup builddir
1111
meson compile -C builddir
1212
```
13+
14+
### Signal-Triggered Watched Paths Logging (DataSync 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 phosphor-data-sync.
18+
19+
#### To check whether certain files or directories are currently being monitored
20+
21+
If you want to check if some particular paths are being monitored or not, create
22+
a file `/tmp/data_sync_paths_info.lsv` and enter the paths line by line. Then
23+
send the signal (Refer below), you can see the output in the same file.
24+
25+
```bash
26+
$ cat data_sync_paths_info.lsv
27+
rtest
28+
/var/lib
29+
$ kill -SIGUSR1 <pid_of_phosphor-rbmc-data-sync-mgr>
30+
$ cat data_sync_paths_info.lsv
31+
Individual user entered paths results:
32+
Not Watching path: rtest
33+
Watching path: /var/lib
34+
```
35+
36+
If the user input file is not present, then on receiving the signal, it will
37+
dump all the currently monitored paths to `/tmp/data_sync_paths_info.json`.
38+
Refer below for example output
39+
40+
```bash
41+
$ cat data_sync_paths_info.json
42+
[
43+
"/var/lib",
44+
"/var/lib/phosphor-state-manager"
45+
]
46+
```
47+
48+
To trigger the utility, send a `SIGUSR1` signal to the
49+
`phosphor-rbmc-data-sync-mgr` process:
50+
51+
```bash
52+
pgrep -f phosphor-rbmc-data-sync-mgr
53+
kill -SIGUSR1 <pid_of_phosphor-rbmc-data-sync-mgr>
54+
```

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
{
@@ -130,6 +131,42 @@ class DataWatcher
130131
*/
131132
sdbusplus::async::task<DataOperations> onDataChange();
132133

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

src/manager.cpp

Lines changed: 77 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,85 @@ 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_paths_info.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+
bool Manager::watchedPathCheck(std::string& filePath)
85+
{
86+
std::ifstream infile{filePath};
87+
if (!infile)
88+
{
89+
return false;
90+
}
91+
std::vector<std::string> lines(std::istream_iterator<std::string>{infile},
92+
{});
93+
std::unordered_set<std::string> watchedPaths = collectAllWatchedPaths();
94+
95+
if (std::ofstream outfile{std::string(filePath), std::ios::trunc}; outfile)
96+
{
97+
outfile << "Individual user entered paths results:\n";
98+
for (const auto& line : lines)
99+
{
100+
outfile << (watchedPaths.contains(line) ? "Watching"
101+
: "Not Watching")
102+
<< " path: " << line << '\n';
103+
}
104+
return true;
105+
}
106+
else
107+
{
108+
return false;
109+
}
110+
}
111+
112+
void Manager::logSignalHandler(int signal)
113+
{
114+
lg2::info("Received signal: {SIGNAL}", "SIGNAL", signal);
115+
std::string checkForWatchedPaths = "/tmp/data_sync_paths_info.lsv";
116+
if (watchedPathCheck(checkForWatchedPaths))
117+
{
118+
lg2::info("Checked and wrote each path in file {FILE}", "FILE",
119+
checkForWatchedPaths);
120+
}
121+
else
122+
{
123+
lg2::info("Unable to open user input {FILE}, logging all watched paths",
124+
"FILE", checkForWatchedPaths);
125+
saveWatchedPathsToFile();
126+
}
127+
}
128+
52129
// NOLINTNEXTLINE
53130
sdbusplus::async::task<> Manager::parseConfiguration()
54131
{

src/manager.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 <ranges>
12+
#include <unordered_set>
1213
#include <vector>
1314

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

206+
/**
207+
* @brief A helper function which saves all currently watched paths to a
208+
* JSON file.
209+
*/
210+
static void saveWatchedPathsToFile();
211+
212+
/**
213+
* @brief A helper function which reads paths from the given input file
214+
* stream and compares each path against currently watched paths, then
215+
* writes the result to same path `/tmp/data_sync_paths_info.lsv`.
216+
*
217+
* @param[in] filePath - Input file path containing one path per line to
218+
* check.
219+
*
220+
* @return True if filePath present and succesfully wrote to it; otherwise
221+
* False.
222+
*/
223+
static bool watchedPathCheck(std::string& filePath);
224+
225+
/**
226+
* @brief A helper function to handle logging when a SIGUSR1 signal is
227+
* received.
228+
*
229+
* @param[in] signal - The signal value that triggered the handler.
230+
*/
231+
static void logSignalHandler(int signal);
232+
233+
/**
234+
* @brief A helper API to collect all the paths currently being watched for
235+
* immediate sync.
236+
*
237+
* @returns std::unordered_set<std::string> - A set containing all the paths
238+
* being watched.
239+
*/
240+
static std::unordered_set<std::string> collectAllWatchedPaths();
241+
205242
/**
206243
* @brief The async context object used to perform operations asynchronously
207244
* 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_paths_info.lsv");
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_paths_info.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)