diff --git a/Makefile.am b/Makefile.am index d6210fdc..a91fd19c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,6 +43,7 @@ sgminer_SOURCES += adl.c adl.h adl_functions.h sgminer_SOURCES += pool.c pool.h sgminer_SOURCES += algorithm.c algorithm.h sgminer_SOURCES += config_parser.c config_parser.h +sgminer_SOURCES += events.c events.h sgminer_SOURCES += ocl/patch_kernel.c ocl/patch_kernel.h sgminer_SOURCES += ocl/build_kernel.c ocl/build_kernel.h sgminer_SOURCES += ocl/binary_kernel.c ocl/binary_kernel.h diff --git a/config_parser.c b/config_parser.c index 3f5430a2..018e1bd9 100644 --- a/config_parser.c +++ b/config_parser.c @@ -662,7 +662,7 @@ static char *parse_config_array(json_t *obj, char *parentkey, bool fileconf) json_t *val; //fix parent key - remove extra "s" to match opt names (e.g. --pool-gpu-memclock not --pools-gpu-memclock) - if(!strcasecmp(parentkey, "pools") || !strcasecmp(parentkey, "profiles")) + if(!strcasecmp(parentkey, "pools") || !strcasecmp(parentkey, "profiles") || !strcasecmp(parentkey, "events")) parentkey[(strlen(parentkey) - 1)] = '\0'; json_array_foreach(obj, idx, val) diff --git a/doc/configuration.md b/doc/configuration.md index 856987b2..2d4d99be 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -8,8 +8,11 @@ * [Globals and the Default Profile](#globals-and-the-default-profile) * [Working with Profiles and Pool Specific Settings](#working-with-profiles-and-pool-specific-settings) * [Include and Includes](#include-and-includes) +* [Events](#events) * [CLI Only options](#cli-only-options) * [Config-file and CLI options](#config-file-and-cli-options) +* [Event options](#event-options) +* [Event Types](#event-types) --- @@ -233,6 +236,34 @@ There is no limit as to how includes can be used as long as they follow proper j --- +## Events + +Users can now execute commands or perform certain tasks when pre-defined events occur while mining. + +For example, one might want their miner to email them via a script when the miner goes idle and reboot the computer when a GPU goes dead. This gives users a little more flexibility controlling their mining uptime without necessarily resorting to external watchdog programs that, in some cases, can be troublesome. + +Here is a configuration example of the above scenario: +``` +... +"events":[ + { + "on":"idle", + "runcmd":"/bin/mailscript \"Miner Idle\" \"Hey! My miner went idle!\"" + }, + { + "on":"gpu_dead", + "reboot":"yes" + } +], +... +``` + +For more details on configuration options, see [Event Options](#event-options) below. + +[Top](#configuration-and-command-line-options) + +--- + ## CLI Only options * [config](#config) `--config` or `-c` @@ -2446,3 +2477,120 @@ Displays extra work time debug information. *Default:* `false` [Top](#configuration-and-command-line-options) :: [Config-file and CLI options](#config-file-and-cli-options) :: [Miscellaneous Options](#miscellaneous-options) + +--- + +## Event options + +* [on](#on) +* [runcmd](#runcmd) +* [reboot](#reboot) +* [reboot-delay](#reboot-delay) +* [quit](#quit) +* [quit-message](#quit-message) + +### on + +Specify which event type to respond on. See below for a list of supported [event types](#event-types) + +*Available*: Events + +*Config File Syntax:* `"on":""` + +*Command Line Syntax:* `--event-on ` + +*Argument:* `string` Name of the event type + +*Default:* None + +[Top](#configuration-and-command-line-options) :: [Event options](#event-options) + +### runcmd + +Specify a command to run when the event occurs. Please remember to properly escape quotes (") with backslashes (\\) if you need to specify multi-word parameters enclosed in quotes (") for your commands: `\"` + +*Available*: Events + +*Config File Syntax:* `"runcmd":""` + +*Command Line Syntax:* `--event-runcmd ` + +*Argument:* `string` Command to execute on event + +*Default:* None + +[Top](#configuration-and-command-line-options) :: [Event options](#event-options) + +### reboot + +Reboot when event occurs. + +*Available*: Events + +*Config File Syntax:* `"reboot":""` + +*Command Line Syntax:* `--event-reboot ` + +*Argument:* `string` Yes: `"true"` `"yes"` `"1"` or No: `"false"` `"no"` `"0"` + +*Default:* `false` + +[Top](#configuration-and-command-line-options) :: [Event options](#event-options) + +### reboot-delay + +Wait a number of seconds before rebooting when event occurs. This is useful if you also want to fire off a script via `runcmd` prior to rebooting, giving it extra seconds to finish. + +*Available*: Events + +*Config File Syntax:* `"reboot-delay":""` + +*Command Line Syntax:* `--event-reboot-delay ` + +*Argument:* `number` Seconds to wait before reboot + +*Default:* `0` + +[Top](#configuration-and-command-line-options) :: [Event options](#event-options) + +### quit + +Exit sgminer when event occurs. + +*Available*: Events + +*Config File Syntax:* `"quit":""` + +*Command Line Syntax:* `--event-quit ` + +*Argument:* `string` Yes: `"true"` `"yes"` `"1"` or No: `"false"` `"no"` `"0"` + +*Default:* `false` + +[Top](#configuration-and-command-line-options) :: [Event options](#event-options) + +### quit-message + +Message to display on sgminer exit when event occurs. + +*Available*: Events + +*Config File Syntax:* `"quit-message":""` + +*Command Line Syntax:* `--event-quit-message ""` + +*Argument:* `string` Message + +*Default:* `event_type` + +[Top](#configuration-and-command-line-options) :: [Event options](#event-options) + +--- + +## Event Types + +* `idle` Occurs when a GPU goes idle for not performing any work or when no work has been received in 10 minutes. +* `gpu_sick` Occurs when a GPU fails to respond for 2 minutes +* `gpu_dead` Occurs when a GPU fails to respond for 10 minutes + +[Top](#configuration-and-command-line-options) \ No newline at end of file diff --git a/events.c b/events.c new file mode 100644 index 00000000..9f9db1a9 --- /dev/null +++ b/events.c @@ -0,0 +1,270 @@ +/* + * Copyright 2013-2014 sgminer developers (see AUTHORS.md) + * Copyright 2011-2013 Con Kolivas + * Copyright 2011-2012 Luke Dashjr + * Copyright 2010 Jeff Garzik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. See COPYING for more details. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "compat.h" +#include "miner.h" +#include "events.h" +#include "config_parser.h" + +// global event list +event_t *events = NULL, *last_event = NULL; + +/*************************************************** + * Helper functions + **************************************************/ +static void *cmd_thread(void *cmdp) +{ + const char *cmd = (const char*)cmdp; + + applog(LOG_DEBUG, "Executing command: %s", cmd); + system(cmd); + + return NULL; +} + +static void runcmd(const char *cmd) +{ + if (empty_string(cmd)) + return; + + pthread_t pth; + pthread_create(&pth, NULL, cmd_thread, (void*)cmd); +} + +/**************************************************** +* Event list functions +****************************************************/ +//find an event by event_type +static event_t *get_event(const char *event_type) +{ + event_t *p = events; + + while (p != NULL) + { + if(!strcasecmp(p->event_type, event_type)) + return p; + + p = p->next; + } + + return NULL; +} + +// add event to the list +static event_t *add_event(unsigned int id) +{ + event_t *event; + + // allocate memory + if (!(event = (event_t *)malloc(sizeof(event_t)))) + quit(1, "malloc() failed in add_event()"); + + // set defaults + event->id = id; + event->event_type = ""; + event->runcmd = ""; + event->reboot = false; + event->reboot_delay = 0; + event->quit = false; + event->quit_msg = ""; + event->prev = event->next = NULL; + + // first event? + if(events == NULL) + { + events = event; + last_event = event; + } + // no, append to the list + else + { + last_event->next = event; + event->prev = last_event; + last_event = event; + } + + return event; +} + +// remove event from the list +static void remove_event(event_t *event) +{ + // only event? + if(event == events && event == last_event) + events = last_event = NULL; + // first event? + else if(event == events) + { + event->next->prev = NULL; + events = event->next; + } + // last event? + else if(event == last_event) + { + event->prev->next = NULL; + last_event = event->prev; + } + // in the middle + else + { + event->prev->next = event->next; + event->next->prev = event->prev; + } + + // free memory + free(event); +} + +#ifndef EVENT_ADD_CHECK + #define EVENT_ADD_CHECK if (!last_event || (last_event->id != json_array_index)) add_event(json_array_index); +#endif + +/******************************************** +* Config functions +*********************************************/ +char *set_event_type(const char *event_type) +{ + event_t *event; + + // make sure event type doesn't already exist + if ((event = get_event(event_type)) != NULL) + return NULL; + + EVENT_ADD_CHECK; + + last_event->event_type = event_type; + + return NULL; +} + +char *set_event_runcmd(const char *cmd) +{ + EVENT_ADD_CHECK; + + last_event->runcmd = cmd; + + return NULL; +} + +char *set_event_reboot(const char *arg) +{ + EVENT_ADD_CHECK; + + if (empty_string(arg)) + return NULL; + + last_event->reboot = strtobool(arg); + + applog(LOG_NOTICE, "Event %s reboot = %s", last_event->event_type, ((last_event->reboot)?"true":"false")); + + return NULL; +} + +char *set_event_reboot_delay(const char *delay) +{ + EVENT_ADD_CHECK; + + last_event->reboot_delay = atoi(delay); + + // if the reboot delay is greater than 0 seconds, automatically turn on reboot + if (last_event->reboot_delay > 0) + last_event->reboot = true; + + return NULL; +} + +char *set_event_quit(const char *arg) +{ + EVENT_ADD_CHECK; + + if (empty_string(arg)) + return NULL; + + last_event->quit = strtobool(arg); + + applog(LOG_DEBUG, "Event %s quit = %s", last_event->event_type, ((last_event->quit)?"true":"false")); + + return NULL; +} + +char *set_event_quit_message(const char *msg) +{ + EVENT_ADD_CHECK; + + last_event->quit_msg = msg; + + // if the quit message is set, automatically turn on quit + if (!empty_string(last_event->quit_msg)) + last_event->quit = true; + + return NULL; +} + +/****************************************** +* Event functions +*******************************************/ +void event_notify(const char *event_type) +{ + event_t *event; + + // find an event of the specified type + if ((event = get_event(event_type)) == NULL) + return; + + applog(LOG_DEBUG, "Executing event %s", event_type); + + // run command if defined + if (!empty_string(event->runcmd)) + runcmd(event->runcmd); + + // reboot if set + if (event->reboot == true) + { + //wait specified amount of time + if (event->reboot_delay > 0) + { + applog(LOG_NOTICE, "waiting %d to reboot", event->reboot_delay); + sleep(event->reboot_delay); + } + + #ifdef WIN32 + runcmd("shutdown /r /t 0"); + #else + applog(LOG_NOTICE, "running shutdown -r now"); + runcmd("/sbin/shutdown -r now"); + #endif + } + + // quit sgminer if set + if (event->quit == true) + quit(0, ((empty_string(event->quit_msg))?event_type:event->quit_msg)); + +} \ No newline at end of file diff --git a/events.h b/events.h new file mode 100644 index 00000000..b2b884e2 --- /dev/null +++ b/events.h @@ -0,0 +1,23 @@ +#ifndef EVENTS_H +#define EVENTS_H + +typedef struct event { + unsigned int id; + const char *event_type; + const char *runcmd; + bool reboot; + unsigned int reboot_delay; + bool quit; + const char *quit_msg; + struct event *prev, *next; +} event_t; + +extern char *set_event_type(const char *event_type); +extern char *set_event_runcmd(const char *cmd); +extern char *set_event_reboot(const char *arg); +extern char *set_event_reboot_delay(const char *delay); +extern char *set_event_quit(const char *arg); +extern char *set_event_quit_message(const char *msg); +extern void event_notify(const char *event_type); + +#endif /* EVENTS_H */ \ No newline at end of file diff --git a/sgminer.c b/sgminer.c index 0458ce33..089b43f6 100644 --- a/sgminer.c +++ b/sgminer.c @@ -59,6 +59,7 @@ char *curly = ":D"; #include "algorithm.h" #include "pool.h" #include "config_parser.h" +#include "events.h" #if defined(unix) || defined(__APPLE__) #include @@ -1378,6 +1379,27 @@ struct opt_table opt_config_table[] = { OPT_WITH_ARG("--expiry|-E", set_int_0_to_9999, opt_show_intval, &opt_expiry, "Upper bound on how many seconds after getting work we consider a share from it stale"), + + // event options + OPT_WITH_ARG("--event-on", + set_event_type, NULL, NULL, + "Select event type to perform task on"), + OPT_WITH_ARG("--event-runcmd", + set_event_runcmd, NULL, NULL, + "Command to perform on event"), + OPT_WITH_ARG("--event-reboot", + set_event_reboot, NULL, NULL, + "Reboot the system on event"), + OPT_WITH_ARG("--event-reboot-delay", + set_event_reboot_delay, NULL, NULL, + "Delay in seconds to wait before rebooting"), + OPT_WITH_ARG("--event-quit", + set_event_quit, NULL, NULL, + "Quit sgminer on event"), + OPT_WITH_ARG("--event-quit-message", + set_event_quit_message, NULL, NULL, + "Quit message when quitting sgminer on event"), + OPT_WITHOUT_ARG("--failover-only", opt_set_bool, &opt_fail_only, "Don't leak work to backup pools when primary pool is lagging"), @@ -1776,6 +1798,10 @@ struct opt_table opt_config_table[] = { opt_set_bool, NULL, NULL, opt_hidden), OPT_WITH_ARG("--profiles", opt_set_bool, NULL, NULL, opt_hidden), + OPT_WITH_ARG("--includes", + opt_set_bool, NULL, NULL, opt_hidden), + OPT_WITH_ARG("--events", + opt_set_bool, NULL, NULL, opt_hidden), OPT_WITH_ARG("--difficulty-multiplier", set_difficulty_multiplier, NULL, NULL, "(deprecated) Difficulty multiplier for jobs received from stratum pools"), @@ -5838,6 +5864,7 @@ static struct work *hash_pop(bool blocking) if (rc && !no_work) { no_work = true; applog(LOG_WARNING, "Waiting for work to be available from pools."); + event_notify("idle"); } } while (!HASH_COUNT(staged_work)); } @@ -7019,6 +7046,7 @@ static void hash_sole_work(struct thr_info *mythr) applog(LOG_ERR, "%s %d failure, disabling!", drv->name, cgpu->device_id); cgpu->deven = DEV_DISABLED; dev_error(cgpu, REASON_THREAD_ZERO_HASH); + event_notify("idle"); cgpu->shutdown = true; break; } @@ -7516,6 +7544,11 @@ static void *watchdog_thread(void __maybe_unused *userdata) cgtime(&now); + // check last getwork time if greater than 10 mins, declare idle... + if ((time(NULL) - last_getwork) >= 600) { + event_notify("idle"); + } + if (!sched_paused && !should_run()) { applog(LOG_WARNING, "Pausing execution as per stop time %02d:%02d scheduled", schedstop.tm.tm_hour, schedstop.tm.tm_min); @@ -7594,6 +7627,8 @@ static void *watchdog_thread(void __maybe_unused *userdata) cgtime(&thr->sick); dev_error(cgpu, REASON_DEV_SICK_IDLE_60); + event_notify("gpu_sick"); + #ifdef HAVE_ADL if (adl_active && cgpu->has_adl && gpu_activity(gpu) > 50) { applog(LOG_ERR, "GPU still showing activity suggesting a hard hang."); @@ -7610,6 +7645,7 @@ static void *watchdog_thread(void __maybe_unused *userdata) cgtime(&thr->sick); dev_error(cgpu, REASON_DEV_DEAD_IDLE_600); + event_notify("gpu_dead"); } else if (now.tv_sec - thr->sick.tv_sec > 60 && (cgpu->status == LIFE_SICK || cgpu->status == LIFE_DEAD)) { /* Attempt to restart a GPU that's sick or dead once every minute */ diff --git a/winbuild/sgminer.vcxproj b/winbuild/sgminer.vcxproj index 0728c416..abf80519 100644 --- a/winbuild/sgminer.vcxproj +++ b/winbuild/sgminer.vcxproj @@ -275,6 +275,7 @@ + @@ -335,6 +336,7 @@ + diff --git a/winbuild/sgminer.vcxproj.filters b/winbuild/sgminer.vcxproj.filters index 0ba37eab..3487079d 100644 --- a/winbuild/sgminer.vcxproj.filters +++ b/winbuild/sgminer.vcxproj.filters @@ -200,6 +200,9 @@ Source Files\algorithm + + Source Files + @@ -379,6 +382,9 @@ Header Files\algorithm + + Header Files +