Skip to content

Instantly share code, notes, and snippets.

@thomasdarimont
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasdarimont/a5efda28841f5886dc11 to your computer and use it in GitHub Desktop.
Save thomasdarimont/a5efda28841f5886dc11 to your computer and use it in GitHub Desktop.
Simple monitoring application. Watches for the existence of a process and automatically restarts it if missing or kills it if it is not wanted. Configurable via a control-file.

#Usage

appmonitor "logical_app_name" "complete start command" "complete kill command" "path to control file"

#Example

appmonitor "calculator" "/Applications/Calculator.app/Contents/MacOS/Calculator" "pkill -9 'Calculator'" "/tmp/calculator.control"

/tmp/calculator.control must only contain either the string "ON" or "OFF" without quotes. If the control file contains "ON" and the monitored app is not started, it will be started by the appmonitor. If the control file contains "OFF"and the monitored app is started, it will be killed by the appmonitor. Otherwise the appmonitor sleeps.

//
// main.c
// appmonitor
//
// Created by Thomas Darimont on 14/10/14.
// Copyright (c) 2014 training. All rights reserved.
//
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <spawn.h>
typedef enum {
LOG_DEBUG = 0,
LOG_WARNING,
LOG_INFO,
LOG_ERROR
}
log_level_t;
typedef struct {
const char* app_name;
const char* app_start_cmd_line;
const char* app_kill_cmd_line;
const char* app_control_file_path;
const int check_interval_seconds;
} app_monitor_config;
const int ONE_SECOND = 1;
volatile bool stopMonitoring = false;
volatile bool spawnApp = false;
#ifndef HAVE_STRDUP
char *strdup(char const* in){ if (!in) return NULL; char *out;
asprintf(&out, "%s", in); return out;
}
#endif
static log_level_t __g_loglevel = LOG_DEBUG;
void log_msg(log_level_t level, const char *format, ... ){
char buffer[0xFF] = {0},
timestamp[0xFF] = {0},
*slevel;
va_list ap;
time_t rawtime;
struct tm * timeinfo;
if( level >= __g_loglevel ){
va_start( ap, format );
vsnprintf( buffer, 0xFF, format, ap );
va_end(ap);
time( &rawtime );
timeinfo = localtime( &rawtime );
strftime( timestamp, 0xFF, "%d/%m/%Y %X", timeinfo );
switch( level ){
case LOG_DEBUG : slevel = "DEBUG"; break;
case LOG_WARNING : slevel = "WARNING"; break;
case LOG_INFO : slevel = "INFO"; break;
case LOG_ERROR : slevel = "ERROR"; break;
}
printf("[%s] [%s] %s", timestamp, slevel, buffer );
}
}
bool is_app_running(app_monitor_config *config){
char *app_running_cmd = strdup("ps -A -o command | grep ");
asprintf(&app_running_cmd, "%s ^%s$", app_running_cmd, config->app_start_cmd_line);
asprintf(&app_running_cmd, "%s %s", app_running_cmd, "| grep -v grep");
log_msg(LOG_INFO, "checking app state via: '%s'\n", app_running_cmd);
int exitCode = system(app_running_cmd);
return exitCode == 0;
}
bool should_app_be_running(app_monitor_config *config){
FILE *controlFile = fopen(config->app_control_file_path, "r");
if (!controlFile)
{
return false;
}
char line[4];
if(!feof(controlFile)){
fgets(line, sizeof (line), controlFile);
}
fclose(controlFile);
if(strcmp(line,"OFF") == 0){
return false;
}
return true;
}
void launch_app_process(const char* command){
pid_t pid;
extern char **environ;
char *argv[] = {"sh", "-c", (char*)command, NULL};
int status = posix_spawn(&pid, "/bin/sh" , NULL, NULL, argv, environ);
if (status == 0) {
log_msg(LOG_INFO, "Child pid: %i\n", pid);
if (waitpid(pid, &status, 0) != -1) {
log_msg(LOG_INFO, "Child exited with status %i\n", status);
} else {
log_msg(LOG_INFO, "waitpid");
}
} else {
log_msg(LOG_INFO, "posix_spawn: %s\n", strerror(status));
}
}
void *app_launcher_loop(void* arg){
app_monitor_config *config = (app_monitor_config*)arg;
while(!stopMonitoring){
if(spawnApp){
log_msg(LOG_INFO, "starting app: '%s' via: '%s'\n", config->app_name, config->app_start_cmd_line);
launch_app_process(config->app_start_cmd_line);
spawnApp=false;
} else {
sleep(ONE_SECOND);
}
}
return NULL;
}
void start_and_run_app_launcher_loop(app_monitor_config *config ){
pthread_t launcherThread;
int status = pthread_create(&launcherThread, NULL, &app_launcher_loop, config);
if (status != 0) {
log_msg(LOG_INFO, "problem starting applauncher. exiting...\n");
} else {
log_msg(LOG_INFO, "applauncher started.\n");
}
}
void kill_app(app_monitor_config *config){
log_msg(LOG_INFO, "killing app: %s via: '%s'\n", config->app_name, config->app_kill_cmd_line);
system(config->app_kill_cmd_line);
}
void *app_monitoring_loop(void* arg) {
app_monitor_config *config = (app_monitor_config*)arg;
log_msg(LOG_INFO, "monitoring application: %s\n", config->app_name);
while(!stopMonitoring){
bool appIsRunning = is_app_running(config);
bool appShouldBeRunning = should_app_be_running(config);
log_msg(LOG_INFO, "monitor status appIsRunning: '%d' appShouldBeRunning: '%d'\n", appIsRunning, appShouldBeRunning);
if(appShouldBeRunning && !appIsRunning){
spawnApp = !appIsRunning;
}else if(!appShouldBeRunning && appIsRunning){
kill_app(config);
}
sleep(config->check_interval_seconds);
}
log_msg(LOG_INFO, "stop monitoring.\n");
return NULL;
}
void waitForEnterKey(){
char c = -1;
while(c != '\n'){
c = getchar();
}
}
void start_and_run_app_monitoring(app_monitor_config *config){
pthread_t monitorThread;
int status = pthread_create(&monitorThread, NULL, &app_monitoring_loop, config);
if (status != 0) {
log_msg(LOG_INFO, "problem starting appmonitor. exiting...\n");
} else {
log_msg(LOG_INFO, "appmonitor started.\n");
waitForEnterKey();
stopMonitoring = true;
}
}
app_monitor_config process_arguments(int argc, const char * argv[]){
app_monitor_config config = {
.app_name = argv[1],
.app_start_cmd_line = argv[2],
.app_kill_cmd_line = argv[3],
.app_control_file_path = argv[4],
.check_interval_seconds = 2 * ONE_SECOND
};
return config;
}
int main(int argc, const char * argv[]) {
app_monitor_config config = process_arguments(argc,argv);
start_and_run_app_launcher_loop(&config);
start_and_run_app_monitoring(&config);
return 0;
}
[15/10/2014 01:33:39] [INFO] applauncher started.
[15/10/2014 01:33:39] [INFO] appmonitor started.
[15/10/2014 01:33:39] [INFO] monitoring application: calculator
[15/10/2014 01:33:39] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:39] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:40] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:40] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:41] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:41] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:42] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:42] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:43] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:43] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:44] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:44] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:45] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:45] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:46] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:46] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:47] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:47] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:33:48] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:33:48] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '1'
[15/10/2014 01:33:49] [INFO] starting app: 'calculator' via: '/Applications/Calculator.app/Contents/MacOS/Calculator'
[15/10/2014 01:33:49] [INFO] Child pid: 28953
[15/10/2014 01:33:49] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:49] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:50] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:50] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:51] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:51] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:52] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:52] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:53] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:53] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:54] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:54] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:55] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:55] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:56] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:56] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:57] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:57] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '1'
[15/10/2014 01:33:58] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
/Applications/Calculator.app/Contents/MacOS/Calculator
[15/10/2014 01:33:58] [INFO] monitor status appIsRunning: '1' appShouldBeRunning: '0'
[15/10/2014 01:33:58] [INFO] killing app: calculator via: 'pkill -9 'Calculator''
[15/10/2014 01:33:58] [INFO] Child exited with status 9
[15/10/2014 01:33:59] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:00] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:01] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:01] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:02] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:02] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:03] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:03] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:04] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:04] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:05] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:05] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:06] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:06] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:07] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:07] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
[15/10/2014 01:34:08] [INFO] checking app state via: 'ps -A -o command | grep ^/Applications/Calculator.app/Contents/MacOS/Calculator$ | grep -v grep'
[15/10/2014 01:34:08] [INFO] monitor status appIsRunning: '0' appShouldBeRunning: '0'
Program ended with exit code: 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment