From d30a5d0b193b47e5cffee28ffdb82d2a1659411a Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sun, 20 Apr 2025 20:27:12 -0500 Subject: [PATCH] Speed up achievement scoring. First get all of the necessary achievement data from the database, then process it and print it to the file. Comparing scoring all course achievements for all users in a course with 5000 users shows a considerable speed improvement. It takes more than three minutes for this with the develop branch, and less than 3 seconds with this pull request. Obviously the generated scoring files are identical. --- .../Instructor/AchievementList.pm | 69 +++++++++---------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm b/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm index f05aa29ea3..17a75c2838 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/AchievementList.pm @@ -234,8 +234,20 @@ sub score_handler ($c) { my $scope = $c->param('action.score.scope'); my @achievementsToScore = $scope eq 'all' ? @{ $c->{allAchievementIDs} } : $c->param('selected_achievements'); + # First get everything that is needed from the database. + my @achievements = sortAchievements($db->getAchievements(@achievementsToScore)); + my @users = $db->getUsersWhere({ user_id => { not_like => 'set_id:%' } }, [qw(section last_name)]); + + my %globalUserAchievements = map { $_->user_id => $_ } $db->getGlobalUserAchievementsWhere; + + my %userAchievements; + for (@achievements) { + $userAchievements{ $_->user_id }{ $_->achievement_id } = $_ + for $db->getUserAchievementsWhere({ achievement_id => $_->achievement_id }); + } + # Define file name - my $scoreFileName = $courseName . "_achievement_scores.csv"; + my $scoreFileName = $courseName . '_achievement_scores.csv'; my $scoreFilePath = $ce->{courseDirs}{scoring} . '/' . $scoreFileName; # Back up existing file @@ -247,60 +259,41 @@ sub score_handler ($c) { # Check path and open the file $scoreFilePath = surePathToFile($ce->{courseDirs}{scoring}, $scoreFilePath); - my $SCORE = Mojo::File->new($scoreFilePath)->open('>:encoding(UTF-8)') - or return (0, $c->maketext("Failed to open [_1]", $scoreFilePath)); + my $scoreFile = Mojo::File->new($scoreFilePath)->open('>:encoding(UTF-8)') + or return (0, $c->maketext('Failed to open [_1]', $scoreFilePath)); # Print out header info - print $SCORE $c->maketext("username, last name, first name, section, achievement level, achievement score,"); - - my @achievements = $db->getAchievements(@achievementsToScore); - @achievements = sortAchievements(@achievements); + print $scoreFile $c->maketext('username, last name, first name, section, achievement level, achievement score,'); for my $achievement (@achievements) { - print $SCORE $achievement->achievement_id . ", "; - } - print $SCORE "\n"; - - my @users = $db->listUsers; - - # Get user records - my @userRecords = (); - for my $currentUser (@users) { - my $userObj = $db->getUser($currentUser); - die "Unable to find user object for $currentUser. " unless $userObj; - push(@userRecords, $userObj); + print $scoreFile $achievement->achievement_id . ', '; } - - @userRecords = - sort { (lc($a->section) cmp lc($b->section)) || (lc($a->last_name) cmp lc($b->last_name)) } @userRecords; + print $scoreFile "\n"; # Print out achievement information for each user - for my $userRecord (@userRecords) { + for my $userRecord (@users) { my $user_id = $userRecord->user_id; - next unless $db->existsGlobalUserAchievement($user_id); - next if ($userRecord->{status} eq 'D' || $userRecord->{status} eq 'A'); - print $SCORE "$user_id, $userRecord->{last_name}, $userRecord->{first_name}, $userRecord->{section}, "; - my $globalUserAchievement = $db->getGlobalUserAchievement($user_id); - my $level_id = $globalUserAchievement->level_achievement_id; - $level_id = ' ' unless $level_id; - my $points = $globalUserAchievement->achievement_points; - $points = 0 unless $points; - print $SCORE "$level_id, $points, "; + next if !$globalUserAchievements{$user_id} || $userRecord->{status} eq 'D' || $userRecord->{status} eq 'A'; + + print $scoreFile "$user_id, $userRecord->{last_name}, $userRecord->{first_name}, $userRecord->{section}, "; + + my $level_id = $globalUserAchievements{$user_id}->level_achievement_id || ' '; + my $points = $globalUserAchievements{$user_id}->achievement_points || 0; + print $scoreFile "$level_id, $points, "; for my $achievement (@achievements) { my $achievement_id = $achievement->achievement_id; - if ($db->existsUserAchievement($user_id, $achievement_id)) { - my $userAchievement = $db->getUserAchievement($user_id, $achievement_id); - print $SCORE $userAchievement->earned ? "1, " : "0, "; + if ($userAchievements{$user_id}{$achievement_id}) { + print $scoreFile $userAchievements{$user_id}{$achievement_id}->earned ? '1, ' : '0, '; } else { - print $SCORE ", "; + print $scoreFile ', '; } } - print $SCORE "\n"; + print $scoreFile "\n"; } - $SCORE->close; + $scoreFile->close; # Include a download link return (