Skip to content

Commit

Permalink
Add four-layer Elapsed Time (ET) report View (#9)
Browse files Browse the repository at this point in the history
* Elapsed Time Support.

* Elapsed Support

* Make changes to match Jared and Daniel's

* Stable Elapsed Time View.

* Add the view to show the sum of elapsed time of all jobs, for each repo.

* Enabled the "Job Elapsed Time List" page for each repo

* Make JobET view full function.

* Job ET per build view is complete

* Use public URL instead of debugging one, so it'll be accessible by others.

* Use @Url.Action instead of a hardcoded URI (#3)

Url.Action(...)  is a built-in MVC helper function that can build up a URI regardless of where the website is running. This allows the code to work regardless of where the webpage is deployed ( localhost ,  jdash-et ,  jdash , etc).

* 1. Add support to make bootstrap table columns sortable.
2. Make "Job ET per build" table sortable, so managers can choose to sort by either "Build Number", and "Elapsed Time".

* Add "ElapsedTime" in the main layout as the entrance of Elapsed Time Report.

* Add report on average ET per job and average ET per build (of a job).

* Changes to address Jared's comments.
  • Loading branch information
smile21prc authored and jaredpar committed Jun 23, 2016
1 parent f5af5f2 commit 5f1dcaa
Show file tree
Hide file tree
Showing 17 changed files with 1,045 additions and 4 deletions.
13 changes: 12 additions & 1 deletion ApiFun/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal static void Main(string[] args)
// PrintJobInfo();
// PrintQueue();
// PrintViews();
// PrintPullRequestData();
//PrintPullRequestData();
// PrintFailure();
// PrintJobs();

Expand Down Expand Up @@ -272,6 +272,16 @@ private static async Task Random()
var test = await client.GetFailedTestCasesAsync(buildId);
var prInfo = await client.GetPullRequestInfoAsync(buildId);
*/
var testboundBuildId = BoundBuildId.Parse("https://dotnet-ci.cloudapp.net/job/dotnet_coreclr/job/release_1.0.0/job/x64_release_rhel7.2_pri1_flow/30/");
var testbuildId = testboundBuildId.BuildId;
var client = CreateClient(uri: testboundBuildId.HostUri, auth: true);
var elapsedTimeObj = client.GetBuildInfo(testbuildId).Duration;
Console.WriteLine($"\tET: {elapsedTimeObj.TotalMilliseconds}");

// var buildInfo = await client.GetBuildInfoAsync(buildId);
// var buildResult = await client.GetBuildResultAsync(buildInfo);
// var test = await client.GetFailedTestCasesAsync(buildId);
// var prInfo = await client.GetPullRequestInfoAsync(buildId);

var account = GetStorageAccount();
var dateKey = new DateKey(DateTimeOffset.UtcNow - TimeSpan.FromDays(1));
Expand Down Expand Up @@ -418,6 +428,7 @@ private static void PrintPullRequestData()
{
Console.WriteLine($"\tBuild: {buildId.Number}");
var date = client.GetBuildInfo(buildId).Date;
var elapsedTime = client.GetBuildInfo(buildId).Duration;
// list.Add(Tuple.Create(date, buildId));
}
}
Expand Down
6 changes: 4 additions & 2 deletions Dashboard/App_Start/BundleConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ public static void RegisterBundles(BundleCollection bundles)

bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js"));
"~/Scripts/respond.js",
"~/Scripts/bootstrap-sortable.js"));

bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css",
"~/Content/site.css"));
"~/Content/site.css",
"~/Content/bootstrap-sortable.css"));
}
}
}
110 changes: 110 additions & 0 deletions Dashboard/Content/bootstrap-sortable.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* adding sorting ability to HTML tables with Bootstrap styling
* @summary HTML tables sorting ability
* @version 2.0.0
* @requires tinysort, moment.js, jQuery
* @license MIT
* @author Matus Brlit (drvic10k)
* @copyright Matus Brlit (drvic10k), bootstrap-sortable contributors
*/

table.sortable span.sign {
display: block;
position: absolute;
top: 50%;
right: 5px;
font-size: 12px;
margin-top: -10px;
color: #bfbfc1;
}

table.sortable th:after {
display: block;
position: absolute;
top: 50%;
right: 5px;
font-size: 12px;
margin-top: -10px;
color: #bfbfc1;
}

table.sortable th.arrow:after {
content: '';
}

table.sortable span.arrow, span.reversed, th.arrow.down:after, th.reversedarrow.down:after, th.arrow.up:after, th.reversedarrow.up:after {
border-style: solid;
border-width: 5px;
font-size: 0;
border-color: #ccc transparent transparent transparent;
line-height: 0;
height: 0;
width: 0;
margin-top: -2px;
}

table.sortable span.arrow.up, th.arrow.up:after {
border-color: transparent transparent #ccc transparent;
margin-top: -7px;
}

table.sortable span.reversed, th.reversedarrow.down:after {
border-color: transparent transparent #ccc transparent;
margin-top: -7px;
}

table.sortable span.reversed.up, th.reversedarrow.up:after {
border-color: #ccc transparent transparent transparent;
margin-top: -2px;
}

table.sortable span.az:before, th.az.down:after {
content: "a .. z";
}

table.sortable span.az.up:before, th.az.up:after {
content: "z .. a";
}

table.sortable th.az.nosort:after, th.AZ.nosort:after, th._19.nosort:after, th.month.nosort:after {
content: "..";
}

table.sortable span.AZ:before, th.AZ.down:after {
content: "A .. Z";
}

table.sortable span.AZ.up:before, th.AZ.up:after {
content: "Z .. A";
}

table.sortable span._19:before, th._19.down:after {
content: "1 .. 9";
}

table.sortable span._19.up:before, th._19.up:after {
content: "9 .. 1";
}

table.sortable span.month:before, th.month.down:after {
content: "jan .. dec";
}

table.sortable span.month.up:before, th.month.up:after {
content: "dec .. jan";
}

table.sortable>thead th:not([data-defaultsort=disabled]) {
cursor: pointer;
position: relative;
top: 0;
left: 0;
}

table.sortable>thead th:hover:not([data-defaultsort=disabled]) {
background: #efefef;
}

table.sortable>thead th div.mozilla {
position: relative;
}
188 changes: 188 additions & 0 deletions Dashboard/Controllers/BuildsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class BuildsController : Controller
{
private readonly DashboardStorage _storage;
private readonly BuildUtil _buildUtil;
private const int _ETRangeCount = 6;

public BuildsController()
{
Expand Down Expand Up @@ -101,6 +102,105 @@ public ActionResult View(bool pr = false, DateTimeOffset? startDate = null, stri
return View(viewName: "View", model: model);
}

/// <summary>
/// A view of the elapsed time grouped by the result.
/// </summary>
/// <returns></returns>
public ActionResult ElapsedTime(bool pr = false, DateTimeOffset? startDate = null, string viewName = AzureUtil.ViewNameRoslyn)
{
var filter = CreateBuildFilter(actionName: nameof(ElapsedTime), viewName: viewName, startDate: startDate, pr: pr);
var results =
_buildUtil.GetBuildResults(filter.StartDate, viewName)
.Where(x => pr || !JobUtil.IsPullRequestJobName(x.JobId))
.ToList();

var totalCount = results.Count;
var totalSucceeded = results.Count(x => x.ClassificationKind == ClassificationKind.Succeeded);

var runCounts = results
.Select(x => new ElapsedTimeModel() { JobId = x.JobId, JobName = x.JobName, ElapsedTime = x.DurationSeconds })
.ToList();

List<int> runsPerETRange = new List<int>();

for (int i = 0; i < _ETRangeCount; i++)
{
runsPerETRange.Add(0);
}

foreach (var runElapsedTime in runCounts)
{
int ETDigits = runElapsedTime.ElapsedTime.ToString().Length;
runsPerETRange[ETDigits - 1] = runsPerETRange[ETDigits - 1] + 1;
}

var model = new ElapsedTimeSummaryModel()
{
Filter = filter,
TotalBuildCount = totalCount,
TotalSucceededCount = totalSucceeded,
RunCountsPerETRange = runsPerETRange
};

return View(viewName: "ElapsedTime", model: model);
}

/// <summary>
/// A view of the total elapsed time per team/project repo, ranked from most elapsed time to least.
/// </summary>
/// <returns></returns>
public ActionResult ProjectElapsedTime(bool pr = false, DateTimeOffset? startDate = null)
{
var filter = CreateBuildFilter(actionName: nameof(ProjectElapsedTime), startDate: startDate, pr: pr);

List<string> repoNameList = _buildUtil.GetViewNames(filter.StartDate);
List<ProjectElapsedTimeModel> ETListOfProjects = new List<ProjectElapsedTimeModel>();
var totalCount = 0;
var totalSucceeded = 0;

foreach (var repoName in repoNameList)
{
ProjectElapsedTimeModel currRepo = new ProjectElapsedTimeModel();

currRepo.RepoName = repoName;

var results =
_buildUtil.GetBuildResults(filter.StartDate, repoName)
.Where(x => pr || !JobUtil.IsPullRequestJobName(x.JobId))
.ToList();

if (repoName == AzureUtil.ViewNameAll)
{
totalCount = results.Count;
totalSucceeded = results.Count(x => x.ClassificationKind == ClassificationKind.Succeeded);
}

var runCounts = results
.Select(x => new ElapsedTimeModel() { JobId = x.JobId, JobName = x.JobName, ElapsedTime = x.DurationSeconds })
.ToList();

foreach (var runElapsedTime in runCounts)
{
currRepo.ETSum = currRepo.ETSum + runElapsedTime.ElapsedTime;
}

//Store total elapsed time in minutes.
currRepo.ETSum = currRepo.ETSum / 60;

ETListOfProjects.Add(currRepo);
}

var model = new ProjectElapsedTimeSummaryModel()
{
Filter = filter,
TotalBuildCount = totalCount,
TotalSucceededCount = totalSucceeded,
ProjectElapsedTimeList = ETListOfProjects
};

return View(viewName: "ProjectElapsedTime", model: model);
}

public ActionResult Kind(string name = null, bool pr = false, DateTime? startDate = null, string viewName = AzureUtil.ViewNameRoslyn)
{
var filter = CreateBuildFilter(nameof(Kind), name, viewName, pr, startDate);
Expand Down Expand Up @@ -140,6 +240,94 @@ public ActionResult KindByViewName(string name = null, bool pr = false, DateTime
return View(viewName: "KindByViewName", model: model);
}

public ActionResult JobListByRepoName(string name = null, bool pr = false, DateTime? startDate = null, string viewName = AzureUtil.ViewNameAll)
{
var startDateValue = startDate ?? DateTimeOffset.UtcNow - TimeSpan.FromDays(1);
BuildFilterModel filter;
List<BuildResultEntity> results;
var totalJobCount = 0;
var totalETOfCurrRepo = 0;

//When navigating from "ProjectElapsedTime" view to "JobElapsedTime" view, var "name" is set to the repo name being selected.
//When refreshing "JobElapsedTime" view via repo name dropdown list, var "viewName" is set to the repo name, var "name" == null
if (name != null)
{
filter = CreateBuildFilter(actionName: nameof(JobListByRepoName), viewName: name, startDate: startDate, pr: pr);
results = _buildUtil
.GetBuildResults(startDateValue, name)
.Where(x => pr || !JobUtil.IsPullRequestJobName(x.JobId))
.ToList();
}
else
{
filter = CreateBuildFilter(actionName: nameof(JobListByRepoName), viewName: viewName, startDate: startDate, pr: pr);
results = _buildUtil
.GetBuildResults(startDateValue, viewName)
.Where(x => pr || !JobUtil.IsPullRequestJobName(x.JobId))
.ToList();
}

SortedDictionary<string, AgJobElapsedTime> aggregatedJobElapsedTimeDic = new SortedDictionary<string, AgJobElapsedTime>();
foreach (var entry in results)
{
string currJobName = entry.BuildId.JobName;
totalETOfCurrRepo += entry.DurationSeconds;

if (aggregatedJobElapsedTimeDic.ContainsKey(currJobName))
{
aggregatedJobElapsedTimeDic[currJobName].ETSum = aggregatedJobElapsedTimeDic[currJobName].ETSum + entry.DurationSeconds;
aggregatedJobElapsedTimeDic[currJobName].NumOfBuilds++;
}
else
{
AgJobElapsedTime newAgJobElapsedTime = new AgJobElapsedTime();
newAgJobElapsedTime.ETSum = entry.DurationSeconds;
newAgJobElapsedTime.NumOfBuilds = 1;
aggregatedJobElapsedTimeDic.Add(currJobName, newAgJobElapsedTime);
}
}

totalJobCount = aggregatedJobElapsedTimeDic.Count;

var model = new JobElapsedTimeModel()
{
Filter = filter,
TotalJobCount = totalJobCount,
TotalETOfCurrRepo = totalETOfCurrRepo,
AgJobElapsedTimeDict = aggregatedJobElapsedTimeDic
};
return View(viewName: "JobElapsedTime", model: model);
}


public ActionResult JobElapsedTimePerBuild(bool pr = false, DateTime? startDate = null, string viewName = AzureUtil.ViewNameRoslyn, string jobName = "dotnet_coreclr/master/checked_windows_nt_bld")
{
var startDateValue = startDate ?? DateTimeOffset.UtcNow - TimeSpan.FromDays(1);
var filter = CreateBuildFilter(actionName: nameof(JobElapsedTimePerBuild), viewName: viewName, startDate: startDate, pr: pr);
var results = _buildUtil
.GetBuildResults(startDateValue, viewName)
.Where(x => pr || !JobUtil.IsPullRequestJobName(x.JobId) && x.JobId.Name == jobName)
.ToList();
var buildCount = results.Count;
var totalETOfCurrJob = 0;

foreach (var entry in results)
{
totalETOfCurrJob += entry.DurationSeconds;
}

var model = new JobElapsedTimePerBuildModel()
{
Filter = filter,
TotalBuildCount = buildCount,
TotalETOfCurrJob = totalETOfCurrJob,
Entries = results
};

return View(viewName: "JobElapsedTimePerBuild", model: model);
}


public string Csv(string viewName = AzureUtil.ViewNameRoslyn, bool pr = false, DateTime? startDate = null)
{
var filter = CreateBuildFilter(nameof(Csv), viewName: viewName, pr: pr, startDate: startDate);
Expand Down
Loading

0 comments on commit 5f1dcaa

Please sign in to comment.