Skip to content

Commit e0406b0

Browse files
committed
Add flexible periodic reboot feature
1 parent e3328ae commit e0406b0

File tree

7 files changed

+175
-6
lines changed

7 files changed

+175
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [1.3.0] - 2025-08-24
6+
7+
### Added
8+
- Flexible periodic reboot feature. Supports daily time (`HH:MM`) and intervals in hours (`h`), days (`d`), weeks (`w`), and months (`m`).
9+
510
## [1.2.0] - 2025-08-23
611

712
### Added

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This application is particularly useful in environments where multiple processes
2121
- Provides file command interface to manually start, stop, or restart individual processes or the entire watchdog system or even a Linux reboot.
2222
- Generates statistics log files to track the status and history of each managed process.
2323
- Monitors CPU and memory usage of each process.
24+
- Supports periodic reboot of the host system.
2425

2526
## Requirements
2627
- If `heartbeat_interval > 0` : Processes managed by Process Watchdog must periodically send their heartbeat messages to prevent being restarted.
@@ -64,6 +65,13 @@ cmd = /usr/bin/python test_child.py 4 noheartbeat
6465

6566
### Fields
6667
- `udp_port` : The UDP port to expect heartbeats.
68+
- `periodic_reboot`: Optional. Specifies the periodic reboot schedule. The feature is disabled if the key is not found or the value is invalid. The supported formats are:
69+
- Daily Specific Time: A specific time in `HH:MM` format (e.g., `04:00` for a reboot at 4:00 AM every day).
70+
- Hourly Interval: An integer value followed by `h` (e.g., `6h` for every 6 hours).
71+
- Daily Interval: An integer value followed by `d` (e.g., `5d` for every 5 days).
72+
- Weekly Interval: An integer value followed by `w` (e.g., `2w` for every 2 weeks).
73+
- Monthly Interval: An integer value followed by `m` (e.g., `3m` for every 3 months).
74+
- Default to Days: If no unit is provided, the value is treated as days (e.g., `7` is equivalent to `7d`).
6775
- `[app:<AppName>]` : Each application to be monitored should have its own section prefixed with `app:`. `<AppName>` will be used as the name of the application.
6876
- `start_delay` : Delay in seconds before starting the application.
6977
- `heartbeat_delay` : Time in seconds to wait before expecting a heartbeat from the application.
@@ -302,6 +310,15 @@ Minimum first heartbeat time: 104 seconds
302310
Average heartbeat time: 102 seconds
303311
Maximum heartbeat time: 110 seconds
304312
Minimum heartbeat time: 102 seconds
313+
Resource sample count: 190
314+
Current CPU usage: 0.50%
315+
Maximum CPU usage: 0.80%
316+
Minimum CPU usage: 0.00%
317+
Average CPU usage: 0.40%
318+
Current memory usage: 11.62 MB
319+
Maximum memory usage: 11.88 MB
320+
Minimum memory usage: 11.38 MB
321+
Average memory usage: 11.74 MB
305322
Magic: A50FAA55
306323
```
307324

@@ -353,10 +370,10 @@ Use the provided `run.sh` script to start the Process Watchdog application. This
353370
Or just `./run.sh &` which is recommended.
354371

355372
## TODO
373+
- Attaching to already running processes
356374
- Create easy-to-use heartbeat libraries
357375
- Enable commands over UDP
358376
- Enable remote syslog server reporting
359-
- Add periodic reboot feature
360377
- Add periodic server health reporting
361378
- Add IPC and TCP support
362379
- Add json support

config.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[processWatchdog]
22
udp_port = 12345
3+
periodic_reboot = OFF
34

45
[app:Communicator]
56
start_delay = 10

src/apps.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,30 @@ typedef struct
5959
time_t last_heartbeat; /**< Time when the last heartbeat was received from the application. */
6060
} Application_t;
6161

62+
typedef enum
63+
{
64+
REBOOT_MODE_DISABLED,
65+
REBOOT_MODE_DAILY_TIME,
66+
REBOOT_MODE_INTERVAL
67+
} RebootMode_t;
68+
6269
typedef struct
6370
{
6471
int app_count; /**< Total number of applications found in the ini file. */
6572
int udp_port; /**< UDP port number specified in the ini file. */
6673
char ini_file[MAX_APP_CMD_LENGTH]; /**< Path to the ini file. */
6774
time_t ini_last_modified_time; /**< Last modified time of the ini file. */
6875
time_t uptime; /**< System uptime in seconds. */
76+
RebootMode_t periodic_reboot; /**< Periodic reboot mode. */
77+
union
78+
{
79+
struct
80+
{
81+
int hour;
82+
int min;
83+
} daily_time;
84+
long interval_minutes;
85+
} reboot_params; /**< Periodic reboot parameters. */
6986
} AppState_t;
7087

7188
// Function prototypes

src/config.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include <errno.h>
2929
#include <limits.h>
3030

31+
#define MAX_REBOOT_MINUTES 525600 // 1 year in minutes
32+
3133
static time_t config_get_file_modified_time(const char *path)
3234
{
3335
struct stat attr;
@@ -100,6 +102,88 @@ static int config_ini_handler(void *user, const char *section, const char *name,
100102
return 0;
101103
}
102104
}
105+
else if(strcmp(name, "periodic_reboot") == 0)
106+
{
107+
state->periodic_reboot = REBOOT_MODE_DISABLED;
108+
int hour, min;
109+
110+
if(sscanf(value, "%d:%d", &hour, &min) == 2)
111+
{
112+
if(hour >= 0 && hour <= 23 && min >= 0 && min <= 59)
113+
{
114+
state->periodic_reboot = REBOOT_MODE_DAILY_TIME;
115+
state->reboot_params.daily_time.hour = hour;
116+
state->reboot_params.daily_time.min = min;
117+
LOGN("Periodic reboot set to daily at %02d:%02d", hour, min);
118+
}
119+
}
120+
else
121+
{
122+
char unit = 'd';
123+
long interval = 0;
124+
char *endptr;
125+
interval = strtol(value, &endptr, 10);
126+
127+
if(endptr != value)
128+
{
129+
if(*endptr != '\0')
130+
{
131+
unit = *endptr;
132+
}
133+
134+
long multiplier = 0;
135+
136+
switch(unit)
137+
{
138+
case 'h':
139+
case 'H':
140+
multiplier = 60;
141+
break;
142+
143+
case 'd':
144+
case 'D':
145+
multiplier = 24 * 60;
146+
break;
147+
148+
case 'w':
149+
case 'W':
150+
multiplier = 7 * 24 * 60;
151+
break;
152+
153+
case 'm':
154+
case 'M':
155+
multiplier = 30 * 24 * 60;
156+
break;
157+
158+
default:
159+
break;
160+
}
161+
162+
if(multiplier > 0 && interval > 0)
163+
{
164+
if(interval > LONG_MAX / multiplier)
165+
{
166+
LOGE("Reboot interval value is too large and would cause an overflow.");
167+
}
168+
else
169+
{
170+
long interval_minutes = interval * multiplier;
171+
172+
if(interval_minutes <= MAX_REBOOT_MINUTES)
173+
{
174+
state->periodic_reboot = REBOOT_MODE_INTERVAL;
175+
state->reboot_params.interval_minutes = interval_minutes;
176+
LOGN("Periodic reboot set to an interval of %ld minutes.", interval_minutes);
177+
}
178+
else
179+
{
180+
LOGW("Reboot interval of %ld minutes is too long (max is %d minutes).", interval_minutes, MAX_REBOOT_MINUTES);
181+
}
182+
}
183+
}
184+
}
185+
}
186+
}
103187
}
104188
else if(index != -1) // This is an app section we are tracking
105189
{
@@ -172,6 +256,7 @@ int config_parse_file(const char *ini_path, Application_t *apps, int max_apps, A
172256
state->app_count = 0;
173257
state->uptime = get_uptime();
174258
state->udp_port = UDP_PORT;
259+
state->periodic_reboot = REBOOT_MODE_DISABLED;
175260
LOGD("Reading ini file %s", ini_path);
176261
// Prepare user data for the handler
177262
void *user_data[2] = { apps, state };

src/main.c

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ extern char *optarg;
136136
extern int opterr, optind;
137137

138138
#define APPNAME basename(argv[0])
139-
#define VERSION "1.2.0"
139+
#define VERSION "1.3.0"
140140
#define OPTSTR "i:v:t:h"
141141
#define USAGE_FMT "%s -i <file.ini> [-v] [-h] [-t testname]\n"
142142

@@ -214,6 +214,7 @@ void help(char *progname)
214214
fprintf(stderr, GREEN "\nINI File example config:\n" RESET
215215
"[processWatchdog]\n"
216216
"udp_port = 12345\n"
217+
"periodic_reboot = OFF\n"
217218
"\n"
218219
"[app:Communicator]\n"
219220
"start_delay = 10\n"
@@ -291,6 +292,7 @@ int main(int argc, char *argv[])
291292
}
292293

293294
LOGN("%s started v:%s", APPNAME, VERSION);
295+
time_t start_time = time(NULL);
294296

295297
// Read config
296298
if(read_ini_file())
@@ -320,6 +322,7 @@ int main(int argc, char *argv[])
320322
// Loop here until exit signal arrived
321323
while(main_alive)
322324
{
325+
long uptime = time(NULL) - start_time;
323326
// Poll UDP messages
324327
length = sizeof(data) - 1;
325328

@@ -347,13 +350,13 @@ int main(int argc, char *argv[])
347350
if(process_is_started(i))
348351
{
349352
// Update resource usage stats (1 min)
350-
if((get_uptime() % 60) == 0 && process_is_running(i))
353+
if((uptime % 60) == 0 && process_is_running(i))
351354
{
352355
stats_update_resource_usage(i, get_app_pid(i));
353356
}
354357

355358
// Update stats files periodically (15 mins)
356-
if((get_uptime() % (15 * 60)) == 0)
359+
if((uptime % (15 * 60)) == 0)
357360
{
358361
stats_write_to_file(i, get_app_name(i));
359362
stats_print_to_file(i, get_app_name(i));
@@ -420,13 +423,53 @@ int main(int argc, char *argv[])
420423
return_code = EXIT_REBOOT;
421424
}
422425

426+
// Check for periodic reboot (1 min)
427+
if(apps_get_state()->periodic_reboot != REBOOT_MODE_DISABLED && (uptime % 60 == 0))
428+
{
429+
switch(apps_get_state()->periodic_reboot)
430+
{
431+
case REBOOT_MODE_DAILY_TIME:
432+
{
433+
time_t now = time(NULL);
434+
struct tm *tm_now = localtime(&now);
435+
436+
if(tm_now->tm_hour == apps_get_state()->reboot_params.daily_time.hour && tm_now->tm_min == apps_get_state()->reboot_params.daily_time.min)
437+
{
438+
LOGN("Periodic reboot triggered (daily time)");
439+
main_alive = false;
440+
return_code = EXIT_REBOOT;
441+
}
442+
443+
break;
444+
}
445+
446+
case REBOOT_MODE_INTERVAL:
447+
{
448+
long uptime_minutes = uptime / 60;
449+
450+
if(uptime_minutes > 0 && uptime_minutes % apps_get_state()->reboot_params.interval_minutes == 0)
451+
{
452+
LOGN("Periodic reboot triggered (interval)");
453+
main_alive = false;
454+
return_code = EXIT_REBOOT;
455+
}
456+
457+
break;
458+
}
459+
460+
default:
461+
break;
462+
}
463+
}
464+
423465
#if 0 // feature disabled
424466

425467
// Check if ini updated and re-read
426468
if(is_ini_updated())
427469
{
428470
if(read_ini_file())
429471
{
472+
// TODO: attaching to existing PIDs to prevent restarting the processes
430473
exit(EXIT_RESTART);
431474
}
432475
}

test/server_test.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#include "gtest/gtest.h"
22

33
extern "C" {
4-
#include "server.h"
4+
#include "server.h"
55
}
66

7-
TEST(ServerTest, UdpStart) {
7+
TEST(ServerTest, UdpStart)
8+
{
89
int socket;
910
int port = 12345;
1011
ASSERT_EQ(udp_start(&socket, port), 0);

0 commit comments

Comments
 (0)