From dbd2d03a0d3e65a0025fe5b60edbc57571c7ef9b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 15 Aug 2014 14:01:32 -0700 Subject: [PATCH] server/web: add support for per-application credit See http://boinc.berkeley.edu/trac/wiki/PerAppCredit If enabled (by the config flag) validators will maintain on a per-(app, user, credit type) basis, and same for teams, in new DB tables credit_user and credit_team. This info is displayed in the web site, on user and team pages, using project-supplied functions to generate the HTML. Note: update_stats doesn't decay the recent-average values for per-app credit; I'll add this if needed. --- db/boinc_db.cpp | 68 +++++++++++++++++++++++++++++++++ db/boinc_db.h | 12 ++++++ db/boinc_db_types.h | 23 +++++++++++ db/schema.sql | 22 +++++++++++ doc/projects.inc | 10 +++++ html/inc/boinc_db.inc | 13 +++++++ html/inc/prefs.inc | 4 -- html/inc/prefs_project.inc | 4 -- html/inc/team.inc | 3 ++ html/inc/util.inc | 4 ++ html/ops/db_update.php | 30 +++++++++++++++ html/project.sample/project.inc | 56 ++++++++++++++++++++++++++- lib/common_defs.h | 7 ++++ py/Boinc/boinc_db.py | 4 ++ sched/credit.cpp | 60 +++++++++++++++++++++++++++++ sched/credit.h | 2 + sched/sched_config.cpp | 1 + sched/sched_config.h | 2 + sched/update_stats.cpp | 2 +- sched/validator.cpp | 6 +++ 20 files changed, 323 insertions(+), 10 deletions(-) diff --git a/db/boinc_db.cpp b/db/boinc_db.cpp index d448c7bcd3..2dc4bcb53c 100644 --- a/db/boinc_db.cpp +++ b/db/boinc_db.cpp @@ -97,6 +97,8 @@ void VDA_CHUNK_HOST::clear() {memset(this, 0, sizeof(*this));} void BADGE::clear() {memset(this, 0, sizeof(*this));} void BADGE_USER::clear() {memset(this, 0, sizeof(*this));} void BADGE_TEAM::clear() {memset(this, 0, sizeof(*this));} +void CREDIT_USER::clear() {memset(this, 0, sizeof(*this));} +void CREDIT_TEAM::clear() {memset(this, 0, sizeof(*this));} DB_PLATFORM::DB_PLATFORM(DB_CONN* dc) : DB_BASE("platform", dc?dc:&boinc_db){} @@ -170,6 +172,10 @@ DB_BADGE_USER::DB_BADGE_USER(DB_CONN* dc) : DB_BASE("badge_user", dc?dc:&boinc_db){} DB_BADGE_TEAM::DB_BADGE_TEAM(DB_CONN* dc) : DB_BASE("badge_team", dc?dc:&boinc_db){} +DB_CREDIT_USER::DB_CREDIT_USER(DB_CONN* dc) : + DB_BASE("credit_user", dc?dc:&boinc_db){} +DB_CREDIT_TEAM::DB_CREDIT_TEAM(DB_CONN* dc) : + DB_BASE("credit_team", dc?dc:&boinc_db){} int DB_PLATFORM::get_id() {return id;} int DB_APP::get_id() {return id;} @@ -2654,4 +2660,66 @@ void DB_BADGE_TEAM::db_parse(MYSQL_ROW &r) { reassign_time = atof(r[i++]); } +void DB_CREDIT_USER::db_print(char* buf) { + sprintf(buf, + "userid=%d, " + "appid=%d, " + "njobs=%d, " + "total=%.15e, " + "expavg=%.15e, " + "expavg_time=%.15e, " + "credit_type=%d ", + userid, + appid, + njobs, + total, + expavg, + expavg_time, + credit_type + ); +} + +void DB_CREDIT_USER::db_parse(MYSQL_ROW &r) { + int i=0; + clear(); + userid = atoi(r[i++]); + appid = atoi(r[i++]); + njobs = atoi(r[i++]); + total = atof(r[i++]); + expavg = atof(r[i++]); + expavg_time = atof(r[i++]); + credit_type = atoi(r[i++]); +} + +void DB_CREDIT_TEAM::db_print(char* buf) { + sprintf(buf, + "teamid=%d, " + "appid=%d, " + "njobs=%d, " + "total=%.15e, " + "expavg=%.15e, " + "expavg_time=%.15e, " + "credit_type=%d ", + teamid, + appid, + njobs, + total, + expavg, + expavg_time, + credit_type + ); +} + +void DB_CREDIT_TEAM::db_parse(MYSQL_ROW &r) { + int i=0; + clear(); + teamid = atoi(r[i++]); + appid = atoi(r[i++]); + njobs = atoi(r[i++]); + total = atof(r[i++]); + expavg = atof(r[i++]); + expavg_time = atof(r[i++]); + credit_type = atoi(r[i++]); +} + const char *BOINC_RCSID_ac374386c8 = "$Id$"; diff --git a/db/boinc_db.h b/db/boinc_db.h index 79c2d6aeb0..bf99c06eec 100644 --- a/db/boinc_db.h +++ b/db/boinc_db.h @@ -531,4 +531,16 @@ struct DB_BADGE_TEAM : public DB_BASE, public BADGE_TEAM { void db_parse(MYSQL_ROW&); }; +struct DB_CREDIT_USER : public DB_BASE, public CREDIT_USER { + DB_CREDIT_USER(DB_CONN* p=0); + void db_print(char*); + void db_parse(MYSQL_ROW&); +}; + +struct DB_CREDIT_TEAM : public DB_BASE, public CREDIT_TEAM { + DB_CREDIT_TEAM(DB_CONN* p=0); + void db_print(char*); + void db_parse(MYSQL_ROW&); +}; + #endif diff --git a/db/boinc_db_types.h b/db/boinc_db_types.h index 56257bbada..22ce07d2e1 100644 --- a/db/boinc_db_types.h +++ b/db/boinc_db_types.h @@ -799,4 +799,27 @@ struct BADGE_TEAM { void clear(); }; +struct CREDIT_USER { + int userid; + int appid; + // need not be an app ID + int njobs; + double total; + double expavg; + double expavg_time; + int credit_type; + void clear(); +}; + +struct CREDIT_TEAM { + int teamid; + int appid; + int njobs; + double total; + double expavg; + double expavg_time; + int credit_type; + void clear(); +}; + #endif diff --git a/db/schema.sql b/db/schema.sql index ffe703f51d..12ef14ffd5 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -731,3 +731,25 @@ create table badge_team ( create_time double not null, reassign_time double not null ); + +create table credit_user ( + userid integer not null, + appid integer not null, + njobs integer not null, + total double not null, + expavg double not null, + expavg_time double not null, + credit_type integer not null, + primary key (userid, appid, credit_type) +) engine=InnoDB; + +create table credit_team ( + teamid integer not null, + appid integer not null, + njobs integer not null, + total double not null, + expavg double not null, + expavg_time double not null, + credit_type integer not null, + primary key (teamid, appid, credit_type) +) engine=InnoDB; diff --git a/doc/projects.inc b/doc/projects.inc index 9832d293be..b06b4e261f 100644 --- a/doc/projects.inc +++ b/doc/projects.inc @@ -540,6 +540,16 @@ $math = array( "", "Study hash functions" ), + array( + "Moo! Wrapper", + "http://moowrap.net/", + "Private", + tra("Cryptography and combinatorics"), + "Run applications from distributed.net", + "", + "", + "Run applications from distributed.net" + ), array( "Enigma@Home", "http://www.enigmaathome.net/", diff --git a/html/inc/boinc_db.inc b/html/inc/boinc_db.inc index 87b7c5a8fa..d29968dff9 100644 --- a/html/inc/boinc_db.inc +++ b/html/inc/boinc_db.inc @@ -646,5 +646,18 @@ class BoincBadgeTeam { } } +class BoincCreditUser { + static function lookup($clause) { + $db = BoincDb::get(); + return $db->lookup('credit_user', 'BoincCreditUser', $clause); + } +} + +class BoincCreditTeam { + static function lookup($clause) { + $db = BoincDb::get(); + return $db->lookup('credit_team', 'BoincCreditTeam', $clause); + } +} ?> diff --git a/html/inc/prefs.inc b/html/inc/prefs.inc index b4433b75b2..a863df3e16 100644 --- a/html/inc/prefs.inc +++ b/html/inc/prefs.inc @@ -39,10 +39,6 @@ // Various functions are defined below for converting between these forms, // and also to/from HTML form elements -error_reporting(E_ALL); -ini_set('display_errors', true); -ini_set('display_startup_errors', true); - include_once("../inc/prefs_util.inc"); $cpu_prefs = array( diff --git a/html/inc/prefs_project.inc b/html/inc/prefs_project.inc index 453f6db714..731fffbdc7 100644 --- a/html/inc/prefs_project.inc +++ b/html/inc/prefs_project.inc @@ -42,10 +42,6 @@ // In addition there are some fields of the user table // (send_email and show_hosts) that are treated as project preferences -error_reporting(E_ALL); -ini_set('display_errors', true); -ini_set('display_startup_errors', true); - include_once("../inc/prefs_util.inc"); include_once("../project/project_specific_prefs.inc"); diff --git a/html/inc/team.inc b/html/inc/team.inc index bf18d3e09f..d916d68bc6 100644 --- a/html/inc/team.inc +++ b/html/inc/team.inc @@ -104,6 +104,9 @@ function display_team_page($team, $user) { if (!no_computing()) { row2(tra('Total credit'), format_credit_large($team->total_credit)); row2(tra('Recent average credit'), format_credit_large($team->expavg_credit)); + if (function_exists('project_team_credit')) { + project_team_credit($team); + } show_badges_row(false, $team); $x = ""; diff --git a/html/inc/util.inc b/html/inc/util.inc index db562b9288..e6994d0f3c 100644 --- a/html/inc/util.inc +++ b/html/inc/util.inc @@ -18,6 +18,10 @@ // Utility functions for BOINC web pages +error_reporting(E_ALL); +ini_set('display_errors', true); +ini_set('display_startup_errors', true); + require_once("../inc/util_basic.inc"); require_once("../project/project.inc"); require_once("../inc/countries.inc"); diff --git a/html/ops/db_update.php b/html/ops/db_update.php index 57fa4d867f..a69a35faf3 100644 --- a/html/ops/db_update.php +++ b/html/ops/db_update.php @@ -941,6 +941,35 @@ function update_6_5_2014() { ); } +function update_8_15_2014() { + do_query( + "create table credit_user ( + userid integer not null, + appid integer not null, + njobs integer not null, + total double not null, + expavg double not null, + expavg_time double not null, + credit_type integer not null, + primary key (userid, appid, credit_type) + ) engine=InnoDB + " + ); + do_query( + "create table credit_team ( + teamid integer not null, + appid integer not null, + njobs integer not null, + total double not null, + expavg double not null, + expavg_time double not null, + credit_type integer not null, + primary key (teamid, appid, credit_type) + ) engine=InnoDB + " + ); +} + // Updates are done automatically if you use "upgrade". // // If you need to do updates manually, @@ -983,6 +1012,7 @@ $db_updates = array ( array(27008, "update_4_2_2014"), array(27009, "update_5_3_2014"), array(27010, "update_6_5_2014"), + array(27011, "update_8_15_2014"), ); ?> diff --git a/html/project.sample/project.inc b/html/project.sample/project.inc index badf82f3d2..3c7cc09668 100644 --- a/html/project.sample/project.inc +++ b/html/project.sample/project.inc @@ -134,8 +134,62 @@ function project_user_page_private($user){ // shown in the private account page } + +// show project-specific credit on user/team pages +// +function show_app_credit_user($user, $app_name, $appids) { + $t = 0; + $a = 0; + $n = 0; + foreach ($appids as $appid) { + $cu = BoincCreditUser::lookup( + "userid=$user->id and appid=$appid and credit_type=0" + ); + if ($cu) { + $t += $cu->total; + $a += $cu->expavg; + $n += $cu->njobs; + } + } + row2("$app_name credit", + format_credit_large($t)." total, ". + format_credit($a)." average". + " ($n tasks)" + ); +} +function show_app_credit_team($team, $app_name, $appids) { + $t = 0; + $a = 0; + $n = 0; + foreach ($appids as $appid) { + $ct = BoincCreditTeam::lookup( + "teamid=$team->id and appid=$appid and credit_type=0" + ); + if ($ct) { + $t += $ct->total; + $a += $ct->expavg; + $n += $ct->njobs; + } + } + row2("$app_name credit", + format_credit_large($t)." total, ". + format_credit($a)." average". + " ($n tasks)" + ); +} + function project_user_credit($user){ - // shown in the the private account page, credit area +if (0) { + show_app_credit_user($user, "Remote Test", array(16)); + show_app_credit_user($user, "Uppercase", array(1, 25)); +} +} + +function project_team_credit($team) { +if (0) { + show_app_credit_team($team, "Remote Test", array(16)); + show_app_credit_team($team, "Uppercase", array(1, 25)); +} } function project_forum_post_rules() { diff --git a/lib/common_defs.h b/lib/common_defs.h index a835b1d08e..25501659ee 100644 --- a/lib/common_defs.h +++ b/lib/common_defs.h @@ -213,6 +213,13 @@ enum BATTERY_STATE { // input/output files can be deleted, // result and workunit records can be purged. +// credit types +// +#define CREDIT_TYPE_FLOPS 0 +#define CREDIT_TYPE_STORAGE 1 +#define CREDIT_TYPE_NETWORK 2 +#define CREDIT_TYPE_PROJECT 3 + struct TIME_STATS { double now; // the client's current time of day diff --git a/py/Boinc/boinc_db.py b/py/Boinc/boinc_db.py index c70d29ee31..a49734657d 100644 --- a/py/Boinc/boinc_db.py +++ b/py/Boinc/boinc_db.py @@ -119,3 +119,7 @@ BATCH_STATE_IN_PROGRESS = 1 BATCH_STATE_COMPLETE = 2 BATCH_STATE_ABORTED = 3 BATCH_STATE_RETIRED = 4 +CREDIT_TYPE_FLOPS = 0 +CREDIT_TYPE_STORAGE = 1 +CREDIT_TYPE_NETWORK = 2 +CREDIT_TYPE_PROJECT = 3 diff --git a/sched/credit.cpp b/sched/credit.cpp index 93c554ebd3..5ed803f225 100644 --- a/sched/credit.cpp +++ b/sched/credit.cpp @@ -124,6 +124,66 @@ int grant_credit(DB_HOST &host, double start_time, double credit) { return 0; } +int grant_credit_by_app(RESULT& result, double credit) { + DB_CREDIT_USER cu; + char clause1[1024], clause2[1024]; + double now = dtime(); + + sprintf(clause1, "where userid=%d and appid=%d", result.userid, result.appid); + int retval = cu.lookup(clause1); + if (retval) { + cu.clear(); + cu.userid = result.userid; + cu.appid = result.appid; + retval = cu.insert(); + if (retval) return retval; + } + update_average( + now, + result.sent_time, + credit, CREDIT_HALF_LIFE, + cu.expavg, cu.expavg_time + ); + sprintf(clause1, + "total=total+%.15e, expavg=%.15e, expavg_time=%.15e, njobs=njobs+1", + credit, cu.expavg, cu.expavg_time + ); + sprintf(clause2, + "userid=%d and appid=%d", result.userid, result.appid + ); + retval = cu.update_fields_noid(clause1, clause2); + if (retval) return retval; + + // team. keep track of credit for zero (no team) also + // + DB_CREDIT_TEAM ct; + sprintf(clause1, "where teamid=%d and appid=%d", result.teamid, result.appid); + retval = ct.lookup(clause1); + if (retval) { + ct.clear(); + ct.teamid = result.teamid; + ct.appid = result.appid; + retval = ct.insert(); + if (retval) return retval; + } + update_average( + now, + result.sent_time, + credit, CREDIT_HALF_LIFE, + ct.expavg, ct.expavg_time + ); + sprintf(clause1, + "total=total+%.15e, expavg=%.15e, expavg_time=%.15e, njobs=njobs+1", + credit, ct.expavg, ct.expavg_time + ); + sprintf(clause2, + "teamid=%d and appid=%d", result.teamid, result.appid + ); + retval = ct.update_fields_noid(clause1, clause2); + if (retval) return retval; + return 0; +} + ///////////////////// V2 CREDIT STUFF STARTS HERE /////////////////// // levels of confidence in a credit value diff --git a/sched/credit.h b/sched/credit.h index 623eb46895..e185d5b9cf 100644 --- a/sched/credit.h +++ b/sched/credit.h @@ -63,3 +63,5 @@ extern int hav_lookup(DB_HOST_APP_VERSION& hav, int hostid, int avid); extern int write_modified_app_versions( std::vector& app_versions ); + +extern int grant_credit_by_app(RESULT& result, double credit); diff --git a/sched/sched_config.cpp b/sched/sched_config.cpp index 4cebaa0e53..0780ac0508 100644 --- a/sched/sched_config.cpp +++ b/sched/sched_config.cpp @@ -296,6 +296,7 @@ int SCHED_CONFIG::parse(FILE* f) { if (xp.parse_bool("prefer_primary_platform", prefer_primary_platform)) continue; if (xp.parse_double("version_select_random_factor", version_select_random_factor)) continue; if (xp.parse_double("maintenance_delay", maintenance_delay)) continue; + if (xp.parse_bool("credit_by_app", credit_by_app)) continue; //////////// SCHEDULER LOG FLAGS ///////// diff --git a/sched/sched_config.h b/sched/sched_config.h index 7ef02d5e76..182396de74 100644 --- a/sched/sched_config.h +++ b/sched/sched_config.h @@ -177,6 +177,8 @@ struct SCHED_CONFIG { bool estimate_flops_from_hav_pfc; // Use host_app_version peak flop count rather than elapsed time // to calculate projected_flops when choosing version. + bool credit_by_app; + // store per-app credit info in credit_user and credit_team // time intervals double maintenance_delay; diff --git a/sched/update_stats.cpp b/sched/update_stats.cpp index 9b806b353a..04bc714e78 100644 --- a/sched/update_stats.cpp +++ b/sched/update_stats.cpp @@ -17,7 +17,7 @@ // update_stats: // Update average credit for idle users, hosts and teams. -// These fields are updates as new credit is granted; +// These fields are updated as new credit is granted; // the purpose of this program is to decay credit of entities // that are inactive for long periods. // Run it about once a day. diff --git a/sched/validator.cpp b/sched/validator.cpp index 5915e73fe8..04c5374429 100644 --- a/sched/validator.cpp +++ b/sched/validator.cpp @@ -308,6 +308,9 @@ int handle_wu( if (!no_credit) { result.granted_credit = canonical_result.granted_credit; grant_credit(host, result.sent_time, result.granted_credit); + if (config.credit_by_app) { + grant_credit_by_app(result, result.granted_credit); + } } break; case VALIDATE_STATE_INVALID: @@ -534,6 +537,9 @@ int handle_wu( result.id, result.name, result.granted_credit, result.hostid ); + if (config.credit_by_app) { + grant_credit_by_app(result, credit); + } } break; case VALIDATE_STATE_INVALID: