Commit 90839cdf5ab27f41778ea80b937aa2bd249d4f81

Con Kolivas 2011-07-11T13:41:31

Implement a completely curses based display and don't output to stderr when log is enabled unless it's redirected away from the terminal.

diff --git a/Makefile.am b/Makefile.am
index c57383f..5fd9ebd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,7 +22,7 @@ cgminer_SOURCES	= elist.h miner.h compat.h			\
 		  sha256_cryptopp.c sha256_sse2_amd64.c
 
 cgminer_LDFLAGS	= $(PTHREAD_FLAGS)
-cgminer_LDADD	= @LIBCURL@ @JANSSON_LIBS@ @PTHREAD_LIBS@ @OPENCL_LIBS@ lib/libgnu.a ccan/libccan.a
+cgminer_LDADD	= @LIBCURL@ @JANSSON_LIBS@ @PTHREAD_LIBS@ @OPENCL_LIBS@ @NCURSES_LIBS@ lib/libgnu.a ccan/libccan.a
 cgminer_CPPFLAGS = @LIBCURL_CPPFLAGS@ -I$(top_builddir)/lib -I$(top_srcdir)/lib
 
 if HAVE_x86_64
diff --git a/README b/README
index 70eb612..38273d6 100644
--- a/README
+++ b/README
@@ -7,6 +7,7 @@ Dependencies:
 	libcurl			http://curl.haxx.se/libcurl/
 	jansson			http://www.digip.org/jansson/
 	(jansson is included in-tree and an installed one may conflict)
+	libncurses5-dev (or libpdcurses on WIN32)
 
 Basic *nix build instructions:
 	To build with GPU mining support:
diff --git a/configure.ac b/configure.ac
index aea69bd..04d9369 100644
--- a/configure.ac
+++ b/configure.ac
@@ -60,6 +60,7 @@ LIBS=$SAVED_LIBS
 
 AC_CHECK_LIB(jansson, json_loads, request_jansson=false, request_jansson=true)
 AC_CHECK_LIB(pthread, pthread_create, PTHREAD_LIBS=-lpthread)
+AC_CHECK_LIB(ncurses, addstr, NCURSES_LIBS=-lncurses)
 
 AM_CONDITIONAL([WANT_JANSSON], [test x$request_jansson = xtrue])
 AM_CONDITIONAL([HAVE_WINDOWS], [test x$have_win32 = xtrue])
@@ -148,6 +149,7 @@ AC_SUBST(OPENCL_LIBS)
 AC_SUBST(JANSSON_LIBS)
 AC_SUBST(PTHREAD_FLAGS)
 AC_SUBST(PTHREAD_LIBS)
+AC_SUBST(NCURSES_LIBS)
 
 AC_CONFIG_FILES([
 	Makefile
diff --git a/main.c b/main.c
index b260a85..b4c64ac 100644
--- a/main.c
+++ b/main.c
@@ -28,6 +28,7 @@
 #include <ccan/opt/opt.h>
 #include <jansson.h>
 #include <curl/curl.h>
+#include <curses.h>
 #include "compat.h"
 #include "miner.h"
 #include "findnonce.h"
@@ -507,13 +508,108 @@ err_out:
 	return false;
 }
 
-static double total_secs;
+static inline int gpu_from_thr_id(int thr_id)
+{
+	return thr_id % nDevs;
+}
+
+static inline int cpu_from_thr_id(int thr_id)
+{
+	return (thr_id - gpu_threads) % num_processors;
+}
+
+static WINDOW * mainwin;
+static double total_secs = 0.1;
 static char statusline[256];
+static int cpucursor, gpucursor, logstart, logcursor;
+static bool curses_active = false;
+static struct cgpu_info *gpus, *cpus;
 
-static inline void print_status(void)
+static inline void print_status(int thr_id)
 {
-	printf("%s\r", statusline);
-	fflush(stdout);
+	int x;
+
+	if (unlikely(!curses_active))
+		return;
+	getyx(mainwin, logcursor, x);
+
+	move(2,0);
+	printw("Totals: %s", statusline);
+	clrtoeol();
+
+	if (thr_id && thr_id < gpu_threads) {
+		int gpu = gpu_from_thr_id(thr_id);
+		struct cgpu_info *cgpu = &gpus[gpu];
+
+		move(gpucursor + gpu, 0);
+		printw("GPU %d: [%.1f Mh/s] [Q:%d  A:%d  R:%d  HW:%d  E:%.0f%%  U:%.2f/m]          ",
+			gpu, cgpu->total_mhashes / total_secs,
+			cgpu->getworks, cgpu->accepted, cgpu->rejected, cgpu->hw_errors,
+			cgpu->efficiency, cgpu->utility);
+		clrtoeol();
+	} else if (thr_id && thr_id >= gpu_threads) {
+		int cpu = cpu_from_thr_id(thr_id);
+		struct cgpu_info *cgpu = &cpus[cpu];
+
+		move(cpucursor + cpu, 0);
+		printw("CPU %d: [%.1f Mh/s] [Q:%d  A:%d  R:%d  HW:%d  E:%.0f%%  U:%.2f/m]",
+			cpu, cgpu->total_mhashes / total_secs,
+			cgpu->getworks, cgpu->accepted, cgpu->rejected, cgpu->hw_errors,
+			cgpu->efficiency, cgpu->utility);
+		clrtoeol();
+	}
+
+	move(logcursor, 0);
+	refresh();
+}
+
+static void refresh_display(void)
+{
+	int i, x, maxy;
+
+	if (unlikely(!curses_active))
+		return;
+	getyx(mainwin, logcursor, x);
+
+	move(0,0);
+	attron(A_BOLD);
+	printw(PROGRAM_NAME " version " VERSION);
+	attroff(A_BOLD);
+	clrtoeol();
+	move(1, 0);
+	clrtoeol();
+	hline('-', 80);
+	move(3, 0);
+	clrtoeol();
+	hline('-', 80);
+	move(logstart, 0);
+	clrtoeol();
+	hline('-', 80);
+	move(logcursor, 0);
+
+	for (i = 0; i < mining_threads; i++)
+		print_status(i);
+
+	move(logcursor, 0);
+	redrawwin(mainwin);
+}
+
+void log_curses(const char *f, va_list ap)
+{
+	int i, x, maxy;
+
+	if (unlikely(!curses_active))
+		return;
+	vwprintw(mainwin, f, ap);
+	clrtoeol();
+	getyx(mainwin, logcursor, x);
+
+	/* Scroll log output downwards */
+	getmaxyx(mainwin, maxy, x);
+	if (logcursor >= maxy - 1)
+		refresh_display();
+	else
+		refresh();
 }
 
 static bool submit_fail = false;
@@ -526,7 +622,6 @@ static bool submit_upstream_work(const struct work *work)
 	bool rc = false;
 	struct cgpu_info *cgpu = thr_info[work->thr_id].cgpu;
 	CURL *curl = curl_easy_init();
-	double utility, efficiency;
 
 	if (unlikely(!curl)) {
 		applog(LOG_ERR, "CURL initialisation failed");
@@ -573,29 +668,26 @@ static bool submit_upstream_work(const struct work *work)
 		if (opt_debug)
 			applog(LOG_DEBUG, "PROOF OF WORK RESULT: true (yay!!!)");
 		if (!opt_quiet)
-			printf("[Accepted] ");
+			applog(LOG_WARNING, "Share accepted from %sPU %d",
+				cgpu->is_gpu? "G" : "C", cgpu->cpu_gpu);
 	} else {
 		cgpu->rejected++;
 		rejected++;
 		if (opt_debug)
 			applog(LOG_DEBUG, "PROOF OF WORK RESULT: false (booooo)");
 		if (!opt_quiet)
-			printf("[Rejected] ");
+			applog(LOG_WARNING, "Share rejected from %sPU %d",
+				cgpu->is_gpu? "G" : "C", cgpu->cpu_gpu);
 	}
 
-	utility = cgpu->accepted / ( total_secs ? total_secs : 1 ) * 60;
-	efficiency = cgpu->getworks ? cgpu->accepted * 100.0 / cgpu->getworks : 0.0;
+	cgpu->utility = cgpu->accepted / ( total_secs ? total_secs : 1 ) * 60;
+	cgpu->efficiency = cgpu->getworks ? cgpu->accepted * 100.0 / cgpu->getworks : 0.0;
 
-	if (!opt_quiet) {
-		printf("[%sPU %d] [%.1f Mh/s] [Q:%d  A:%d  R:%d  HW:%d  E:%.0f%%  U:%.2f/m]                 \n",
-			cgpu->is_gpu? "G" : "C", cgpu->cpu_gpu, cgpu->total_mhashes / total_secs,
-			cgpu->getworks, cgpu->accepted, cgpu->rejected, cgpu->hw_errors,
-			efficiency, utility);
-		print_status();
-	}
+	if (!opt_quiet)
+		print_status(work->thr_id);
 	applog(LOG_INFO, "%sPU %d  Requested:%d  Accepted:%d  Rejected:%d  HW errors:%d  Efficiency:%.0f%%  Utility:%.2f/m",
-	       cgpu->is_gpu? "G" : "C", cgpu->cpu_gpu, cgpu->getworks, cgpu->accepted, cgpu->rejected, cgpu->hw_errors, efficiency, utility
-           );
+		cgpu->is_gpu? "G" : "C", cgpu->cpu_gpu, cgpu->getworks, cgpu->accepted,
+		cgpu->rejected, cgpu->hw_errors, cgpu->efficiency, cgpu->utility);
 
 	json_decref(val);
 
@@ -960,7 +1052,7 @@ static void hashmeter(int thr_id, struct timeval *diff,
 	sprintf(statusline, "[(%ds):%.1f  (avg):%.1f Mh/s] [Q:%d  A:%d  R:%d  HW:%d  E:%.0f%%  U:%.2f/m]          ",
 		opt_log_interval, rolling_local / local_secs, total_mhashes_done / total_secs,
 		getwork_requested, accepted, rejected, hw_errors, efficiency, utility);
-	print_status();
+	print_status(thr_id);
 	applog(LOG_INFO, "[Rate (%ds):%.1f  (avg):%.2f Mhash/s] [Requested:%d  Accepted:%d  Rejected:%d  HW errors:%d  Efficiency:%.0f%%  Utility:%.2f/m]",
 		opt_log_interval, rolling_local / local_secs, total_mhashes_done / total_secs,
 		getwork_requested, accepted, rejected, hw_errors, efficiency, utility);
@@ -1183,11 +1275,6 @@ bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce)
 	return submit_work_sync(thr, work);
 }
 
-static inline int cpu_from_thr_id(int thr_id)
-{
-	return (thr_id - gpu_threads) % num_processors;
-}
-
 static void *miner_thread(void *userdata)
 {
 	struct thr_info *mythr = userdata;
@@ -1404,11 +1491,6 @@ static inline cl_int queue_kernel_parameters(_clState *clState, dev_blk_ctx *blk
 	return status;
 }
 
-static inline int gpu_from_thr_id(int thr_id)
-{
-	return thr_id % nDevs;
-}
-
 static void *gpuminer_thread(void *userdata)
 {
 	const unsigned long cycle = opt_log_interval / 5 ? : 1;
@@ -1661,7 +1743,6 @@ int main (int argc, char *argv[])
 	struct thr_info *thr;
 	unsigned int i, j = 0;
 	char name[32];
-	struct cgpu_info *gpus = NULL, *cpus = NULL;
 
 	if (unlikely(pthread_mutex_init(&time_lock, NULL)))
 		return 1;
@@ -1726,7 +1807,12 @@ int main (int argc, char *argv[])
 		opt_n_threads = num_processors;
 	}
 
+	logcursor = 4;
 	mining_threads = opt_n_threads + gpu_threads;
+	gpucursor = logcursor;
+	cpucursor = gpucursor + total_devices + 1;
+	logstart = cpucursor + (opt_n_threads ? num_processors : 0);
+	logcursor = logstart + 1;
 
 	if (!rpc_userpass) {
 		if (!rpc_user || !rpc_pass) {
@@ -1907,6 +1993,17 @@ int main (int argc, char *argv[])
 	total_mhashes_done = 0;
 	pthread_mutex_unlock(&hash_lock);
 
+	/* Set up the ncurses interface */
+	if ((mainwin = initscr()) == NULL) {
+		applog(LOG_ERR, "Failed to initscr");
+		return 1;
+	}
+	idlok(mainwin, true);
+	scrollok(mainwin, true);
+	curses_active = true;
+	move(logcursor, 0);
+	refresh_display();
+
 	/* main loop - simply wait for workio thread to exit */
 	pthread_join(thr_info[work_thr_id].pth, NULL);
 	curl_global_cleanup();
@@ -1917,6 +2014,10 @@ int main (int argc, char *argv[])
 
 	applog(LOG_INFO, "workio thread dead, exiting.");
 
+	delwin(mainwin);
+	endwin();
+	refresh();
+
 	return 0;
 }
 
diff --git a/miner.h b/miner.h
index a83553f..1d4e1b2 100644
--- a/miner.h
+++ b/miner.h
@@ -135,6 +135,8 @@ struct cgpu_info {
 	double local_mhashes;
 	double total_mhashes;
 	unsigned int getworks;
+	double efficiency;
+	double utility;
 };
 
 struct thr_info {
@@ -267,6 +269,7 @@ struct work {
 
 bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce);
 
+extern void log_curses(const char *f, va_list ap);
 extern void vapplog(int prio, const char *fmt, va_list ap);
 extern void applog(int prio, const char *fmt, ...);
 extern struct thread_q *tq_new(void);
diff --git a/util.c b/util.c
index 04f23b8..9077754 100644
--- a/util.c
+++ b/util.c
@@ -20,6 +20,7 @@
 #include <jansson.h>
 #include <curl/curl.h>
 #include <time.h>
+#include <curses.h>
 #include "miner.h"
 #include "elist.h"
 
@@ -68,7 +69,7 @@ void vapplog(int prio, const char *fmt, va_list ap)
 #endif
 	else if (opt_log_output || prio == LOG_WARNING || prio == LOG_ERR) {
 		char *f;
-		int len, i, extra = 0;
+		int len;
 		struct timeval tv = { };
 		struct tm tm, *tm_p;
 
@@ -80,10 +81,8 @@ void vapplog(int prio, const char *fmt, va_list ap)
 		pthread_mutex_unlock(&time_lock);
 
 		len = 40 + strlen(fmt) + 2;
-		if (len < 80)
-			extra = 80 - len;
-		f = alloca(len + extra);
-		sprintf(f, "[%d-%02d-%02d %02d:%02d:%02d] %s",
+		f = alloca(len);
+		sprintf(f, "[%d-%02d-%02d %02d:%02d:%02d] %s\n",
 			tm.tm_year + 1900,
 			tm.tm_mon + 1,
 			tm.tm_mday,
@@ -91,11 +90,12 @@ void vapplog(int prio, const char *fmt, va_list ap)
 			tm.tm_min,
 			tm.tm_sec,
 			fmt);
-		vfprintf(stderr, f, ap);	/* atomic write to stderr */
-		for (i = 0; i < extra; i++)
-			fprintf(stderr, " ");
-		fprintf(stderr, "\n");
-		fflush(stderr);
+		/* Only output to stderr if it's not going to the screen as well */
+		if (opt_log_output && !isatty(fileno((FILE *)stderr))) {
+			vfprintf(stderr, f, ap);	/* atomic write to stderr */
+			fflush(stderr);
+		}
+		log_curses(f, ap);
 	}
 }