|
| 1 | +using ApiService.OneFuzzLib.Orm; |
| 2 | +using Microsoft.Extensions.Logging; |
| 3 | +using Polly; |
| 4 | +namespace Microsoft.OneFuzz.Service; |
| 5 | + |
| 6 | +public interface IJobResultOperations : IOrm<JobResult> { |
| 7 | + |
| 8 | + Async.Task<JobResult?> GetJobResult(Guid jobId); |
| 9 | + Async.Task<OneFuzzResultVoid> CreateOrUpdate(Guid jobId, JobResultType resultType, Dictionary<string, double> resultValue); |
| 10 | + |
| 11 | +} |
| 12 | +public class JobResultOperations : Orm<JobResult>, IJobResultOperations { |
| 13 | + |
| 14 | + public JobResultOperations(ILogger<JobResultOperations> log, IOnefuzzContext context) |
| 15 | + : base(log, context) { |
| 16 | + } |
| 17 | + |
| 18 | + public async Async.Task<JobResult?> GetJobResult(Guid jobId) { |
| 19 | + return await SearchByPartitionKeys(new[] { jobId.ToString() }).SingleOrDefaultAsync(); |
| 20 | + } |
| 21 | + |
| 22 | + private JobResult UpdateResult(JobResult result, JobResultType type, Dictionary<string, double> resultValue) { |
| 23 | + |
| 24 | + var newResult = result; |
| 25 | + double newValue; |
| 26 | + switch (type) { |
| 27 | + case JobResultType.NewCrashingInput: |
| 28 | + newValue = result.NewCrashingInput + resultValue["count"]; |
| 29 | + newResult = result with { NewCrashingInput = newValue }; |
| 30 | + break; |
| 31 | + case JobResultType.NewReport: |
| 32 | + newValue = result.NewReport + resultValue["count"]; |
| 33 | + newResult = result with { NewReport = newValue }; |
| 34 | + break; |
| 35 | + case JobResultType.NewUniqueReport: |
| 36 | + newValue = result.NewUniqueReport + resultValue["count"]; |
| 37 | + newResult = result with { NewUniqueReport = newValue }; |
| 38 | + break; |
| 39 | + case JobResultType.NewRegressionReport: |
| 40 | + newValue = result.NewRegressionReport + resultValue["count"]; |
| 41 | + newResult = result with { NewRegressionReport = newValue }; |
| 42 | + break; |
| 43 | + case JobResultType.NewCrashDump: |
| 44 | + newValue = result.NewCrashDump + resultValue["count"]; |
| 45 | + newResult = result with { NewCrashDump = newValue }; |
| 46 | + break; |
| 47 | + case JobResultType.CoverageData: |
| 48 | + double newCovered = resultValue["covered"]; |
| 49 | + double newTotalCovered = resultValue["features"]; |
| 50 | + double newCoverageRate = resultValue["rate"]; |
| 51 | + newResult = result with { InstructionsCovered = newCovered, TotalInstructions = newTotalCovered, CoverageRate = newCoverageRate }; |
| 52 | + break; |
| 53 | + case JobResultType.RuntimeStats: |
| 54 | + double newTotalIterations = resultValue["total_count"]; |
| 55 | + newResult = result with { IterationCount = newTotalIterations }; |
| 56 | + break; |
| 57 | + default: |
| 58 | + _logTracer.LogWarning($"Invalid Field {type}."); |
| 59 | + break; |
| 60 | + } |
| 61 | + _logTracer.LogInformation($"Attempting to log new result: {newResult}"); |
| 62 | + return newResult; |
| 63 | + } |
| 64 | + |
| 65 | + private async Async.Task<bool> TryUpdate(Job job, JobResultType resultType, Dictionary<string, double> resultValue) { |
| 66 | + var jobId = job.JobId; |
| 67 | + |
| 68 | + var jobResult = await GetJobResult(jobId); |
| 69 | + |
| 70 | + if (jobResult == null) { |
| 71 | + _logTracer.LogInformation("Creating new JobResult for Job {JobId}", jobId); |
| 72 | + |
| 73 | + var entry = new JobResult(JobId: jobId, Project: job.Config.Project, Name: job.Config.Name); |
| 74 | + |
| 75 | + jobResult = UpdateResult(entry, resultType, resultValue); |
| 76 | + |
| 77 | + var r = await Insert(jobResult); |
| 78 | + if (!r.IsOk) { |
| 79 | + throw new InvalidOperationException($"failed to insert job result {jobResult.JobId}"); |
| 80 | + } |
| 81 | + _logTracer.LogInformation("created job result {JobId}", jobResult.JobId); |
| 82 | + } else { |
| 83 | + _logTracer.LogInformation("Updating existing JobResult entry for Job {JobId}", jobId); |
| 84 | + |
| 85 | + jobResult = UpdateResult(jobResult, resultType, resultValue); |
| 86 | + |
| 87 | + var r = await Update(jobResult); |
| 88 | + if (!r.IsOk) { |
| 89 | + throw new InvalidOperationException($"failed to insert job result {jobResult.JobId}"); |
| 90 | + } |
| 91 | + _logTracer.LogInformation("updated job result {JobId}", jobResult.JobId); |
| 92 | + } |
| 93 | + |
| 94 | + return true; |
| 95 | + } |
| 96 | + |
| 97 | + public async Async.Task<OneFuzzResultVoid> CreateOrUpdate(Guid jobId, JobResultType resultType, Dictionary<string, double> resultValue) { |
| 98 | + |
| 99 | + var job = await _context.JobOperations.Get(jobId); |
| 100 | + if (job == null) { |
| 101 | + return OneFuzzResultVoid.Error(ErrorCode.INVALID_REQUEST, "invalid job"); |
| 102 | + } |
| 103 | + |
| 104 | + var success = false; |
| 105 | + try { |
| 106 | + _logTracer.LogInformation("attempt to update job result {JobId}", job.JobId); |
| 107 | + var policy = Policy.Handle<InvalidOperationException>().WaitAndRetryAsync(50, _ => new TimeSpan(0, 0, 5)); |
| 108 | + await policy.ExecuteAsync(async () => { |
| 109 | + success = await TryUpdate(job, resultType, resultValue); |
| 110 | + _logTracer.LogInformation("attempt {success}", success); |
| 111 | + }); |
| 112 | + return OneFuzzResultVoid.Ok; |
| 113 | + } catch (Exception e) { |
| 114 | + return OneFuzzResultVoid.Error(ErrorCode.UNABLE_TO_UPDATE, new string[] { |
| 115 | + $"Unexpected failure when attempting to update job result for {job.JobId}", |
| 116 | + $"Exception: {e}" |
| 117 | + }); |
| 118 | + } |
| 119 | + } |
| 120 | +} |
| 121 | + |
0 commit comments