Browse Source

Merge branch 'kanoi_cherry_20130120a' into bfgminer

Luke Dashjr 13 years ago
parent
commit
9a86ac8e6a
7 changed files with 444 additions and 95 deletions
  1. 54 0
      API-README
  2. 1 1
      README
  3. 77 4
      api.c
  4. 38 15
      miner.c
  5. 6 0
      miner.h
  6. 236 51
      miner.php
  7. 32 24
      util.c

+ 54 - 0
API-README

@@ -346,6 +346,18 @@ The list of requests - a (*) means it requires privileged access - and replies a
                               The current options are:
                                MMQ opt=clock val=2 to 230 (and a multiple of 2)
 
+ zero|Which,true/false (*)
+               none           There is no reply section just the STATUS section
+                              stating that the zero, and optional summary, was done
+                              If Which='all', all normal cgminer and API statistics
+                              will be zeroed other than the numbers displayed by the
+                              usbstats and stats commands
+                              If Which='bestshare', only the 'Best Share' values
+                              are zeroed for each pool and the global 'Best Share'
+                              The true/false option determines if a full summary is
+                              shown on the cgminer display like is normally displayed
+                              on exit.
+
 When you enable, disable or restart a GPU or PGA, you will also get Thread messages
 in the BFGMiner status window
 
@@ -398,6 +410,16 @@ miner.php - an example web page to access the API
 Feature Changelog for external applications using the API:
 
 
+API V1.24
+
+Added API commands:
+ 'zero'
+
+Modified API commands:
+ 'pools' - add 'Best Share'
+
+----------
+
 API V1.23 (BFGMiner v2.10.1)
 
 Added API commands:
@@ -954,6 +976,38 @@ true
 
 ---------
 
+Default:
+ $userlist = null;
+
+Define password checking and default access
+ null means there is no password checking
+
+$userlist is an array of 3 arrays e.g.
+$userlist = array('sys' => array('boss' => 'bpass'),
+                  'usr' => array('user' => 'upass', 'pleb' => 'ppass'),
+                  'def' => array('Pools'));
+
+'sys' is an array of system users and passwords (full access)
+'usr' is an array of user level users and passwords (readonly access)
+'def' is an array of custompages that anyone not logged in can view
+
+Any of the 3 can be null, meaning there are none of that item
+
+All validated 'usr' users are given $readonly = true; access
+All validated 'sys' users are given the $readonly access you defined
+
+If 'def' has one or more values, and allowcustompages is true, then
+anyone without a password can see the list of custompage buttons given
+in 'def' and will see the first one when they go to the web page, with
+a login button at the top right
+
+From the login page, if you login with no username or password, it will
+show the first 'def' custompage (if there are any)
+
+If you are logged in, it will show a logout button at the top right
+
+---------
+
 Default:
  $notify = true;
 

+ 1 - 1
README

@@ -461,7 +461,7 @@ The block display shows:
 Block: 0074c5e482e34a50...  Diff:2.98M  Started: [17:17:22]  Best share: 2.71K
 
 This shows a short stretch of the current block, when the new block started,
-and the all time best difficulty share you've submitted since starting BFGMiner
+and the all time best difficulty share you've found since starting BFGMiner
 this time.
 
 

+ 77 - 4
api.c

@@ -133,7 +133,7 @@ static const char SEPARATOR = '|';
 #define SEPSTR "|"
 static const char GPUSEP = ',';
 
-static const char *APIVERSION = "1.23";
+static const char *APIVERSION = "1.24";
 static const char *DEAD = "Dead";
 static const char *SICK = "Sick";
 static const char *NOSTART = "NoStart";
@@ -388,6 +388,11 @@ static const char *JSON_PARAMETER = "parameter";
 #define MSG_PGASETERR 93
 #endif
 
+#define MSG_ZERMIS 94
+#define MSG_ZERINV 95
+#define MSG_ZERSUM 96
+#define MSG_ZERNOSUM 97
+
 enum code_severity {
 	SEVERITY_ERR,
 	SEVERITY_WARN,
@@ -558,6 +563,10 @@ struct CODES {
  { SEVERITY_SUCC,  MSG_PGASETOK, PARAM_BOTH,	"PGA %d set OK" },
  { SEVERITY_ERR,   MSG_PGASETERR, PARAM_BOTH,	"PGA %d set failed: %s" },
 #endif
+ { SEVERITY_ERR,   MSG_ZERMIS,	PARAM_NONE,	"Missing zero parameters" },
+ { SEVERITY_ERR,   MSG_ZERINV,	PARAM_STR,	"Invalid zero parameter '%s'" },
+ { SEVERITY_SUCC,  MSG_ZERSUM,	PARAM_STR,	"Zeroed %s stats with summary" },
+ { SEVERITY_SUCC,  MSG_ZERNOSUM, PARAM_STR,	"Zeroed %s stats without summary" },
  { SEVERITY_FAIL, 0, 0, NULL }
 };
 
@@ -1691,6 +1700,9 @@ static void pgaenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char
 
 	struct cgpu_info *cgpu = devices[dev];
 
+	applog(LOG_DEBUG, "API: request to pgaenable pgaid %d device %d %s%u",
+			id, dev, cgpu->api->name, cgpu->device_id);
+
 	if (cgpu->deven != DEV_DISABLED) {
 		message(io_data, MSG_PGALRENA, id, NULL, isjson);
 		return;
@@ -1704,10 +1716,11 @@ static void pgaenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char
 #endif
 
 	for (i = 0; i < mining_threads; i++) {
-		pga = thr_info[i].cgpu->device_id;
+		pga = thr_info[i].cgpu->cgminer_id;
 		if (pga == dev) {
 			thr = &thr_info[i];
 			cgpu->deven = DEV_ENABLED;
+			applog(LOG_DEBUG, "API: pushing ping (%d) to thread %d", ping, thr->id);
 			tq_push(thr->q, &ping);
 		}
 	}
@@ -1744,6 +1757,9 @@ static void pgadisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, cha
 
 	struct cgpu_info *cgpu = devices[dev];
 
+	applog(LOG_DEBUG, "API: request to pgadisable pgaid %d device %d %s%u",
+			id, dev, cgpu->api->name, cgpu->device_id);
+
 	if (cgpu->deven == DEV_DISABLED) {
 		message(io_data, MSG_PGALRDIS, id, NULL, isjson);
 		return;
@@ -1902,6 +1918,7 @@ static void poolstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __m
 			root = api_add_escape(root, "Stratum URL", pool->stratum_url, false);
 		else
 			root = api_add_const(root, "Stratum URL", BLANK, false);
+		root = api_add_uint64(root, "Best Share", &(pool->best_diff), true);
 
 		root = print_data(root, buf, isjson, isjson && (i > 0));
 		io_add(io_data, buf);
@@ -1991,6 +2008,9 @@ static void gpuenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char
 		return;
 	}
 
+	applog(LOG_DEBUG, "API: request to gpuenable gpuid %d %s%u",
+			id, gpus[id].api->name, gpus[id].device_id);
+
 	if (gpus[id].deven != DEV_DISABLED) {
 		message(io_data, MSG_ALRENA, id, NULL, isjson);
 		return;
@@ -2004,10 +2024,9 @@ static void gpuenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char
 				message(io_data, MSG_GPUMRE, id, NULL, isjson);
 				return;
 			}
-
 			gpus[id].deven = DEV_ENABLED;
+			applog(LOG_DEBUG, "API: pushing ping (%d) to thread %d", ping, thr->id);
 			tq_push(thr->q, &ping);
-
 		}
 	}
 
@@ -2034,6 +2053,9 @@ static void gpudisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, cha
 		return;
 	}
 
+	applog(LOG_DEBUG, "API: request to gpudisable gpuid %d %s%u",
+			id, gpus[id].api->name, gpus[id].device_id);
+
 	if (gpus[id].deven == DEV_DISABLED) {
 		message(io_data, MSG_ALRDIS, id, NULL, isjson);
 		return;
@@ -2761,6 +2783,8 @@ static int itemstats(struct io_data *io_data, int i, char *id, struct cgminer_st
 		root = api_add_uint64(root, "Bytes Sent", &(pool_stats->bytes_sent), false);
 		root = api_add_uint64(root, "Times Recv", &(pool_stats->times_received), false);
 		root = api_add_uint64(root, "Bytes Recv", &(pool_stats->bytes_received), false);
+		root = api_add_uint64(root, "Net Bytes Sent", &(pool_stats->net_bytes_sent), false);
+		root = api_add_uint64(root, "Net Bytes Recv", &(pool_stats->net_bytes_received), false);
 	}
 
 	if (extra)
@@ -3048,6 +3072,54 @@ static void pgaset(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe
 }
 #endif
 
+static void dozero(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
+{
+	if (param == NULL || *param == '\0') {
+		message(io_data, MSG_ZERMIS, 0, NULL, isjson);
+		return;
+	}
+
+	char *sum = strchr(param, ',');
+	if (sum)
+		*(sum++) = '\0';
+	if (!sum || !*sum) {
+		message(io_data, MSG_MISBOOL, 0, NULL, isjson);
+		return;
+	}
+
+	bool all = false;
+	bool bs = false;
+	if (strcasecmp(param, "all") == 0)
+		all = true;
+	else if (strcasecmp(param, "bestshare") == 0)
+		bs = true;
+
+	if (all == false && bs == false) {
+		message(io_data, MSG_ZERINV, 0, param, isjson);
+		return;
+	}
+
+	*sum = tolower(*sum);
+	if (*sum != 't' && *sum != 'f') {
+		message(io_data, MSG_INVBOOL, 0, NULL, isjson);
+		return;
+	}
+
+	bool dosum = (*sum == 't');
+	if (dosum)
+		print_summary();
+
+	if (all)
+		zero_stats();
+	if (bs)
+		zero_bestshare();
+
+	if (dosum)
+		message(io_data, MSG_ZERSUM, 0, all ? "All" : "BestShare", isjson);
+	else
+		message(io_data, MSG_ZERNOSUM, 0, all ? "All" : "BestShare", isjson);
+}
+
 static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group);
 
 struct CMDS {
@@ -3107,6 +3179,7 @@ struct CMDS {
 #ifdef HAVE_AN_FPGA
 	{ "pgaset",		pgaset,		true },
 #endif
+	{ "zero",		dozero,		true },
 	{ NULL,			NULL,		false }
 };
 

+ 38 - 15
miner.c

@@ -2566,6 +2566,8 @@ static uint64_t share_diff(const struct work *work)
 		best_diff = ret;
 		suffix_string(best_diff, best_share, 0);
 	}
+	if (ret > work->pool->best_diff)
+		work->pool->best_diff = ret;
 	mutex_unlock(&control_lock);
 	return ret;
 }
@@ -2766,7 +2768,7 @@ static inline struct pool *select_pool(bool lagging)
 	return pool;
 }
 
-static double DIFFEXACTONE = 26959946667150639794667015087019630673637144422540572481103610249216.0;
+static double DIFFEXACTONE = 26959946667150639794667015087019630673637144422540572481103610249215.0;
 
 /*
  * Calculate the work share difficulty
@@ -3025,8 +3027,6 @@ static void disable_curses(void)
 }
 #endif
 
-static void print_summary(void);
-
 static void __kill_work(void)
 {
 	struct thr_info *thr;
@@ -4469,7 +4469,7 @@ static void display_pool_summary(struct pool *pool)
 		wlog(" Rejected difficulty shares: %1.f\n", pool->diff_rejected);
 		if (pool->accepted || pool->rejected)
 			wlog(" Reject ratio: %.1f%%\n", (double)(pool->rejected * 100) / (double)(pool->accepted + pool->rejected));
-		uint64_t pool_bytes_xfer = pool->cgminer_pool_stats.bytes_received + pool->cgminer_pool_stats.bytes_sent;
+		uint64_t pool_bytes_xfer = pool->cgminer_pool_stats.net_bytes_received + pool->cgminer_pool_stats.net_bytes_sent;
 		efficiency = pool_bytes_xfer ? pool->diff_accepted * 2048. / pool_bytes_xfer : 0.0;
 		wlog(" Efficiency (accepted * difficulty / 2 KB): %.2f\n", efficiency);
 
@@ -4735,6 +4735,20 @@ void write_config(FILE *fcfg)
 	json_escape_free();
 }
 
+void zero_bestshare(void)
+{
+	int i;
+
+	best_diff = 0;
+	memset(best_share, 0, 8);
+	suffix_string(best_diff, best_share, 0);
+
+	for (i = 0; i < total_pools; i++) {
+		struct pool *pool = pools[i];
+		pool->best_diff = 0;
+	}
+}
+
 void zero_stats(void)
 {
 	int i;
@@ -4749,17 +4763,16 @@ void zero_stats(void)
 	total_stale = 0;
 	total_discarded = 0;
 	total_bytes_xfer = 0;
-	total_diff_accepted = total_diff_rejected = total_diff_stale = 0;
 	new_blocks = 0;
-	found_blocks = 0;
 	local_work = 0;
 	total_go = 0;
 	total_ro = 0;
 	total_secs = 1.0;
-	best_diff = 0;
 	total_diff1 = 0;
-	memset(best_share, 0, 8);
-	suffix_string(best_diff, best_share, 0);
+	found_blocks = 0;
+	total_diff_accepted = 0;
+	total_diff_rejected = 0;
+	total_diff_stale = 0;
 
 	for (i = 0; i < total_pools; i++) {
 		struct pool *pool = pools[i];
@@ -4768,13 +4781,17 @@ void zero_stats(void)
 		pool->accepted = 0;
 		pool->rejected = 0;
 		pool->solved = 0;
-		pool->diff1 = 0;
-		pool->diff_accepted = pool->diff_rejected = pool->diff_stale = 0;
 		pool->getwork_requested = 0;
 		pool->stale_shares = 0;
 		pool->discarded_work = 0;
 		pool->getfail_occasions = 0;
 		pool->remotefail_occasions = 0;
+		pool->last_share_time = 0;
+		pool->diff1 = 0;
+		pool->diff_accepted = 0;
+		pool->diff_rejected = 0;
+		pool->diff_stale = 0;
+		pool->last_share_diff = 0;
 		pool->cgminer_stats.getwork_calls = 0;
 		pool->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET;
 		pool->cgminer_stats.getwork_wait_max.tv_sec = 0;
@@ -4790,10 +4807,13 @@ void zero_stats(void)
 		pool->cgminer_pool_stats.max_diff_count = 0;
 		pool->cgminer_pool_stats.times_sent = 0;
 		pool->cgminer_pool_stats.bytes_sent = 0;
+		pool->cgminer_pool_stats.net_bytes_sent = 0;
 		pool->cgminer_pool_stats.times_received = 0;
-		pool->cgminer_pool_stats.times_received = 0;
+		pool->cgminer_pool_stats.net_bytes_received = 0;
 	}
 
+	zero_bestshare();
+
 	mutex_lock(&hash_lock);
 	for (i = 0; i < total_devices; ++i) {
 		struct cgpu_info *cgpu = devices[i];
@@ -4804,8 +4824,11 @@ void zero_stats(void)
 		cgpu->hw_errors = 0;
 		cgpu->utility = 0.0;
 		cgpu->utility_diff1 = 0;
+		cgpu->last_share_pool_time = 0;
 		cgpu->diff1 = 0;
-		cgpu->diff_accepted = cgpu->diff_rejected = 0;
+		cgpu->diff_accepted = 0;
+		cgpu->diff_rejected = 0;
+		cgpu->last_share_diff = 0;
 		cgpu->thread_fail_init_count = 0;
 		cgpu->thread_zero_hash_count = 0;
 		cgpu->thread_fail_queue_count = 0;
@@ -7129,7 +7152,7 @@ static void log_print_status(struct cgpu_info *cgpu)
 	applog(LOG_WARNING, "%s", logline);
 }
 
-static void print_summary(void)
+void print_summary(void)
 {
 	struct timeval diff;
 	int hours, mins, secs, i;
@@ -7190,7 +7213,7 @@ static void print_summary(void)
 			applog(LOG_WARNING, " Rejected difficulty shares: %1.f", pool->diff_rejected);
 			if (pool->accepted || pool->rejected)
 				applog(LOG_WARNING, " Reject ratio: %.1f%%", (double)(pool->rejected * 100) / (double)(pool->accepted + pool->rejected));
-			uint64_t pool_bytes_xfer = pool->cgminer_pool_stats.bytes_received + pool->cgminer_pool_stats.bytes_sent;
+			uint64_t pool_bytes_xfer = pool->cgminer_pool_stats.net_bytes_received + pool->cgminer_pool_stats.net_bytes_sent;
 			efficiency = pool_bytes_xfer ? pool->diff_accepted * 2048. / pool_bytes_xfer : 0.0;
 			applog(LOG_WARNING, " Efficiency (accepted * difficulty / 2 KB): %.2f", efficiency);
 

+ 6 - 0
miner.h

@@ -390,8 +390,10 @@ struct cgminer_pool_stats {
 	uint32_t max_diff_count;
 	uint64_t times_sent;
 	uint64_t bytes_sent;
+	uint64_t net_bytes_sent;
 	uint64_t times_received;
 	uint64_t bytes_received;
+	uint64_t net_bytes_received;
 };
 
 struct cgpu_info {
@@ -768,6 +770,7 @@ extern void api(int thr_id);
 extern struct pool *current_pool(void);
 extern int enabled_pools;
 extern bool detect_stratum(struct pool *pool, char *url);
+extern void print_summary(void);
 extern struct pool *add_pool(void);
 extern void add_pool_details(struct pool *pool, bool live, char *url, char *user, char *pass);
 
@@ -969,6 +972,7 @@ struct pool {
 
 	time_t last_share_time;
 	double last_share_diff;
+	uint64_t best_diff;
 
 	struct cgminer_stats cgminer_stats;
 	struct cgminer_pool_stats cgminer_pool_stats;
@@ -1076,6 +1080,8 @@ extern void validate_pool_priorities(void);
 extern void switch_pools(struct pool *selected);
 extern void remove_pool(struct pool *pool);
 extern void write_config(FILE *fcfg);
+extern void zero_bestshare(void);
+extern void zero_stats(void);
 extern void default_save_file(char *filename);
 extern bool log_curses_only(int prio, const char *f, va_list ap) FORMAT_SYNTAX_CHECK(printf, 2, 0);
 extern void clear_logwin(void);

+ 236 - 51
miner.php

@@ -8,7 +8,7 @@ global $checklastshare, $poolinputs, $hidefields;
 global $ignorerefresh, $changerefresh, $autorefresh;
 global $allowcustompages, $customsummarypages;
 global $miner_font_family, $miner_font_size;
-global $colouroverride, $placebuttons;
+global $colouroverride, $placebuttons, $userlist;
 #
 # See API-README for more details of these variables and how
 # to configure miner.php
@@ -20,6 +20,9 @@ $title = 'Mine';
 # Set $readonly to false then it will check BFGMiner 'privileged'
 $readonly = false;
 #
+# Set $userlist to null to allow anyone access or read API-README
+$userlist = null;
+#
 # Set $notify to false to NOT attempt to display the notify command
 # Set $notify to true to attempt to display the notify command
 $notify = true;
@@ -110,23 +113,23 @@ $poolspage = array(
 			'Utility', 'Hardware Errors=HW Errs', 'Network Blocks=Net Blks'),
  'POOL+STATS' => array('STATS.ID=ID', 'POOL.URL=URL',
 			'POOL.Has Stratum=Stratum', 'POOL.Stratum Active=StrAct',
-			'STATS.Bytes Sent=BSent',
-			'STATS.Bytes Recv=BRecv'));
+			'STATS.Net Bytes Sent=NSent',
+			'STATS.Net Bytes Recv=NRecv'));
 #
 $poolssum = array(
  'SUMMARY' => array('MHS av', 'Found Blocks', 'Accepted',
 			'Rejected', 'Utility', 'Hardware Errors'),
  'POOL+STATS' => array(
-			'STATS.Bytes Sent',
-			'STATS.Bytes Recv'));
+			'STATS.Net Bytes Sent',
+			'STATS.Net Bytes Recv'));
 #
 $poolsext = array(
  'POOL+STATS' => array(
 	'where' => null,
 	'group' => array('POOL.URL', 'POOL.Has Stratum', 'POOL.Stratum Active'),
 	'calc' => array(
-			'STATS.Bytes Sent' => 'sum',
-			'STATS.Bytes Recv' => 'sum'),
+			'STATS.Net Bytes Sent' => 'sum',
+			'STATS.Net Bytes Recv' => 'sum'),
 	'having' => array(array('STATS.Bytes Recv', '>', 0)))
 );
 
@@ -208,6 +211,10 @@ $rigerror = array();
 global $rownum;
 $rownum = 0;
 #
+// Login
+global $ses;
+$ses = 'rutroh';
+#
 function getcss($cssname, $dom = false)
 {
  global $colourtable, $colouroverride;
@@ -235,7 +242,7 @@ function getdom($domname)
  return getcss($domname, true);
 }
 #
-function htmlhead($checkapi, $rig, $pg = null)
+function htmlhead($checkapi, $rig, $pg = null, $noscript = false)
 {
  global $title, $miner_font_family, $miner_font_size;
  global $error, $readonly, $poolinputs, $here;
@@ -281,8 +288,10 @@ td.lst { $miner_font ".getcss('td.lst')."}
 td.hi { $miner_font ".getcss('td.hi')."}
 td.lo { $miner_font ".getcss('td.lo')."}
 </style>
-</head><body".getdom('body').">
-<script type='text/javascript'>
+</head><body".getdom('body').">\n";
+if ($noscript === false)
+{
+echo "<script type='text/javascript'>
 function pr(a,m){if(m!=null){if(!confirm(m+'?'))return}window.location='$here?ref=$autorefresh'+a}\n";
 
 if ($ignorerefresh == false)
@@ -296,8 +305,9 @@ function prs2(a,n,r){var v=document.getElementById('gi'+n).value;var c=a.substr(
 	if ($poolinputs === true)
 		echo "function cbs(s){var t=s.replace(/\\\\/g,'\\\\\\\\'); return t.replace(/,/g, '\\\\,')}\nfunction pla(r){var u=document.getElementById('purl').value;var w=document.getElementById('pwork').value;var p=document.getElementById('ppass').value;pr('&rig='+r+'&arg=addpool|'+cbs(u)+','+cbs(w)+','+cbs(p),'Add Pool '+u)}\nfunction psp(r){var p=document.getElementById('prio').value;pr('&rig='+r+'&arg=poolpriority|'+p,'Set Pool Priorities to '+p)}\n";
  }
+echo "</script>\n";
+}
 ?>
-</script>
 <table width=100% height=100% border=0 cellpadding=0 cellspacing=0 summary='Mine'>
 <tr><td align=center valign=top>
 <table border=0 cellpadding=4 cellspacing=0 summary='Mine'>
@@ -574,6 +584,14 @@ function classlastshare($when, $alldata, $warnclass, $errorclass)
  return '';
 }
 #
+function endzero($num)
+{
+ $rep = preg_replace('/0*$/', '', $num);
+ if ($rep === '')
+	$rep = '0';
+ return $rep;
+}
+#
 function fmt($section, $name, $value, $when, $alldata)
 {
  global $dfmt, $rownum;
@@ -832,12 +850,16 @@ function fmt($section, $name, $value, $when, $alldata)
 	case 'total.Diff1 Work':
 	case 'STATS.Times Sent':
 	case 'STATS.Bytes Sent':
+	case 'STATS.Net Bytes Sent':
 	case 'STATS.Times Recv':
 	case 'STATS.Bytes Recv':
+	case 'STATS.Net Bytes Recv':
 	case 'total.Times Sent':
 	case 'total.Bytes Sent':
+	case 'total.Net Bytes Sent':
 	case 'total.Times Recv':
 	case 'total.Bytes Recv':
+	case 'total.Net Bytes Recv':
 		$parts = explode('.', $value, 2);
 		if (count($parts) == 1)
 			$dec = '';
@@ -845,6 +867,23 @@ function fmt($section, $name, $value, $when, $alldata)
 			$dec = '.'.$parts[1];
 		$ret = number_format((float)$parts[0]).$dec;
 		break;
+	case 'STATS.Hs':
+	case 'STATS.W':
+	case 'STATS.history_time':
+	case 'STATS.Pool Wait':
+	case 'STATS.Pool Max':
+	case 'STATS.Pool Min':
+	case 'STATS.Pool Av':
+	case 'STATS.Min Diff':
+	case 'STATS.Max Diff':
+	case 'STATS.Work Diff':
+		$parts = explode('.', $value, 2);
+		if (count($parts) == 1)
+			$dec = '';
+		else
+			$dec = '.'.endzero($parts[1]);
+		$ret = number_format((float)$parts[0]).$dec;
+		break;
 	case 'GPU.Status':
 	case 'PGA.Status':
 	case 'DEVS.Status':
@@ -1495,7 +1534,6 @@ function doforeach($cmd, $des, $sum, $head, $datetime)
 #
 function refreshbuttons()
 {
- global $readonly;
  global $ignorerefresh, $changerefresh, $autorefresh;
 
  if ($ignorerefresh == false && $changerefresh == true)
@@ -1509,7 +1547,7 @@ function refreshbuttons()
 #
 function pagebuttons($rig, $pg)
 {
- global $readonly, $rigs;
+ global $readonly, $rigs, $userlist, $ses;
  global $allowcustompages, $customsummarypages;
 
  if ($rig === null)
@@ -1545,18 +1583,33 @@ function pagebuttons($rig, $pg)
  }
 
  echo '<tr><td><table cellpadding=0 cellspacing=0 border=0><tr><td nowrap>';
- if ($prev !== null)
-	echo riginput($prev, 'Prev').'&nbsp;';
- echo "<input type=button value='Refresh' onclick='pr(\"$refresh\",null)'>&nbsp;";
- if ($next !== null)
-	echo riginput($next, 'Next').'&nbsp;';
- echo '&nbsp;';
- if (count($rigs) > 1)
-	echo "<input type=button value='Summary' onclick='pr(\"\",null)'>&nbsp;";
+ if ($userlist === null || isset($_SESSION[$ses]))
+ {
+	if ($prev !== null)
+		echo riginput($prev, 'Prev').'&nbsp;';
+	echo "<input type=button value='Refresh' onclick='pr(\"$refresh\",null)'>&nbsp;";
+	if ($next !== null)
+		echo riginput($next, 'Next').'&nbsp;';
+	echo '&nbsp;';
+	if (count($rigs) > 1)
+		echo "<input type=button value='Summary' onclick='pr(\"\",null)'>&nbsp;";
+ }
 
  if ($allowcustompages === true)
-	foreach ($customsummarypages as $pagename => $data)
+ {
+	if ($userlist === null || isset($_SESSION[$ses]))
+		$list = $customsummarypages;
+	else
+	{
+		if ($userlist !== null && isset($userlist['def']))
+			$list = array_flip($userlist['def']);
+		else
+			$list = array();
+	}
+
+	foreach ($list as $pagename => $data)
 		echo "<input type=button value='$pagename' onclick='pr(\"&pg=$pagename\",null)'>&nbsp;";
+ }
 
  echo '</td><td width=100%>&nbsp;</td><td nowrap>';
  if ($rig !== null && $readonly === false)
@@ -1568,6 +1621,12 @@ function pagebuttons($rig, $pg)
 	echo "&nbsp;<input type=button value='Quit' onclick='prc(\"quit&rig=$rig\",\"Quit BFGMiner$rg\")'>";
  }
  refreshbuttons();
+ if (isset($_SESSION[$ses]))
+	echo "&nbsp;<input type=button value='Logout' onclick='pr(\"&logout=1\",null)'>";
+ else
+	if ($userlist !== null)
+		echo "&nbsp;<input type=button value='Login' onclick='pr(\"&login=1\",null)'>";
+
  echo "</td></tr></table></td></tr>";
 }
 #
@@ -2397,13 +2456,126 @@ function showcustompage($pagename)
 	pagebuttons(null, $pagename);
 }
 #
+function onlylogin()
+{
+ global $here;
+
+ htmlhead(false, null, null, true);
+
+?>
+<tr height=15%><td>&nbsp;</td></tr>
+<tr><td>
+ <center>
+  <table width=384 height=368 cellpadding=0 cellspacing=0 border=0>
+   <tr><td>
+    <table width=100% height=100% border=0 align=center cellpadding=5 cellspacing=0>
+     <tr><td><form action='<?php echo $here; ?>' method=post>
+      <table width=200 border=0 align=center cellpadding=5 cellspacing=0>
+       <tr><td height=120 colspan=2>&nbsp;</td></tr>
+       <tr><td colspan=2 align=center valign=middle>
+        <h2>LOGIN</h2></td></tr>
+       <tr><td align=center valign=middle><div align=right>Username:</div></td>
+        <td height=33 align=center valign=middle>
+        <input type=text name=rut size=18></td></tr>
+       <tr><td align=center valign=middle><div align=right>Password:</div></td>
+        <td height=33 align=center valign=middle>
+        <input type=password name=roh size=18></td></tr>
+       <tr valign=top><td></td><td><input type=submit value=Login>
+        </td></tr>
+      </table></form></td></tr>
+    </table></td></tr>
+  </table></center>
+</td></tr>
+<?php
+}
+#
+function checklogin()
+{
+ global $allowcustompages, $customsummarypages;
+ global $readonly, $userlist, $ses;
+
+ $out = trim(getparam('logout', true));
+ if ($out !== null && $out !== '' && isset($_SESSION[$ses]))
+	unset($_SESSION[$ses]);
+
+ $login = trim(getparam('login', true));
+ if ($login !== null && $login !== '')
+ {
+	if (isset($_SESSION[$ses]))
+		unset($_SESSION[$ses]);
+
+	onlylogin();
+	return 'login';
+ }
+
+ if ($userlist === null)
+	return false;
+
+ $rut = trim(getparam('rut', true));
+ $roh = trim(getparam('roh', true));
+
+ if (($rut !== null && $rut !== '') || ($roh !== null && $roh !== ''))
+ {
+	if (isset($_SESSION[$ses]))
+		unset($_SESSION[$ses]);
+
+	if ($rut !== null && $rut !== '' && $roh !== null && $roh !== '')
+	{
+		if (isset($userlist['sys']) && isset($userlist['sys'][$rut])
+		&&  ($userlist['sys'][$rut] === $roh))
+		{
+			$_SESSION[$ses] = true;
+			return false;
+		}
+
+		if (isset($userlist['usr']) && isset($userlist['usr'][$rut])
+		&&  ($userlist['usr'][$rut] === $roh))
+		{
+			$_SESSION[$ses] = false;
+			$readonly = true;
+			return false;
+		}
+	}
+ }
+
+ if (isset($_SESSION[$ses]))
+ {
+	if ($_SESSION[$ses] == false)
+		$readonly = true;
+	return false;
+ }
+
+ if (isset($userlist['def']) && $allowcustompages === true)
+ {
+	// Ensure at least one exists
+	foreach ($userlist['def'] as $pg)
+		if (isset($customsummarypages[$pg]))
+			return true;
+ }
+
+ onlylogin();
+ return 'login';
+}
+#
 function display()
 {
  global $miner, $port;
  global $readonly, $notify, $rigs;
  global $ignorerefresh, $autorefresh;
- global $allowcustompages;
+ global $allowcustompages, $customsummarypages;
  global $placebuttons;
+ global $userlist, $ses;
+
+ $pagesonly = checklogin();
+
+ if ($pagesonly === 'login')
+	return;
+
+ if ($rigs == null or count($rigs) == 0)
+ {
+	otherrow("<td>No rigs defined</td>");
+	return;
+ }
 
  if ($ignorerefresh == false)
  {
@@ -2412,52 +2584,65 @@ function display()
 		$autorefresh = intval($ref);
  }
 
- $rig = trim(getparam('rig', true));
-
- $arg = trim(getparam('arg', true));
- $preprocess = null;
- if ($arg != null and $arg != '')
+ if ($pagesonly !== true)
  {
-	$num = null;
-	if ($rig != null and $rig != '')
-	{
-		if ($rig >= 0 and $rig < count($rigs))
-			$num = $rig;
-	}
-	else
-		if (count($rigs) == 0)
-			$num = 0;
+	$rig = trim(getparam('rig', true));
 
-	if ($num != null)
+	$arg = trim(getparam('arg', true));
+	$preprocess = null;
+	if ($arg != null and $arg != '')
 	{
-		$parts = explode(':', $rigs[$num], 3);
-		if (count($parts) >= 2)
+		if ($rig != null and $rig != '' and $rig >= 0 and $rig < count($rigs))
 		{
-			$miner = $parts[0];
-			$port = $parts[1];
+			$parts = explode(':', $rigs[$rig], 3);
+			if (count($parts) >= 2)
+			{
+				$miner = $parts[0];
+				$port = $parts[1];
 
-			if ($readonly !== true)
-				$preprocess = $arg;
+				if ($readonly !== true)
+					$preprocess = $arg;
+			}
 		}
 	}
  }
 
- if ($rigs == null or count($rigs) == 0)
- {
-	otherrow("<td>No rigs defined</td>");
-	return;
- }
-
  if ($allowcustompages === true)
  {
 	$pg = trim(getparam('pg', true));
-	if ($pg != null && $pg != '')
+	if ($pagesonly === true)
+	{
+		if ($pg !== null && $pg !== '')
+		{
+			if ($userlist !== null && isset($userlist['def'])
+			&&  !in_array($pg, $userlist['def']))
+				$pg = null;
+		}
+		else
+		{
+			if ($userlist !== null && isset($userlist['def']))
+				foreach ($userlist['def'] as $pglook)
+					if (isset($customsummarypages[$pglook]))
+					{
+						$pg = $pglook;
+						break;
+					}
+		}
+	}
+
+	if ($pg !== null && $pg !== '')
 	{
 		showcustompage($pg);
 		return;
 	}
  }
 
+ if ($pagesonly === true)
+ {
+	onlylogin();
+	return;
+ }
+
  if (count($rigs) == 1)
  {
 	$parts = explode(':', $rigs[0], 3);

+ 32 - 24
util.c

@@ -314,32 +314,38 @@ static void set_nettime(void)
 	wr_unlock(&netacc_lock);
 }
 
-static int my_curl_debug(__maybe_unused CURL *curl, curl_infotype infotype, char *data, size_t datasz, void *userdata)
+static int curl_debug_cb(__maybe_unused CURL *handle, curl_infotype type,
+			 char *data, size_t size,
+			 void *userdata)
 {
-	struct pool *pool = userdata;
+	struct pool *pool = (struct pool *)userdata;
 
-	switch (infotype) {
+	switch(type) {
+		case CURLINFO_HEADER_IN:
+		case CURLINFO_DATA_IN:
+		case CURLINFO_SSL_DATA_IN:
+			pool->cgminer_pool_stats.bytes_received += size;
+			total_bytes_xfer += size;
+			pool->cgminer_pool_stats.net_bytes_received += size;
+			break;
+		case CURLINFO_HEADER_OUT:
+		case CURLINFO_DATA_OUT:
+		case CURLINFO_SSL_DATA_OUT:
+			pool->cgminer_pool_stats.bytes_sent += size;
+			total_bytes_xfer += size;
+			pool->cgminer_pool_stats.net_bytes_sent += size;
+			break;
 		case CURLINFO_TEXT:
 		{
 			if (!opt_protocol)
 				break;
 			// data is not null-terminated, so we need to copy and terminate it for applog
-			char datacp[datasz + 1];
-			memcpy(datacp, data, datasz);
-			datacp[datasz] = '\0';
+			char datacp[size + 1];
+			memcpy(datacp, data, size);
+			datacp[size] = '\0';
 			applog(LOG_DEBUG, "Pool %u: %s", pool->pool_no, datacp);
 			break;
 		}
-		case CURLINFO_HEADER_IN:
-		case CURLINFO_DATA_IN:
-			pool->cgminer_pool_stats.bytes_received += datasz;
-			total_bytes_xfer += datasz;
-			break;
-		case CURLINFO_HEADER_OUT:
-		case CURLINFO_DATA_OUT:
-			pool->cgminer_pool_stats.bytes_sent += datasz;
-			total_bytes_xfer += datasz;
-			break;
 		default:
 			break;
 	}
@@ -379,6 +385,12 @@ void json_rpc_call_async(CURL *curl, const char *url,
 	curl_easy_setopt(curl, CURLOPT_PRIVATE, state);
 	curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
 
+	/* We use DEBUGFUNCTION to count bytes sent/received, and verbose is needed
+	 * to enable it */
+	curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_cb);
+	curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)pool);
+	curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+
 	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
 	curl_easy_setopt(curl, CURLOPT_URL, url);
 	curl_easy_setopt(curl, CURLOPT_ENCODING, "");
@@ -397,12 +409,6 @@ void json_rpc_call_async(CURL *curl, const char *url,
 	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, resp_hdr_cb);
 	curl_easy_setopt(curl, CURLOPT_HEADERDATA, &state->hi);
 
-	/* We use DEBUGFUNCTION to count bytes sent/received, and verbose is needed
-	 * to enable it */
-	curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
-	curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_curl_debug);
-	curl_easy_setopt(curl, CURLOPT_DEBUGDATA, pool);
-
 	curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
 	if (pool->rpc_proxy) {
 		curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy);
@@ -1012,6 +1018,7 @@ static bool __stratum_send(struct pool *pool, char *s, ssize_t len)
 	pool->cgminer_pool_stats.times_sent++;
 	pool->cgminer_pool_stats.bytes_sent += ssent;
 	total_bytes_xfer += ssent;
+	pool->cgminer_pool_stats.net_bytes_sent += ssent;
 	return true;
 }
 
@@ -1142,6 +1149,7 @@ char *recv_line(struct pool *pool)
 	pool->cgminer_pool_stats.times_received++;
 	pool->cgminer_pool_stats.bytes_received += len;
 	total_bytes_xfer += len;
+	pool->cgminer_pool_stats.net_bytes_received += len;
 
 out:
 	if (!sret)
@@ -1520,9 +1528,9 @@ bool initiate_stratum(struct pool *pool)
 
 	/* We use DEBUGFUNCTION to count bytes sent/received, and verbose is needed
 	 * to enable it */
+	curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_cb);
+	curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)pool);
 	curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
-	curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_curl_debug);
-	curl_easy_setopt(curl, CURLOPT_DEBUGDATA, pool);
 
 	curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
 	if (pool->rpc_proxy) {