Skip to content
This repository was archived by the owner on Aug 15, 2023. It is now read-only.

Commit 05b415b

Browse files
authored
Merge pull request #309 from Developer-Autodesk/er/INVGEN-44551-collect-processing-stats
INVGEN-44551: collect processing stats (backend)
2 parents d7d462f + 709c12a commit 05b415b

21 files changed

+413
-111
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Autodesk.Forge.DesignAutomation.Model;
4+
using WebApplication.Definitions;
5+
using Xunit;
6+
// ReSharper disable PossibleInvalidOperationException
7+
8+
namespace WebApplication.Tests
9+
{
10+
public class StatsConversionTest
11+
{
12+
private const int CostPerHour = 6;
13+
14+
[Fact]
15+
public void Null()
16+
{
17+
Assert.Null(FdaStatsDTO.All(null));
18+
}
19+
20+
[Fact]
21+
public void Empty()
22+
{
23+
Assert.Null(FdaStatsDTO.All(new List<Statistics>()));
24+
}
25+
26+
[Fact]
27+
public void InvalidInput()
28+
{
29+
// it's expected that statistics is for successful jobs. Missing field(s) should throw an error.
30+
Assert.ThrowsAny<Exception>(() => FdaStatsDTO.All(new List<Statistics>(new []{ new Statistics() })));
31+
}
32+
33+
[Fact(DisplayName = "Statistics for single work item")]
34+
public void Single()
35+
{
36+
var stats = MakeStat(downloadOffset: 15, instructionStartedOffset: 20, instructionEndedOffset: 40, uploadOffset: 50, finishedOffset: 52);
37+
38+
var calculated = FdaStatsDTO.All(new List<Statistics>(new []{ stats }));
39+
Assert.Equal(15, calculated.Queueing.Value, 1);
40+
Assert.Equal(5, calculated.Download.Value, 1);
41+
Assert.Equal(20, calculated.Processing.Value, 1);
42+
Assert.Equal(10, calculated.Upload.Value, 1);
43+
Assert.Equal(52, calculated.Total.Value, 1);
44+
45+
// validate credits
46+
const double paidTimeSec = 35.0; // download + processing + upload
47+
const double expected = paidTimeSec * CostPerHour / 3600;
48+
Assert.Equal(expected, calculated.Credits, 4);
49+
}
50+
51+
[Fact(DisplayName = "Statistics for multiple work items")]
52+
public void Multiple()
53+
{
54+
var stats1 = MakeStat(downloadOffset: 15, instructionStartedOffset: 20, instructionEndedOffset: 40, uploadOffset: 50, finishedOffset: 52);
55+
var stats2 = MakeStat(downloadOffset: 1, instructionStartedOffset: 6, instructionEndedOffset: 22, uploadOffset: 25, finishedOffset: 33);
56+
57+
var calculated = FdaStatsDTO.All(new List<Statistics>(new []{ stats1, stats2 }));
58+
Assert.Equal(16, calculated.Queueing.Value, 1);
59+
Assert.Equal(10, calculated.Download.Value, 1);
60+
Assert.Equal(36, calculated.Processing.Value, 1);
61+
Assert.Equal(13, calculated.Upload.Value, 1);
62+
Assert.Equal(85, calculated.Total.Value, 1);
63+
64+
// validate credits
65+
const double paidTimeSec = 35.0 + 24.0; // download + processing + upload
66+
const double expected = paidTimeSec * CostPerHour / 3600;
67+
Assert.Equal(expected, calculated.Credits, 4);
68+
}
69+
70+
private static Statistics MakeStat(double downloadOffset, double instructionStartedOffset, double instructionEndedOffset, double uploadOffset, double finishedOffset)
71+
{
72+
var startTime = DateTime.UtcNow;
73+
return new Statistics
74+
{
75+
TimeQueued = startTime,
76+
TimeDownloadStarted = startTime.AddSeconds(downloadOffset),
77+
TimeInstructionsStarted = startTime.AddSeconds(instructionStartedOffset),
78+
TimeInstructionsEnded = startTime.AddSeconds(instructionEndedOffset),
79+
TimeUploadEnded = startTime.AddSeconds(uploadOffset),
80+
TimeFinished = startTime.AddSeconds(finishedOffset)
81+
};
82+
}
83+
}
84+
}

WebApplication/ClientApp/src/JobManager.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ import * as signalR from '@aspnet/signalr';
2020
import repo from './Repository';
2121

2222
class JobManager {
23-
constructor() {
24-
this.jobs = new Map();
25-
}
2623

2724
async startConnection() {
2825
const connection = new signalR.HubConnectionBuilder()
@@ -40,7 +37,7 @@ class JobManager {
4037
if (onStart)
4138
onStart();
4239

43-
connection.on("onComplete", (updatedState) => {
40+
connection.on("onComplete", (updatedState/*, stats*/) => {
4441
// stop connection
4542
connection.stop();
4643

@@ -65,7 +62,7 @@ class JobManager {
6562
if (onStart)
6663
onStart();
6764

68-
connection.on("onComplete", (newProject) => {
65+
connection.on("onComplete", (newProject/*, stats*/) => {
6966
// stop connection
7067
connection.stop();
7168

@@ -99,7 +96,7 @@ class JobManager {
9996

10097
if (onStart) onStart();
10198

102-
connection.on("onComplete", downloadUrl => {
99+
connection.on("onComplete", (downloadUrl/*, stats*/) => {
103100

104101
connection.stop();
105102

@@ -127,7 +124,7 @@ class JobManager {
127124
if (onStart)
128125
onStart();
129126

130-
connection.on("onComplete", (drawingUrl) => {
127+
connection.on("onComplete", (drawingUrl/*, stats*/) => {
131128
// stop connection
132129
connection.stop();
133130

WebApplication/Controllers/ShowParametersChangedController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public async Task<bool> Get()
6969
public async Task<bool> Set([FromBody]bool show)
7070
{
7171
var bucket = await _userResolver.GetBucketAsync();
72-
await bucket.UploadObjectAsync(ONC.ShowParametersChanged, Json.ToStream(show));
72+
await bucket.UploadAsJsonAsync(ONC.ShowParametersChanged, show);
7373
return show;
7474
}
7575
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/////////////////////////////////////////////////////////////////////
2+
// Copyright (c) Autodesk, Inc. All rights reserved
3+
// Written by Forge Design Automation team for Inventor
4+
//
5+
// Permission to use, copy, modify, and distribute this software in
6+
// object code form for any purpose and without fee is hereby granted,
7+
// provided that the above copyright notice appears in all copies and
8+
// that both that copyright notice and the limited warranty and
9+
// restricted rights notice below appear in all supporting
10+
// documentation.
11+
//
12+
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
13+
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
14+
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
15+
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
16+
// UNINTERRUPTED OR ERROR FREE.
17+
/////////////////////////////////////////////////////////////////////
18+
19+
using System.Collections.Generic;
20+
using Autodesk.Forge.DesignAutomation.Model;
21+
// ReSharper disable PossibleInvalidOperationException
22+
23+
namespace WebApplication.Definitions
24+
{
25+
public class FdaStatsDTO
26+
{
27+
private const double CreditsPerHour = 6.0; // should it be in config?
28+
private const double CreditsPerSecond = CreditsPerHour / 3600.0;
29+
30+
public double Credits { get; set; }
31+
32+
public double? Queueing { get; set; }
33+
public double? Download { get; set; }
34+
public double? Processing { get; set; }
35+
public double? Upload { get; set; }
36+
public double? Total { get; set; }
37+
38+
/// <summary>
39+
/// Generate processing statistics.
40+
/// </summary>
41+
/// <param name="stats"></param>
42+
/// <returns></returns>
43+
public static FdaStatsDTO All(ICollection<Statistics> stats)
44+
{
45+
return Convert(stats);
46+
}
47+
48+
/// <summary>
49+
/// Generate processing statistics for cached item (without timings).
50+
/// </summary>
51+
public static FdaStatsDTO CreditsOnly(ICollection<Statistics> stats)
52+
{
53+
return All(stats).ClearTimings();
54+
}
55+
56+
private static FdaStatsDTO Convert(ICollection<Statistics> stats)
57+
{
58+
if (stats == null || stats.Count == 0) return null;
59+
60+
var sum = new FdaStatsDTO();
61+
foreach (var s in stats)
62+
{
63+
var current = ConvertSingle(s);
64+
65+
sum.Queueing = sum.Queueing.GetValueOrDefault() + current.Queueing;
66+
sum.Download = sum.Download.GetValueOrDefault() + current.Download;
67+
sum.Processing = sum.Processing.GetValueOrDefault() + current.Processing;
68+
sum.Upload = sum.Upload.GetValueOrDefault() + current.Upload;
69+
sum.Total = sum.Total.GetValueOrDefault() + current.Total;
70+
71+
sum.Credits += current.Credits;
72+
}
73+
74+
return sum;
75+
}
76+
77+
private static FdaStatsDTO ConvertSingle(Statistics stats)
78+
{
79+
// it's assumed that statistics calculated for successful job, so all timings are present
80+
var downloadSec = stats.TimeInstructionsStarted.Value.Subtract(stats.TimeDownloadStarted.Value).TotalSeconds;
81+
var processingSec = stats.TimeInstructionsEnded.Value.Subtract(stats.TimeInstructionsStarted.Value).TotalSeconds;
82+
var uploadSec = stats.TimeUploadEnded.Value.Subtract(stats.TimeInstructionsEnded.Value).TotalSeconds;
83+
84+
var result = new FdaStatsDTO
85+
{
86+
Queueing = stats.TimeDownloadStarted.Value.Subtract(stats.TimeQueued).TotalSeconds,
87+
Download = downloadSec,
88+
Processing = processingSec,
89+
Upload = uploadSec,
90+
Total = stats.TimeFinished.Value.Subtract(stats.TimeQueued).TotalSeconds,
91+
Credits = (downloadSec + processingSec + uploadSec) * CreditsPerSecond
92+
};
93+
94+
return result;
95+
}
96+
97+
/// <summary>
98+
/// Remove timings.
99+
/// Used for cached jobs, where timings are not important.
100+
/// </summary>
101+
private FdaStatsDTO ClearTimings()
102+
{
103+
Queueing = null;
104+
Download = null;
105+
Processing = null;
106+
Upload = null;
107+
Total = null;
108+
109+
return this;
110+
}
111+
}
112+
}

WebApplication/Definitions/ProcessingResult.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,22 @@
1616
// UNINTERRUPTED OR ERROR FREE.
1717
/////////////////////////////////////////////////////////////////////
1818

19+
using System.Collections.Generic;
20+
using Autodesk.Forge.DesignAutomation.Model;
21+
1922
namespace WebApplication.Definitions
2023
{
2124
public class ProcessingResult
2225
{
2326
public bool Success { get; set; }
2427
public string ReportUrl { get; set; }
2528
public string ErrorMessage { get; set; }
29+
30+
public List<Statistics> Stats { get; set; } = new List<Statistics>();
31+
32+
public ProcessingResult(Statistics statistics)
33+
{
34+
Stats.Add(statistics);
35+
}
2636
}
2737
}

WebApplication/Initializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ await Task.WhenAll(
113113
string signedUrl = await waitForBucketPolicy.ExecuteAsync(async () => await _bucket.CreateSignedUrlAsync(project.OSSSourceModel, ObjectAccess.ReadWrite));
114114

115115
// TransferData from s3 to temporary oss url
116-
await _projectWork.FileTransferAsync(projectUrl,signedUrl);
116+
await _projectWork.FileTransferAsync(projectUrl, signedUrl);
117117

118118
_logger.LogInformation($"'TransferData' for {projectUrl} is done.");
119119

WebApplication/Job/AdoptJobItem.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public override async Task ProcessJobAsync(IResultSender resultSender)
4949
Logger.LogInformation($"ProcessJob (Adopt) {Id} for project {_projectInfo.Name} started.");
5050

5151
// upload the file to OSS
52-
var bucket = await _userResolver.GetBucketAsync(true);
52+
var bucket = await _userResolver.GetBucketAsync(tryToCreate: true);
5353
ProjectStorage projectStorage = await _userResolver.GetProjectStorageAsync(_projectInfo.Name);
5454

5555
string ossSourceModel = projectStorage.Project.OSSSourceModel;
@@ -61,11 +61,12 @@ public override async Task ProcessJobAsync(IResultSender resultSender)
6161

6262
// adopt the project
6363
bool adopted = false;
64+
FdaStatsDTO stats;
6465
try
6566
{
6667
string signedUploadedUrl = await bucket.CreateSignedUrlAsync(ossSourceModel);
67-
68-
await ProjectWork.AdoptAsync(_projectInfo, signedUploadedUrl);
68+
69+
stats = await ProjectWork.AdoptAsync(_projectInfo, signedUploadedUrl);
6970

7071
adopted = true;
7172
}
@@ -85,7 +86,7 @@ public override async Task ProcessJobAsync(IResultSender resultSender)
8586
}
8687

8788
Logger.LogInformation($"ProcessJob (Adopt) {Id} for project {_projectInfo.Name} completed.");
88-
await resultSender.SendSuccessAsync(_dtoGenerator.ToDTO(projectStorage));
89+
await resultSender.SendSuccessAsync(_dtoGenerator.ToDTO(projectStorage), stats);
8990
}
9091
}
9192
}

WebApplication/Job/DrawingJobItem.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.Extensions.Logging;
2020
using System.Threading.Tasks;
2121
using Microsoft.AspNetCore.Routing;
22+
using WebApplication.Definitions;
2223
using WebApplication.Processing;
2324

2425
namespace WebApplication.Job
@@ -41,7 +42,7 @@ public override async Task ProcessJobAsync(IResultSender resultSender)
4142

4243
Logger.LogInformation($"ProcessJob (Drawing) {Id} for project {ProjectId} started.");
4344

44-
await ProjectWork.GenerateDrawingAsync(ProjectId, _hash);
45+
FdaStatsDTO stats = await ProjectWork.GenerateDrawingAsync(ProjectId, _hash);
4546
Logger.LogInformation($"ProcessJob (Drawing) {Id} for project {ProjectId} completed.");
4647

4748
// TODO: this url can be generated right away... we can simply acknowledge that OSS file is ready,
@@ -51,7 +52,7 @@ public override async Task ProcessJobAsync(IResultSender resultSender)
5152
values: new { projectName = ProjectId, hash = _hash });
5253

5354
// send resulting URL to the client
54-
await resultSender.SendSuccessAsync(drawingUrl);
55+
await resultSender.SendSuccessAsync(drawingUrl, stats);
5556
}
5657
}
5758
}

WebApplication/Job/ExportDrawingPdfJobItem.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.Extensions.Logging;
2020
using System.Threading.Tasks;
2121
using Microsoft.AspNetCore.Routing;
22+
using WebApplication.Definitions;
2223
using WebApplication.Processing;
2324

2425
namespace WebApplication.Job
@@ -41,15 +42,14 @@ public ExportDrawingPdfJobItem(ILogger logger, string projectId, string hash, Pr
4142
public override async Task ProcessJobAsync(IResultSender resultSender)
4243
{
4344
using var scope = Logger.BeginScope("Export Drawing PDF ({Id})");
44-
4545
Logger.LogInformation($"ProcessJob (ExportDrawingPDF) {Id} for project {ProjectId} started.");
4646

47-
bool generated = await ProjectWork.ExportDrawingPdfAsync(ProjectId, _hash);
47+
FdaStatsDTO stats = await ProjectWork.ExportDrawingPdfAsync(ProjectId, _hash);
4848

4949
Logger.LogInformation($"ProcessJob (ExportDrawingPDF) {Id} for project {ProjectId} completed.");
5050

5151
string url = "";
52-
if (generated)
52+
if (stats != null)
5353
{
5454
url = _linkGenerator.GetPathByAction(controller: "Download",
5555
action: "DrawingViewables",
@@ -59,7 +59,7 @@ public override async Task ProcessJobAsync(IResultSender resultSender)
5959
url = url.IndexOf("/") == 0 ? url.Substring(1) : url;
6060
}
6161

62-
await resultSender.SendSuccessAsync(url);
62+
await resultSender.SendSuccessAsync(url, stats);
6363
}
6464
}
6565
}

0 commit comments

Comments
 (0)