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

Commit 804b299

Browse files
authored
Job Result Round #2 (#3380)
* Attempting new background process. * Formatting. * Adding JobResultOperations. * Fixing. * Adding strorage tests. * Still trying. * Adding. * Adding to rest of queue. * Adding arg. * Fixing. * Updating job result. * Adding cargo lock. * Restoring queuetaskheartbeat. * Fixing job format. * Properly naming function. * Adding to program.cs. * Removing uning unneccessary code. * More small fixes. * Adding regular crash results. * Resolving issues. * More fixes. * Debugging crashing input. * Adding * Remove * Fixed. * Handling other cases. * Adding value to data. * Adding values to queue messages. * Adidng values. * Adidng await. * Adding runtimestats. * Fixing runtime_stats. * Updating types. * Fixing types. * Resolve. * Resolve type. * Updading onefuzz-result cargo. * Responding to comments. * More comments. * Removing unused params. * Respond to more comments. * Updating cargo. * Fixing return type. * UPdating keys." * UPdating return type. * Updating JobResult with constructor. * Remove debug logging. * Adding warning. * Making generic into TaskContext. * Adding crash dump. * Updating for crash dumps. * Formatting. * Updating to warn. * Formatting. * Formatting. * Borrowing new client.
1 parent bdb2f13 commit 804b299

File tree

33 files changed

+671
-40
lines changed

33 files changed

+671
-40
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Text.Json;
2+
using Microsoft.Azure.Functions.Worker;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
5+
namespace Microsoft.OneFuzz.Service.Functions;
6+
7+
8+
public class QueueJobResult {
9+
private readonly ILogger _log;
10+
private readonly IOnefuzzContext _context;
11+
12+
public QueueJobResult(ILogger<QueueJobResult> logTracer, IOnefuzzContext context) {
13+
_log = logTracer;
14+
_context = context;
15+
}
16+
17+
[Function("QueueJobResult")]
18+
public async Async.Task Run([QueueTrigger("job-result", Connection = "AzureWebJobsStorage")] string msg) {
19+
20+
var _tasks = _context.TaskOperations;
21+
var _jobs = _context.JobOperations;
22+
23+
_log.LogInformation("job result: {msg}", msg);
24+
var jr = JsonSerializer.Deserialize<TaskJobResultEntry>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}");
25+
26+
var task = await _tasks.GetByTaskId(jr.TaskId);
27+
if (task == null) {
28+
_log.LogWarning("invalid {TaskId}", jr.TaskId);
29+
return;
30+
}
31+
32+
var job = await _jobs.Get(task.JobId);
33+
if (job == null) {
34+
_log.LogWarning("invalid {JobId}", task.JobId);
35+
return;
36+
}
37+
38+
JobResultData? data = jr.Data;
39+
if (data == null) {
40+
_log.LogWarning($"job result data is empty, throwing out: {jr}");
41+
return;
42+
}
43+
44+
var jobResultType = data.Type;
45+
_log.LogInformation($"job result data type: {jobResultType}");
46+
47+
Dictionary<string, double> value;
48+
if (jr.Value.Count > 0) {
49+
value = jr.Value;
50+
} else {
51+
_log.LogWarning($"job result data is empty, throwing out: {jr}");
52+
return;
53+
}
54+
55+
var jobResult = await _context.JobResultOperations.CreateOrUpdate(job.JobId, jobResultType, value);
56+
if (!jobResult.IsOk) {
57+
_log.LogError("failed to create or update with job result {JobId}", job.JobId);
58+
}
59+
}
60+
}

src/ApiService/ApiService/OneFuzzTypes/Model.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ public enum HeartbeatType {
3333
TaskAlive,
3434
}
3535

36+
[SkipRename]
37+
public enum JobResultType {
38+
NewCrashingInput,
39+
NoReproCrashingInput,
40+
NewReport,
41+
NewUniqueReport,
42+
NewRegressionReport,
43+
NewCoverage,
44+
NewCrashDump,
45+
CoverageData,
46+
RuntimeStats,
47+
}
48+
3649
public record HeartbeatData(HeartbeatType Type);
3750

3851
public record TaskHeartbeatEntry(
@@ -41,6 +54,16 @@ public record TaskHeartbeatEntry(
4154
Guid MachineId,
4255
HeartbeatData[] Data);
4356

57+
public record JobResultData(JobResultType Type);
58+
59+
public record TaskJobResultEntry(
60+
Guid TaskId,
61+
Guid? JobId,
62+
Guid MachineId,
63+
JobResultData Data,
64+
Dictionary<string, double> Value
65+
);
66+
4467
public record NodeHeartbeatEntry(Guid NodeId, HeartbeatData[] Data);
4568

4669
public record NodeCommandStopIfFree();
@@ -892,6 +915,27 @@ public record SecretAddress<T>(Uri Url) : ISecret<T> {
892915
public record SecretData<T>(ISecret<T> Secret) {
893916
}
894917

918+
public record JobResult(
919+
[PartitionKey][RowKey] Guid JobId,
920+
string Project,
921+
string Name,
922+
double NewCrashingInput = 0,
923+
double NoReproCrashingInput = 0,
924+
double NewReport = 0,
925+
double NewUniqueReport = 0,
926+
double NewRegressionReport = 0,
927+
double NewCrashDump = 0,
928+
double InstructionsCovered = 0,
929+
double TotalInstructions = 0,
930+
double CoverageRate = 0,
931+
double IterationCount = 0
932+
) : EntityBase() {
933+
public JobResult(Guid JobId, string Project, string Name) : this(
934+
JobId: JobId,
935+
Project: Project,
936+
Name: Name, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) { }
937+
}
938+
895939
public record JobConfig(
896940
string Project,
897941
string Name,
@@ -1056,6 +1100,7 @@ public record TaskUnitConfig(
10561100
string? InstanceTelemetryKey,
10571101
string? MicrosoftTelemetryKey,
10581102
Uri HeartbeatQueue,
1103+
Uri JobResultQueue,
10591104
Dictionary<string, string> Tags
10601105
) {
10611106
public Uri? inputQueue { get; set; }

src/ApiService/ApiService/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public static async Async.Task Main() {
118118
.AddScoped<IVmOperations, VmOperations>()
119119
.AddScoped<ISecretsOperations, SecretsOperations>()
120120
.AddScoped<IJobOperations, JobOperations>()
121+
.AddScoped<IJobResultOperations, JobResultOperations>()
121122
.AddScoped<INsgOperations, NsgOperations>()
122123
.AddScoped<IScheduler, Scheduler>()
123124
.AddScoped<IConfig, Config>()

src/ApiService/ApiService/onefuzzlib/Config.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ private static BlobContainerSasPermissions ConvertPermissions(ContainerPermissio
7171
InstanceTelemetryKey: _serviceConfig.ApplicationInsightsInstrumentationKey,
7272
MicrosoftTelemetryKey: _serviceConfig.OneFuzzTelemetry,
7373
HeartbeatQueue: await _queue.GetQueueSas("task-heartbeat", StorageType.Config, QueueSasPermissions.Add) ?? throw new Exception("unable to get heartbeat queue sas"),
74+
JobResultQueue: await _queue.GetQueueSas("job-result", StorageType.Config, QueueSasPermissions.Add) ?? throw new Exception("unable to get heartbeat queue sas"),
7475
Tags: task.Config.Tags ?? new Dictionary<string, string>()
7576
);
7677

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
_logTracer.AddHttpStatus(r.ErrorV);
80+
_logTracer.LogError("failed to insert job result {JobId}", jobResult.JobId);
81+
throw new InvalidOperationException($"failed to insert job result {jobResult.JobId}");
82+
}
83+
_logTracer.LogInformation("created job result {JobId}", jobResult.JobId);
84+
} else {
85+
_logTracer.LogInformation("Updating existing JobResult entry for Job {JobId}", jobId);
86+
87+
jobResult = UpdateResult(jobResult, resultType, resultValue);
88+
89+
var r = await Update(jobResult);
90+
if (!r.IsOk) {
91+
_logTracer.AddHttpStatus(r.ErrorV);
92+
_logTracer.LogError("failed to update job result {JobId}", jobResult.JobId);
93+
throw new InvalidOperationException($"failed to insert job result {jobResult.JobId}");
94+
}
95+
_logTracer.LogInformation("updated job result {JobId}", jobResult.JobId);
96+
}
97+
98+
return true;
99+
}
100+
101+
public async Async.Task<OneFuzzResultVoid> CreateOrUpdate(Guid jobId, JobResultType resultType, Dictionary<string, double> resultValue) {
102+
103+
var job = await _context.JobOperations.Get(jobId);
104+
if (job == null) {
105+
return OneFuzzResultVoid.Error(ErrorCode.INVALID_REQUEST, "invalid job");
106+
}
107+
108+
var success = false;
109+
try {
110+
_logTracer.LogInformation("attempt to update job result {JobId}", job.JobId);
111+
var policy = Policy.Handle<InvalidOperationException>().WaitAndRetryAsync(50, _ => new TimeSpan(0, 0, 5));
112+
await policy.ExecuteAsync(async () => {
113+
success = await TryUpdate(job, resultType, resultValue);
114+
_logTracer.LogInformation("attempt {success}", success);
115+
});
116+
return OneFuzzResultVoid.Ok;
117+
} catch (Exception e) {
118+
return OneFuzzResultVoid.Error(ErrorCode.UNABLE_TO_UPDATE, new string[] {
119+
$"Unexpected failure when attempting to update job result for {job.JobId}",
120+
$"Exception: {e}"
121+
});
122+
}
123+
}
124+
}
125+

src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public interface IOnefuzzContext {
1919
IExtensions Extensions { get; }
2020
IIpOperations IpOperations { get; }
2121
IJobOperations JobOperations { get; }
22+
IJobResultOperations JobResultOperations { get; }
2223
ILogAnalytics LogAnalytics { get; }
2324
INodeMessageOperations NodeMessageOperations { get; }
2425
INodeOperations NodeOperations { get; }
@@ -83,6 +84,7 @@ public OnefuzzContext(IServiceProvider serviceProvider) {
8384
public IVmOperations VmOperations => _serviceProvider.GetRequiredService<IVmOperations>();
8485
public ISecretsOperations SecretsOperations => _serviceProvider.GetRequiredService<ISecretsOperations>();
8586
public IJobOperations JobOperations => _serviceProvider.GetRequiredService<IJobOperations>();
87+
public IJobResultOperations JobResultOperations => _serviceProvider.GetRequiredService<IJobResultOperations>();
8688
public IScheduler Scheduler => _serviceProvider.GetRequiredService<IScheduler>();
8789
public IConfig Config => _serviceProvider.GetRequiredService<IConfig>();
8890
public ILogAnalytics LogAnalytics => _serviceProvider.GetRequiredService<ILogAnalytics>();

src/ApiService/IntegrationTests/Fakes/TestContext.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public TestContext(IHttpClientFactory httpClientFactory, OneFuzzLoggerProvider p
3232
TaskOperations = new TaskOperations(provider.CreateLogger<TaskOperations>(), Cache, this);
3333
NodeOperations = new NodeOperations(provider.CreateLogger<NodeOperations>(), this);
3434
JobOperations = new JobOperations(provider.CreateLogger<JobOperations>(), this);
35+
JobResultOperations = new JobResultOperations(provider.CreateLogger<JobResultOperations>(), this);
3536
NodeTasksOperations = new NodeTasksOperations(provider.CreateLogger<NodeTasksOperations>(), this);
3637
TaskEventOperations = new TaskEventOperations(provider.CreateLogger<TaskEventOperations>(), this);
3738
NodeMessageOperations = new NodeMessageOperations(provider.CreateLogger<NodeMessageOperations>(), this);
@@ -57,6 +58,7 @@ public Async.Task InsertAll(params EntityBase[] objs)
5758
Node n => NodeOperations.Insert(n),
5859
Pool p => PoolOperations.Insert(p),
5960
Job j => JobOperations.Insert(j),
61+
JobResult jr => JobResultOperations.Insert(jr),
6062
Repro r => ReproOperations.Insert(r),
6163
Scaleset ss => ScalesetOperations.Insert(ss),
6264
NodeTasks nt => NodeTasksOperations.Insert(nt),
@@ -84,6 +86,7 @@ public Async.Task InsertAll(params EntityBase[] objs)
8486

8587
public ITaskOperations TaskOperations { get; }
8688
public IJobOperations JobOperations { get; }
89+
public IJobResultOperations JobResultOperations { get; }
8790
public INodeOperations NodeOperations { get; }
8891
public INodeTasksOperations NodeTasksOperations { get; }
8992
public ITaskEventOperations TaskEventOperations { get; }

src/agent/Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/agent/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ members = [
1010
"onefuzz",
1111
"onefuzz-task",
1212
"onefuzz-agent",
13+
"onefuzz-result",
1314
"onefuzz-file-format",
1415
"onefuzz-telemetry",
1516
"reqwest-retry",

0 commit comments

Comments
 (0)