mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-17 12:28:07 +00:00
* 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.
This commit is contained in:
committed by
GitHub
parent
bdb2f1337d
commit
804b2993b7
60
src/ApiService/ApiService/Functions/QueueJobResult.cs
Normal file
60
src/ApiService/ApiService/Functions/QueueJobResult.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Azure.Functions.Worker;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
|
|
||||||
|
public class QueueJobResult {
|
||||||
|
private readonly ILogger _log;
|
||||||
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
|
public QueueJobResult(ILogger<QueueJobResult> logTracer, IOnefuzzContext context) {
|
||||||
|
_log = logTracer;
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Function("QueueJobResult")]
|
||||||
|
public async Async.Task Run([QueueTrigger("job-result", Connection = "AzureWebJobsStorage")] string msg) {
|
||||||
|
|
||||||
|
var _tasks = _context.TaskOperations;
|
||||||
|
var _jobs = _context.JobOperations;
|
||||||
|
|
||||||
|
_log.LogInformation("job result: {msg}", msg);
|
||||||
|
var jr = JsonSerializer.Deserialize<TaskJobResultEntry>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}");
|
||||||
|
|
||||||
|
var task = await _tasks.GetByTaskId(jr.TaskId);
|
||||||
|
if (task == null) {
|
||||||
|
_log.LogWarning("invalid {TaskId}", jr.TaskId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var job = await _jobs.Get(task.JobId);
|
||||||
|
if (job == null) {
|
||||||
|
_log.LogWarning("invalid {JobId}", task.JobId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JobResultData? data = jr.Data;
|
||||||
|
if (data == null) {
|
||||||
|
_log.LogWarning($"job result data is empty, throwing out: {jr}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jobResultType = data.Type;
|
||||||
|
_log.LogInformation($"job result data type: {jobResultType}");
|
||||||
|
|
||||||
|
Dictionary<string, double> value;
|
||||||
|
if (jr.Value.Count > 0) {
|
||||||
|
value = jr.Value;
|
||||||
|
} else {
|
||||||
|
_log.LogWarning($"job result data is empty, throwing out: {jr}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jobResult = await _context.JobResultOperations.CreateOrUpdate(job.JobId, jobResultType, value);
|
||||||
|
if (!jobResult.IsOk) {
|
||||||
|
_log.LogError("failed to create or update with job result {JobId}", job.JobId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,19 @@ public enum HeartbeatType {
|
|||||||
TaskAlive,
|
TaskAlive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SkipRename]
|
||||||
|
public enum JobResultType {
|
||||||
|
NewCrashingInput,
|
||||||
|
NoReproCrashingInput,
|
||||||
|
NewReport,
|
||||||
|
NewUniqueReport,
|
||||||
|
NewRegressionReport,
|
||||||
|
NewCoverage,
|
||||||
|
NewCrashDump,
|
||||||
|
CoverageData,
|
||||||
|
RuntimeStats,
|
||||||
|
}
|
||||||
|
|
||||||
public record HeartbeatData(HeartbeatType Type);
|
public record HeartbeatData(HeartbeatType Type);
|
||||||
|
|
||||||
public record TaskHeartbeatEntry(
|
public record TaskHeartbeatEntry(
|
||||||
@ -41,6 +54,16 @@ public record TaskHeartbeatEntry(
|
|||||||
Guid MachineId,
|
Guid MachineId,
|
||||||
HeartbeatData[] Data);
|
HeartbeatData[] Data);
|
||||||
|
|
||||||
|
public record JobResultData(JobResultType Type);
|
||||||
|
|
||||||
|
public record TaskJobResultEntry(
|
||||||
|
Guid TaskId,
|
||||||
|
Guid? JobId,
|
||||||
|
Guid MachineId,
|
||||||
|
JobResultData Data,
|
||||||
|
Dictionary<string, double> Value
|
||||||
|
);
|
||||||
|
|
||||||
public record NodeHeartbeatEntry(Guid NodeId, HeartbeatData[] Data);
|
public record NodeHeartbeatEntry(Guid NodeId, HeartbeatData[] Data);
|
||||||
|
|
||||||
public record NodeCommandStopIfFree();
|
public record NodeCommandStopIfFree();
|
||||||
@ -892,6 +915,27 @@ public record SecretAddress<T>(Uri Url) : ISecret<T> {
|
|||||||
public record SecretData<T>(ISecret<T> Secret) {
|
public record SecretData<T>(ISecret<T> Secret) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record JobResult(
|
||||||
|
[PartitionKey][RowKey] Guid JobId,
|
||||||
|
string Project,
|
||||||
|
string Name,
|
||||||
|
double NewCrashingInput = 0,
|
||||||
|
double NoReproCrashingInput = 0,
|
||||||
|
double NewReport = 0,
|
||||||
|
double NewUniqueReport = 0,
|
||||||
|
double NewRegressionReport = 0,
|
||||||
|
double NewCrashDump = 0,
|
||||||
|
double InstructionsCovered = 0,
|
||||||
|
double TotalInstructions = 0,
|
||||||
|
double CoverageRate = 0,
|
||||||
|
double IterationCount = 0
|
||||||
|
) : EntityBase() {
|
||||||
|
public JobResult(Guid JobId, string Project, string Name) : this(
|
||||||
|
JobId: JobId,
|
||||||
|
Project: Project,
|
||||||
|
Name: Name, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) { }
|
||||||
|
}
|
||||||
|
|
||||||
public record JobConfig(
|
public record JobConfig(
|
||||||
string Project,
|
string Project,
|
||||||
string Name,
|
string Name,
|
||||||
@ -1056,6 +1100,7 @@ public record TaskUnitConfig(
|
|||||||
string? InstanceTelemetryKey,
|
string? InstanceTelemetryKey,
|
||||||
string? MicrosoftTelemetryKey,
|
string? MicrosoftTelemetryKey,
|
||||||
Uri HeartbeatQueue,
|
Uri HeartbeatQueue,
|
||||||
|
Uri JobResultQueue,
|
||||||
Dictionary<string, string> Tags
|
Dictionary<string, string> Tags
|
||||||
) {
|
) {
|
||||||
public Uri? inputQueue { get; set; }
|
public Uri? inputQueue { get; set; }
|
||||||
|
@ -118,6 +118,7 @@ public class Program {
|
|||||||
.AddScoped<IVmOperations, VmOperations>()
|
.AddScoped<IVmOperations, VmOperations>()
|
||||||
.AddScoped<ISecretsOperations, SecretsOperations>()
|
.AddScoped<ISecretsOperations, SecretsOperations>()
|
||||||
.AddScoped<IJobOperations, JobOperations>()
|
.AddScoped<IJobOperations, JobOperations>()
|
||||||
|
.AddScoped<IJobResultOperations, JobResultOperations>()
|
||||||
.AddScoped<INsgOperations, NsgOperations>()
|
.AddScoped<INsgOperations, NsgOperations>()
|
||||||
.AddScoped<IScheduler, Scheduler>()
|
.AddScoped<IScheduler, Scheduler>()
|
||||||
.AddScoped<IConfig, Config>()
|
.AddScoped<IConfig, Config>()
|
||||||
|
@ -71,6 +71,7 @@ public class Config : IConfig {
|
|||||||
InstanceTelemetryKey: _serviceConfig.ApplicationInsightsInstrumentationKey,
|
InstanceTelemetryKey: _serviceConfig.ApplicationInsightsInstrumentationKey,
|
||||||
MicrosoftTelemetryKey: _serviceConfig.OneFuzzTelemetry,
|
MicrosoftTelemetryKey: _serviceConfig.OneFuzzTelemetry,
|
||||||
HeartbeatQueue: await _queue.GetQueueSas("task-heartbeat", StorageType.Config, QueueSasPermissions.Add) ?? throw new Exception("unable to get heartbeat queue sas"),
|
HeartbeatQueue: await _queue.GetQueueSas("task-heartbeat", StorageType.Config, QueueSasPermissions.Add) ?? throw new Exception("unable to get heartbeat queue sas"),
|
||||||
|
JobResultQueue: await _queue.GetQueueSas("job-result", StorageType.Config, QueueSasPermissions.Add) ?? throw new Exception("unable to get heartbeat queue sas"),
|
||||||
Tags: task.Config.Tags ?? new Dictionary<string, string>()
|
Tags: task.Config.Tags ?? new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
125
src/ApiService/ApiService/onefuzzlib/JobResultOperations.cs
Normal file
125
src/ApiService/ApiService/onefuzzlib/JobResultOperations.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
using ApiService.OneFuzzLib.Orm;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Polly;
|
||||||
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
public interface IJobResultOperations : IOrm<JobResult> {
|
||||||
|
|
||||||
|
Async.Task<JobResult?> GetJobResult(Guid jobId);
|
||||||
|
Async.Task<OneFuzzResultVoid> CreateOrUpdate(Guid jobId, JobResultType resultType, Dictionary<string, double> resultValue);
|
||||||
|
|
||||||
|
}
|
||||||
|
public class JobResultOperations : Orm<JobResult>, IJobResultOperations {
|
||||||
|
|
||||||
|
public JobResultOperations(ILogger<JobResultOperations> log, IOnefuzzContext context)
|
||||||
|
: base(log, context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Async.Task<JobResult?> GetJobResult(Guid jobId) {
|
||||||
|
return await SearchByPartitionKeys(new[] { jobId.ToString() }).SingleOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobResult UpdateResult(JobResult result, JobResultType type, Dictionary<string, double> resultValue) {
|
||||||
|
|
||||||
|
var newResult = result;
|
||||||
|
double newValue;
|
||||||
|
switch (type) {
|
||||||
|
case JobResultType.NewCrashingInput:
|
||||||
|
newValue = result.NewCrashingInput + resultValue["count"];
|
||||||
|
newResult = result with { NewCrashingInput = newValue };
|
||||||
|
break;
|
||||||
|
case JobResultType.NewReport:
|
||||||
|
newValue = result.NewReport + resultValue["count"];
|
||||||
|
newResult = result with { NewReport = newValue };
|
||||||
|
break;
|
||||||
|
case JobResultType.NewUniqueReport:
|
||||||
|
newValue = result.NewUniqueReport + resultValue["count"];
|
||||||
|
newResult = result with { NewUniqueReport = newValue };
|
||||||
|
break;
|
||||||
|
case JobResultType.NewRegressionReport:
|
||||||
|
newValue = result.NewRegressionReport + resultValue["count"];
|
||||||
|
newResult = result with { NewRegressionReport = newValue };
|
||||||
|
break;
|
||||||
|
case JobResultType.NewCrashDump:
|
||||||
|
newValue = result.NewCrashDump + resultValue["count"];
|
||||||
|
newResult = result with { NewCrashDump = newValue };
|
||||||
|
break;
|
||||||
|
case JobResultType.CoverageData:
|
||||||
|
double newCovered = resultValue["covered"];
|
||||||
|
double newTotalCovered = resultValue["features"];
|
||||||
|
double newCoverageRate = resultValue["rate"];
|
||||||
|
newResult = result with { InstructionsCovered = newCovered, TotalInstructions = newTotalCovered, CoverageRate = newCoverageRate };
|
||||||
|
break;
|
||||||
|
case JobResultType.RuntimeStats:
|
||||||
|
double newTotalIterations = resultValue["total_count"];
|
||||||
|
newResult = result with { IterationCount = newTotalIterations };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_logTracer.LogWarning($"Invalid Field {type}.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_logTracer.LogInformation($"Attempting to log new result: {newResult}");
|
||||||
|
return newResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Async.Task<bool> TryUpdate(Job job, JobResultType resultType, Dictionary<string, double> resultValue) {
|
||||||
|
var jobId = job.JobId;
|
||||||
|
|
||||||
|
var jobResult = await GetJobResult(jobId);
|
||||||
|
|
||||||
|
if (jobResult == null) {
|
||||||
|
_logTracer.LogInformation("Creating new JobResult for Job {JobId}", jobId);
|
||||||
|
|
||||||
|
var entry = new JobResult(JobId: jobId, Project: job.Config.Project, Name: job.Config.Name);
|
||||||
|
|
||||||
|
jobResult = UpdateResult(entry, resultType, resultValue);
|
||||||
|
|
||||||
|
var r = await Insert(jobResult);
|
||||||
|
if (!r.IsOk) {
|
||||||
|
_logTracer.AddHttpStatus(r.ErrorV);
|
||||||
|
_logTracer.LogError("failed to insert job result {JobId}", jobResult.JobId);
|
||||||
|
throw new InvalidOperationException($"failed to insert job result {jobResult.JobId}");
|
||||||
|
}
|
||||||
|
_logTracer.LogInformation("created job result {JobId}", jobResult.JobId);
|
||||||
|
} else {
|
||||||
|
_logTracer.LogInformation("Updating existing JobResult entry for Job {JobId}", jobId);
|
||||||
|
|
||||||
|
jobResult = UpdateResult(jobResult, resultType, resultValue);
|
||||||
|
|
||||||
|
var r = await Update(jobResult);
|
||||||
|
if (!r.IsOk) {
|
||||||
|
_logTracer.AddHttpStatus(r.ErrorV);
|
||||||
|
_logTracer.LogError("failed to update job result {JobId}", jobResult.JobId);
|
||||||
|
throw new InvalidOperationException($"failed to insert job result {jobResult.JobId}");
|
||||||
|
}
|
||||||
|
_logTracer.LogInformation("updated job result {JobId}", jobResult.JobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Async.Task<OneFuzzResultVoid> CreateOrUpdate(Guid jobId, JobResultType resultType, Dictionary<string, double> resultValue) {
|
||||||
|
|
||||||
|
var job = await _context.JobOperations.Get(jobId);
|
||||||
|
if (job == null) {
|
||||||
|
return OneFuzzResultVoid.Error(ErrorCode.INVALID_REQUEST, "invalid job");
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
try {
|
||||||
|
_logTracer.LogInformation("attempt to update job result {JobId}", job.JobId);
|
||||||
|
var policy = Policy.Handle<InvalidOperationException>().WaitAndRetryAsync(50, _ => new TimeSpan(0, 0, 5));
|
||||||
|
await policy.ExecuteAsync(async () => {
|
||||||
|
success = await TryUpdate(job, resultType, resultValue);
|
||||||
|
_logTracer.LogInformation("attempt {success}", success);
|
||||||
|
});
|
||||||
|
return OneFuzzResultVoid.Ok;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return OneFuzzResultVoid.Error(ErrorCode.UNABLE_TO_UPDATE, new string[] {
|
||||||
|
$"Unexpected failure when attempting to update job result for {job.JobId}",
|
||||||
|
$"Exception: {e}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ public interface IOnefuzzContext {
|
|||||||
IExtensions Extensions { get; }
|
IExtensions Extensions { get; }
|
||||||
IIpOperations IpOperations { get; }
|
IIpOperations IpOperations { get; }
|
||||||
IJobOperations JobOperations { get; }
|
IJobOperations JobOperations { get; }
|
||||||
|
IJobResultOperations JobResultOperations { get; }
|
||||||
ILogAnalytics LogAnalytics { get; }
|
ILogAnalytics LogAnalytics { get; }
|
||||||
INodeMessageOperations NodeMessageOperations { get; }
|
INodeMessageOperations NodeMessageOperations { get; }
|
||||||
INodeOperations NodeOperations { get; }
|
INodeOperations NodeOperations { get; }
|
||||||
@ -83,6 +84,7 @@ public class OnefuzzContext : IOnefuzzContext {
|
|||||||
public IVmOperations VmOperations => _serviceProvider.GetRequiredService<IVmOperations>();
|
public IVmOperations VmOperations => _serviceProvider.GetRequiredService<IVmOperations>();
|
||||||
public ISecretsOperations SecretsOperations => _serviceProvider.GetRequiredService<ISecretsOperations>();
|
public ISecretsOperations SecretsOperations => _serviceProvider.GetRequiredService<ISecretsOperations>();
|
||||||
public IJobOperations JobOperations => _serviceProvider.GetRequiredService<IJobOperations>();
|
public IJobOperations JobOperations => _serviceProvider.GetRequiredService<IJobOperations>();
|
||||||
|
public IJobResultOperations JobResultOperations => _serviceProvider.GetRequiredService<IJobResultOperations>();
|
||||||
public IScheduler Scheduler => _serviceProvider.GetRequiredService<IScheduler>();
|
public IScheduler Scheduler => _serviceProvider.GetRequiredService<IScheduler>();
|
||||||
public IConfig Config => _serviceProvider.GetRequiredService<IConfig>();
|
public IConfig Config => _serviceProvider.GetRequiredService<IConfig>();
|
||||||
public ILogAnalytics LogAnalytics => _serviceProvider.GetRequiredService<ILogAnalytics>();
|
public ILogAnalytics LogAnalytics => _serviceProvider.GetRequiredService<ILogAnalytics>();
|
||||||
|
@ -32,6 +32,7 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
TaskOperations = new TaskOperations(provider.CreateLogger<TaskOperations>(), Cache, this);
|
TaskOperations = new TaskOperations(provider.CreateLogger<TaskOperations>(), Cache, this);
|
||||||
NodeOperations = new NodeOperations(provider.CreateLogger<NodeOperations>(), this);
|
NodeOperations = new NodeOperations(provider.CreateLogger<NodeOperations>(), this);
|
||||||
JobOperations = new JobOperations(provider.CreateLogger<JobOperations>(), this);
|
JobOperations = new JobOperations(provider.CreateLogger<JobOperations>(), this);
|
||||||
|
JobResultOperations = new JobResultOperations(provider.CreateLogger<JobResultOperations>(), this);
|
||||||
NodeTasksOperations = new NodeTasksOperations(provider.CreateLogger<NodeTasksOperations>(), this);
|
NodeTasksOperations = new NodeTasksOperations(provider.CreateLogger<NodeTasksOperations>(), this);
|
||||||
TaskEventOperations = new TaskEventOperations(provider.CreateLogger<TaskEventOperations>(), this);
|
TaskEventOperations = new TaskEventOperations(provider.CreateLogger<TaskEventOperations>(), this);
|
||||||
NodeMessageOperations = new NodeMessageOperations(provider.CreateLogger<NodeMessageOperations>(), this);
|
NodeMessageOperations = new NodeMessageOperations(provider.CreateLogger<NodeMessageOperations>(), this);
|
||||||
@ -57,6 +58,7 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
Node n => NodeOperations.Insert(n),
|
Node n => NodeOperations.Insert(n),
|
||||||
Pool p => PoolOperations.Insert(p),
|
Pool p => PoolOperations.Insert(p),
|
||||||
Job j => JobOperations.Insert(j),
|
Job j => JobOperations.Insert(j),
|
||||||
|
JobResult jr => JobResultOperations.Insert(jr),
|
||||||
Repro r => ReproOperations.Insert(r),
|
Repro r => ReproOperations.Insert(r),
|
||||||
Scaleset ss => ScalesetOperations.Insert(ss),
|
Scaleset ss => ScalesetOperations.Insert(ss),
|
||||||
NodeTasks nt => NodeTasksOperations.Insert(nt),
|
NodeTasks nt => NodeTasksOperations.Insert(nt),
|
||||||
@ -84,6 +86,7 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
|
|
||||||
public ITaskOperations TaskOperations { get; }
|
public ITaskOperations TaskOperations { get; }
|
||||||
public IJobOperations JobOperations { get; }
|
public IJobOperations JobOperations { get; }
|
||||||
|
public IJobResultOperations JobResultOperations { get; }
|
||||||
public INodeOperations NodeOperations { get; }
|
public INodeOperations NodeOperations { get; }
|
||||||
public INodeTasksOperations NodeTasksOperations { get; }
|
public INodeTasksOperations NodeTasksOperations { get; }
|
||||||
public ITaskEventOperations TaskEventOperations { get; }
|
public ITaskEventOperations TaskEventOperations { get; }
|
||||||
|
16
src/agent/Cargo.lock
generated
16
src/agent/Cargo.lock
generated
@ -2123,6 +2123,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"nix",
|
"nix",
|
||||||
"notify",
|
"notify",
|
||||||
|
"onefuzz-result",
|
||||||
"onefuzz-telemetry",
|
"onefuzz-telemetry",
|
||||||
"pete",
|
"pete",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
@ -2197,6 +2198,20 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "onefuzz-result"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"log",
|
||||||
|
"onefuzz-telemetry",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"storage-queue",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onefuzz-task"
|
name = "onefuzz-task"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -2226,6 +2241,7 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
"onefuzz",
|
"onefuzz",
|
||||||
"onefuzz-file-format",
|
"onefuzz-file-format",
|
||||||
|
"onefuzz-result",
|
||||||
"onefuzz-telemetry",
|
"onefuzz-telemetry",
|
||||||
"path-absolutize",
|
"path-absolutize",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
@ -10,6 +10,7 @@ members = [
|
|||||||
"onefuzz",
|
"onefuzz",
|
||||||
"onefuzz-task",
|
"onefuzz-task",
|
||||||
"onefuzz-agent",
|
"onefuzz-agent",
|
||||||
|
"onefuzz-result",
|
||||||
"onefuzz-file-format",
|
"onefuzz-file-format",
|
||||||
"onefuzz-telemetry",
|
"onefuzz-telemetry",
|
||||||
"reqwest-retry",
|
"reqwest-retry",
|
||||||
|
@ -34,6 +34,8 @@ pub struct StaticConfig {
|
|||||||
|
|
||||||
pub heartbeat_queue: Option<Url>,
|
pub heartbeat_queue: Option<Url>,
|
||||||
|
|
||||||
|
pub job_result_queue: Option<Url>,
|
||||||
|
|
||||||
pub instance_id: Uuid,
|
pub instance_id: Uuid,
|
||||||
|
|
||||||
#[serde(default = "default_as_true")]
|
#[serde(default = "default_as_true")]
|
||||||
@ -71,6 +73,8 @@ struct RawStaticConfig {
|
|||||||
|
|
||||||
pub heartbeat_queue: Option<Url>,
|
pub heartbeat_queue: Option<Url>,
|
||||||
|
|
||||||
|
pub job_result_queue: Option<Url>,
|
||||||
|
|
||||||
pub instance_id: Uuid,
|
pub instance_id: Uuid,
|
||||||
|
|
||||||
#[serde(default = "default_as_true")]
|
#[serde(default = "default_as_true")]
|
||||||
@ -117,6 +121,7 @@ impl StaticConfig {
|
|||||||
microsoft_telemetry_key: config.microsoft_telemetry_key,
|
microsoft_telemetry_key: config.microsoft_telemetry_key,
|
||||||
instance_telemetry_key: config.instance_telemetry_key,
|
instance_telemetry_key: config.instance_telemetry_key,
|
||||||
heartbeat_queue: config.heartbeat_queue,
|
heartbeat_queue: config.heartbeat_queue,
|
||||||
|
job_result_queue: config.job_result_queue,
|
||||||
instance_id: config.instance_id,
|
instance_id: config.instance_id,
|
||||||
managed: config.managed,
|
managed: config.managed,
|
||||||
machine_identity,
|
machine_identity,
|
||||||
@ -152,6 +157,12 @@ impl StaticConfig {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let job_result_queue = if let Ok(key) = std::env::var("ONEFUZZ_JOB_RESULT") {
|
||||||
|
Some(Url::parse(&key)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let instance_telemetry_key =
|
let instance_telemetry_key =
|
||||||
if let Ok(key) = std::env::var("ONEFUZZ_INSTANCE_TELEMETRY_KEY") {
|
if let Ok(key) = std::env::var("ONEFUZZ_INSTANCE_TELEMETRY_KEY") {
|
||||||
Some(InstanceTelemetryKey::new(Uuid::parse_str(&key)?))
|
Some(InstanceTelemetryKey::new(Uuid::parse_str(&key)?))
|
||||||
@ -183,6 +194,7 @@ impl StaticConfig {
|
|||||||
instance_telemetry_key,
|
instance_telemetry_key,
|
||||||
microsoft_telemetry_key,
|
microsoft_telemetry_key,
|
||||||
heartbeat_queue,
|
heartbeat_queue,
|
||||||
|
job_result_queue,
|
||||||
instance_id,
|
instance_id,
|
||||||
managed: !is_unmanaged,
|
managed: !is_unmanaged,
|
||||||
machine_identity,
|
machine_identity,
|
||||||
|
18
src/agent/onefuzz-result/Cargo.toml
Normal file
18
src/agent/onefuzz-result/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "onefuzz-result"
|
||||||
|
version = "0.2.0"
|
||||||
|
authors = ["fuzzing@microsoft.com"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||||
|
async-trait = "0.1"
|
||||||
|
reqwest = "0.11"
|
||||||
|
serde = "1.0"
|
||||||
|
storage-queue = { path = "../storage-queue" }
|
||||||
|
uuid = { version = "1.4", features = ["serde", "v4"] }
|
||||||
|
onefuzz-telemetry = { path = "../onefuzz-telemetry" }
|
||||||
|
log = "0.4"
|
||||||
|
|
129
src/agent/onefuzz-result/src/job_result.rs
Normal file
129
src/agent/onefuzz-result/src/job_result.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use onefuzz_telemetry::warn;
|
||||||
|
use reqwest::Url;
|
||||||
|
use serde::{self, Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use storage_queue::QueueClient;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Hash, Eq, PartialEq, Clone)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum JobResultData {
|
||||||
|
NewCrashingInput,
|
||||||
|
NoReproCrashingInput,
|
||||||
|
NewReport,
|
||||||
|
NewUniqueReport,
|
||||||
|
NewRegressionReport,
|
||||||
|
NewCoverage,
|
||||||
|
NewCrashDump,
|
||||||
|
CoverageData,
|
||||||
|
RuntimeStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
struct JobResult {
|
||||||
|
task_id: Uuid,
|
||||||
|
job_id: Uuid,
|
||||||
|
machine_id: Uuid,
|
||||||
|
machine_name: String,
|
||||||
|
data: JobResultData,
|
||||||
|
value: HashMap<String, f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TaskContext {
|
||||||
|
task_id: Uuid,
|
||||||
|
job_id: Uuid,
|
||||||
|
machine_id: Uuid,
|
||||||
|
machine_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JobResultContext<TaskContext> {
|
||||||
|
pub state: TaskContext,
|
||||||
|
pub queue_client: QueueClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JobResultClient<TaskContext> {
|
||||||
|
pub context: Arc<JobResultContext<TaskContext>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TaskContext> JobResultClient<TaskContext> {
|
||||||
|
pub fn init_job_result(
|
||||||
|
context: TaskContext,
|
||||||
|
queue_url: Url,
|
||||||
|
) -> Result<JobResultClient<TaskContext>>
|
||||||
|
where
|
||||||
|
TaskContext: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let context = Arc::new(JobResultContext {
|
||||||
|
state: context,
|
||||||
|
queue_client: QueueClient::new(queue_url)?,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(JobResultClient { context })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type TaskJobResultClient = JobResultClient<TaskContext>;
|
||||||
|
|
||||||
|
pub async fn init_job_result(
|
||||||
|
queue_url: Url,
|
||||||
|
task_id: Uuid,
|
||||||
|
job_id: Uuid,
|
||||||
|
machine_id: Uuid,
|
||||||
|
machine_name: String,
|
||||||
|
) -> Result<TaskJobResultClient> {
|
||||||
|
let hb = JobResultClient::init_job_result(
|
||||||
|
TaskContext {
|
||||||
|
task_id,
|
||||||
|
job_id,
|
||||||
|
machine_id,
|
||||||
|
machine_name,
|
||||||
|
},
|
||||||
|
queue_url,
|
||||||
|
)?;
|
||||||
|
Ok(hb)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait JobResultSender {
|
||||||
|
async fn send_direct(&self, data: JobResultData, value: HashMap<String, f64>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl JobResultSender for TaskJobResultClient {
|
||||||
|
async fn send_direct(&self, data: JobResultData, value: HashMap<String, f64>) {
|
||||||
|
let task_id = self.context.state.task_id;
|
||||||
|
let job_id = self.context.state.job_id;
|
||||||
|
let machine_id = self.context.state.machine_id;
|
||||||
|
let machine_name = self.context.state.machine_name.clone();
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
.context
|
||||||
|
.queue_client
|
||||||
|
.enqueue(JobResult {
|
||||||
|
task_id,
|
||||||
|
job_id,
|
||||||
|
machine_id,
|
||||||
|
machine_name,
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl JobResultSender for Option<TaskJobResultClient> {
|
||||||
|
async fn send_direct(&self, data: JobResultData, value: HashMap<String, f64>) {
|
||||||
|
match self {
|
||||||
|
Some(client) => client.send_direct(data, value).await,
|
||||||
|
None => warn!("Failed to send Job Result message data from agent."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
src/agent/onefuzz-result/src/lib.rs
Normal file
4
src/agent/onefuzz-result/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
pub mod job_result;
|
@ -39,6 +39,7 @@ serde_json = "1.0"
|
|||||||
serde_yaml = "0.9.21"
|
serde_yaml = "0.9.21"
|
||||||
onefuzz = { path = "../onefuzz" }
|
onefuzz = { path = "../onefuzz" }
|
||||||
onefuzz-telemetry = { path = "../onefuzz-telemetry" }
|
onefuzz-telemetry = { path = "../onefuzz-telemetry" }
|
||||||
|
onefuzz-result = { path = "../onefuzz-result" }
|
||||||
path-absolutize = "3.1"
|
path-absolutize = "3.1"
|
||||||
reqwest-retry = { path = "../reqwest-retry" }
|
reqwest-retry = { path = "../reqwest-retry" }
|
||||||
strum = "0.25"
|
strum = "0.25"
|
||||||
|
@ -265,6 +265,7 @@ pub async fn build_local_context(
|
|||||||
},
|
},
|
||||||
instance_telemetry_key: None,
|
instance_telemetry_key: None,
|
||||||
heartbeat_queue: None,
|
heartbeat_queue: None,
|
||||||
|
job_result_queue: None,
|
||||||
microsoft_telemetry_key: None,
|
microsoft_telemetry_key: None,
|
||||||
logs: None,
|
logs: None,
|
||||||
min_available_memory_mb: 0,
|
min_available_memory_mb: 0,
|
||||||
|
@ -196,6 +196,7 @@ pub async fn launch(
|
|||||||
job_id: Uuid::new_v4(),
|
job_id: Uuid::new_v4(),
|
||||||
instance_id: Uuid::new_v4(),
|
instance_id: Uuid::new_v4(),
|
||||||
heartbeat_queue: None,
|
heartbeat_queue: None,
|
||||||
|
job_result_queue: None,
|
||||||
instance_telemetry_key: None,
|
instance_telemetry_key: None,
|
||||||
microsoft_telemetry_key: None,
|
microsoft_telemetry_key: None,
|
||||||
logs: None,
|
logs: None,
|
||||||
|
@ -65,6 +65,8 @@ pub async fn run(config: Config) -> Result<()> {
|
|||||||
tools.init_pull().await?;
|
tools.init_pull().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let job_result_client = config.common.init_job_result().await?;
|
||||||
|
|
||||||
// the tempdir is always created, however, the reports_path and
|
// the tempdir is always created, however, the reports_path and
|
||||||
// reports_monitor_future are only created if we have one of the three
|
// reports_monitor_future are only created if we have one of the three
|
||||||
// report SyncedDir. The idea is that the option for where to write reports
|
// report SyncedDir. The idea is that the option for where to write reports
|
||||||
@ -88,6 +90,7 @@ pub async fn run(config: Config) -> Result<()> {
|
|||||||
&config.unique_reports,
|
&config.unique_reports,
|
||||||
&config.reports,
|
&config.reports,
|
||||||
&config.no_repro,
|
&config.no_repro,
|
||||||
|
&job_result_client,
|
||||||
);
|
);
|
||||||
(
|
(
|
||||||
Some(reports_dir.path().to_path_buf()),
|
Some(reports_dir.path().to_path_buf()),
|
||||||
|
@ -14,6 +14,7 @@ use onefuzz::{
|
|||||||
machine_id::MachineIdentity,
|
machine_id::MachineIdentity,
|
||||||
syncdir::{SyncOperation, SyncedDir},
|
syncdir::{SyncOperation, SyncedDir},
|
||||||
};
|
};
|
||||||
|
use onefuzz_result::job_result::{init_job_result, TaskJobResultClient};
|
||||||
use onefuzz_telemetry::{
|
use onefuzz_telemetry::{
|
||||||
self as telemetry, Event::task_start, EventData, InstanceTelemetryKey, MicrosoftTelemetryKey,
|
self as telemetry, Event::task_start, EventData, InstanceTelemetryKey, MicrosoftTelemetryKey,
|
||||||
Role,
|
Role,
|
||||||
@ -50,6 +51,8 @@ pub struct CommonConfig {
|
|||||||
|
|
||||||
pub heartbeat_queue: Option<Url>,
|
pub heartbeat_queue: Option<Url>,
|
||||||
|
|
||||||
|
pub job_result_queue: Option<Url>,
|
||||||
|
|
||||||
pub instance_telemetry_key: Option<InstanceTelemetryKey>,
|
pub instance_telemetry_key: Option<InstanceTelemetryKey>,
|
||||||
|
|
||||||
pub microsoft_telemetry_key: Option<MicrosoftTelemetryKey>,
|
pub microsoft_telemetry_key: Option<MicrosoftTelemetryKey>,
|
||||||
@ -103,6 +106,23 @@ impl CommonConfig {
|
|||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn init_job_result(&self) -> Result<Option<TaskJobResultClient>> {
|
||||||
|
match &self.job_result_queue {
|
||||||
|
Some(url) => {
|
||||||
|
let result = init_job_result(
|
||||||
|
url.clone(),
|
||||||
|
self.task_id,
|
||||||
|
self.job_id,
|
||||||
|
self.machine_identity.machine_id,
|
||||||
|
self.machine_identity.machine_name.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(Some(result))
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -26,6 +26,8 @@ use onefuzz_file_format::coverage::{
|
|||||||
binary::{v1::BinaryCoverageJson as BinaryCoverageJsonV1, BinaryCoverageJson},
|
binary::{v1::BinaryCoverageJson as BinaryCoverageJsonV1, BinaryCoverageJson},
|
||||||
source::{v1::SourceCoverageJson as SourceCoverageJsonV1, SourceCoverageJson},
|
source::{v1::SourceCoverageJson as SourceCoverageJsonV1, SourceCoverageJson},
|
||||||
};
|
};
|
||||||
|
use onefuzz_result::job_result::JobResultData;
|
||||||
|
use onefuzz_result::job_result::{JobResultSender, TaskJobResultClient};
|
||||||
use onefuzz_telemetry::{event, warn, Event::coverage_data, Event::coverage_failed, EventData};
|
use onefuzz_telemetry::{event, warn, Event::coverage_data, Event::coverage_failed, EventData};
|
||||||
use storage_queue::{Message, QueueClient};
|
use storage_queue::{Message, QueueClient};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
@ -114,7 +116,7 @@ impl CoverageTask {
|
|||||||
let allowlist = self.load_target_allowlist().await?;
|
let allowlist = self.load_target_allowlist().await?;
|
||||||
|
|
||||||
let heartbeat = self.config.common.init_heartbeat(None).await?;
|
let heartbeat = self.config.common.init_heartbeat(None).await?;
|
||||||
|
let job_result = self.config.common.init_job_result().await?;
|
||||||
let mut seen_inputs = false;
|
let mut seen_inputs = false;
|
||||||
|
|
||||||
let target_exe_path =
|
let target_exe_path =
|
||||||
@ -129,6 +131,7 @@ impl CoverageTask {
|
|||||||
coverage,
|
coverage,
|
||||||
allowlist,
|
allowlist,
|
||||||
heartbeat,
|
heartbeat,
|
||||||
|
job_result,
|
||||||
target_exe.to_string(),
|
target_exe.to_string(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -219,6 +222,7 @@ struct TaskContext<'a> {
|
|||||||
module_allowlist: AllowList,
|
module_allowlist: AllowList,
|
||||||
source_allowlist: Arc<AllowList>,
|
source_allowlist: Arc<AllowList>,
|
||||||
heartbeat: Option<TaskHeartbeatClient>,
|
heartbeat: Option<TaskHeartbeatClient>,
|
||||||
|
job_result: Option<TaskJobResultClient>,
|
||||||
cache: Arc<DebugInfoCache>,
|
cache: Arc<DebugInfoCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +232,7 @@ impl<'a> TaskContext<'a> {
|
|||||||
coverage: BinaryCoverage,
|
coverage: BinaryCoverage,
|
||||||
allowlist: TargetAllowList,
|
allowlist: TargetAllowList,
|
||||||
heartbeat: Option<TaskHeartbeatClient>,
|
heartbeat: Option<TaskHeartbeatClient>,
|
||||||
|
job_result: Option<TaskJobResultClient>,
|
||||||
target_exe: String,
|
target_exe: String,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let cache = DebugInfoCache::new(allowlist.source_files.clone());
|
let cache = DebugInfoCache::new(allowlist.source_files.clone());
|
||||||
@ -247,6 +252,7 @@ impl<'a> TaskContext<'a> {
|
|||||||
module_allowlist: allowlist.modules,
|
module_allowlist: allowlist.modules,
|
||||||
source_allowlist: Arc::new(allowlist.source_files),
|
source_allowlist: Arc::new(allowlist.source_files),
|
||||||
heartbeat,
|
heartbeat,
|
||||||
|
job_result,
|
||||||
cache: Arc::new(cache),
|
cache: Arc::new(cache),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -455,7 +461,16 @@ impl<'a> TaskContext<'a> {
|
|||||||
let s = CoverageStats::new(&coverage);
|
let s = CoverageStats::new(&coverage);
|
||||||
event!(coverage_data; Covered = s.covered, Features = s.features, Rate = s.rate);
|
event!(coverage_data; Covered = s.covered, Features = s.features, Rate = s.rate);
|
||||||
metric!(coverage_data; 1.0; Covered = s.covered, Features = s.features, Rate = s.rate);
|
metric!(coverage_data; 1.0; Covered = s.covered, Features = s.features, Rate = s.rate);
|
||||||
|
self.job_result
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::CoverageData,
|
||||||
|
HashMap::from([
|
||||||
|
("covered".to_string(), s.covered as f64),
|
||||||
|
("features".to_string(), s.features as f64),
|
||||||
|
("rate".to_string(), s.rate),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ impl GeneratorTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hb_client = self.config.common.init_heartbeat(None).await?;
|
let hb_client = self.config.common.init_heartbeat(None).await?;
|
||||||
|
let jr_client = self.config.common.init_job_result().await?;
|
||||||
|
|
||||||
for dir in &self.config.readonly_inputs {
|
for dir in &self.config.readonly_inputs {
|
||||||
dir.init_pull().await?;
|
dir.init_pull().await?;
|
||||||
@ -84,7 +85,10 @@ impl GeneratorTask {
|
|||||||
self.config.ensemble_sync_delay,
|
self.config.ensemble_sync_delay,
|
||||||
);
|
);
|
||||||
|
|
||||||
let crash_dir_monitor = self.config.crashes.monitor_results(new_result, false);
|
let crash_dir_monitor = self
|
||||||
|
.config
|
||||||
|
.crashes
|
||||||
|
.monitor_results(new_result, false, &jr_client);
|
||||||
|
|
||||||
let fuzzer = self.fuzzing_loop(hb_client);
|
let fuzzer = self.fuzzing_loop(hb_client);
|
||||||
|
|
||||||
@ -298,6 +302,7 @@ mod tests {
|
|||||||
task_id: Default::default(),
|
task_id: Default::default(),
|
||||||
instance_id: Default::default(),
|
instance_id: Default::default(),
|
||||||
heartbeat_queue: Default::default(),
|
heartbeat_queue: Default::default(),
|
||||||
|
job_result_queue: Default::default(),
|
||||||
instance_telemetry_key: Default::default(),
|
instance_telemetry_key: Default::default(),
|
||||||
microsoft_telemetry_key: Default::default(),
|
microsoft_telemetry_key: Default::default(),
|
||||||
logs: Default::default(),
|
logs: Default::default(),
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use crate::tasks::{config::CommonConfig, heartbeat::HeartbeatSender, utils::default_bool_true};
|
use crate::tasks::{
|
||||||
|
config::CommonConfig,
|
||||||
|
heartbeat::{HeartbeatSender, TaskHeartbeatClient},
|
||||||
|
utils::default_bool_true,
|
||||||
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use arraydeque::{ArrayDeque, Wrapping};
|
use arraydeque::{ArrayDeque, Wrapping};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -12,6 +16,7 @@ use onefuzz::{
|
|||||||
process::ExitStatus,
|
process::ExitStatus,
|
||||||
syncdir::{continuous_sync, SyncOperation::Pull, SyncedDir},
|
syncdir::{continuous_sync, SyncOperation::Pull, SyncedDir},
|
||||||
};
|
};
|
||||||
|
use onefuzz_result::job_result::{JobResultData, JobResultSender, TaskJobResultClient};
|
||||||
use onefuzz_telemetry::{
|
use onefuzz_telemetry::{
|
||||||
Event::{new_coverage, new_crashdump, new_result, runtime_stats},
|
Event::{new_coverage, new_crashdump, new_result, runtime_stats},
|
||||||
EventData,
|
EventData,
|
||||||
@ -126,21 +131,31 @@ where
|
|||||||
self.verify().await?;
|
self.verify().await?;
|
||||||
|
|
||||||
let hb_client = self.config.common.init_heartbeat(None).await?;
|
let hb_client = self.config.common.init_heartbeat(None).await?;
|
||||||
|
let jr_client = self.config.common.init_job_result().await?;
|
||||||
|
|
||||||
// To be scheduled.
|
// To be scheduled.
|
||||||
let resync = self.continuous_sync_inputs();
|
let resync = self.continuous_sync_inputs();
|
||||||
let new_inputs = self.config.inputs.monitor_results(new_coverage, true);
|
|
||||||
let new_crashes = self.config.crashes.monitor_results(new_result, true);
|
let new_inputs = self
|
||||||
|
.config
|
||||||
|
.inputs
|
||||||
|
.monitor_results(new_coverage, true, &jr_client);
|
||||||
|
let new_crashes = self
|
||||||
|
.config
|
||||||
|
.crashes
|
||||||
|
.monitor_results(new_result, true, &jr_client);
|
||||||
let new_crashdumps = async {
|
let new_crashdumps = async {
|
||||||
if let Some(crashdumps) = &self.config.crashdumps {
|
if let Some(crashdumps) = &self.config.crashdumps {
|
||||||
crashdumps.monitor_results(new_crashdump, true).await
|
crashdumps
|
||||||
|
.monitor_results(new_crashdump, true, &jr_client)
|
||||||
|
.await
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (stats_sender, stats_receiver) = mpsc::unbounded_channel();
|
let (stats_sender, stats_receiver) = mpsc::unbounded_channel();
|
||||||
let report_stats = report_runtime_stats(stats_receiver, hb_client);
|
let report_stats = report_runtime_stats(stats_receiver, &hb_client, &jr_client);
|
||||||
let fuzzers = self.run_fuzzers(Some(&stats_sender));
|
let fuzzers = self.run_fuzzers(Some(&stats_sender));
|
||||||
futures::try_join!(
|
futures::try_join!(
|
||||||
resync,
|
resync,
|
||||||
@ -183,7 +198,7 @@ where
|
|||||||
.inputs
|
.inputs
|
||||||
.local_path
|
.local_path
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or_else(|| anyhow!("Invalid input path"))?;
|
.ok_or_else(|| anyhow!("invalid input path"))?;
|
||||||
let temp_path = task_dir.join(".temp");
|
let temp_path = task_dir.join(".temp");
|
||||||
tokio::fs::create_dir_all(&temp_path).await?;
|
tokio::fs::create_dir_all(&temp_path).await?;
|
||||||
let temp_dir = tempdir_in(temp_path)?;
|
let temp_dir = tempdir_in(temp_path)?;
|
||||||
@ -501,7 +516,7 @@ impl TotalStats {
|
|||||||
self.execs_sec = self.worker_stats.values().map(|x| x.execs_sec).sum();
|
self.execs_sec = self.worker_stats.values().map(|x| x.execs_sec).sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report(&self) {
|
async fn report(&self, jr_client: &Option<TaskJobResultClient>) {
|
||||||
event!(
|
event!(
|
||||||
runtime_stats;
|
runtime_stats;
|
||||||
EventData::Count = self.count,
|
EventData::Count = self.count,
|
||||||
@ -513,6 +528,17 @@ impl TotalStats {
|
|||||||
EventData::Count = self.count,
|
EventData::Count = self.count,
|
||||||
EventData::ExecsSecond = self.execs_sec
|
EventData::ExecsSecond = self.execs_sec
|
||||||
);
|
);
|
||||||
|
if let Some(jr_client) = jr_client {
|
||||||
|
let _ = jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::RuntimeStats,
|
||||||
|
HashMap::from([
|
||||||
|
("total_count".to_string(), self.count as f64),
|
||||||
|
("execs_sec".to_string(), self.execs_sec),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,7 +568,8 @@ impl Timer {
|
|||||||
// are approximating nearest-neighbor interpolation on the runtime stats time series.
|
// are approximating nearest-neighbor interpolation on the runtime stats time series.
|
||||||
async fn report_runtime_stats(
|
async fn report_runtime_stats(
|
||||||
mut stats_channel: mpsc::UnboundedReceiver<RuntimeStats>,
|
mut stats_channel: mpsc::UnboundedReceiver<RuntimeStats>,
|
||||||
heartbeat_client: impl HeartbeatSender,
|
heartbeat_client: &Option<TaskHeartbeatClient>,
|
||||||
|
jr_client: &Option<TaskJobResultClient>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Cache the last-reported stats for a given worker.
|
// Cache the last-reported stats for a given worker.
|
||||||
//
|
//
|
||||||
@ -551,7 +578,7 @@ async fn report_runtime_stats(
|
|||||||
let mut total = TotalStats::default();
|
let mut total = TotalStats::default();
|
||||||
|
|
||||||
// report all zeros to start
|
// report all zeros to start
|
||||||
total.report();
|
total.report(jr_client).await;
|
||||||
|
|
||||||
let timer = Timer::new(RUNTIME_STATS_PERIOD);
|
let timer = Timer::new(RUNTIME_STATS_PERIOD);
|
||||||
|
|
||||||
@ -560,10 +587,10 @@ async fn report_runtime_stats(
|
|||||||
Some(stats) = stats_channel.recv() => {
|
Some(stats) = stats_channel.recv() => {
|
||||||
heartbeat_client.alive();
|
heartbeat_client.alive();
|
||||||
total.update(stats);
|
total.update(stats);
|
||||||
total.report()
|
total.report(jr_client).await
|
||||||
}
|
}
|
||||||
_ = timer.wait() => {
|
_ = timer.wait() => {
|
||||||
total.report()
|
total.report(jr_client).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,10 @@ pub async fn spawn(config: SupervisorConfig) -> Result<(), Error> {
|
|||||||
remote_path: config.crashes.remote_path.clone(),
|
remote_path: config.crashes.remote_path.clone(),
|
||||||
};
|
};
|
||||||
crashes.init().await?;
|
crashes.init().await?;
|
||||||
let monitor_crashes = crashes.monitor_results(new_result, false);
|
|
||||||
|
let jr_client = config.common.init_job_result().await?;
|
||||||
|
|
||||||
|
let monitor_crashes = crashes.monitor_results(new_result, false, &jr_client);
|
||||||
|
|
||||||
// setup crashdumps
|
// setup crashdumps
|
||||||
let (crashdump_dir, monitor_crashdumps) = {
|
let (crashdump_dir, monitor_crashdumps) = {
|
||||||
@ -95,9 +98,12 @@ pub async fn spawn(config: SupervisorConfig) -> Result<(), Error> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let monitor_dir = crashdump_dir.clone();
|
let monitor_dir = crashdump_dir.clone();
|
||||||
|
let monitor_jr_client = config.common.init_job_result().await?;
|
||||||
let monitor_crashdumps = async move {
|
let monitor_crashdumps = async move {
|
||||||
if let Some(crashdumps) = monitor_dir {
|
if let Some(crashdumps) = monitor_dir {
|
||||||
crashdumps.monitor_results(new_crashdump, false).await
|
crashdumps
|
||||||
|
.monitor_results(new_crashdump, false, &monitor_jr_client)
|
||||||
|
.await
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -129,11 +135,13 @@ pub async fn spawn(config: SupervisorConfig) -> Result<(), Error> {
|
|||||||
if let Some(no_repro) = &config.no_repro {
|
if let Some(no_repro) = &config.no_repro {
|
||||||
no_repro.init().await?;
|
no_repro.init().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let monitor_reports_future = monitor_reports(
|
let monitor_reports_future = monitor_reports(
|
||||||
reports_dir.path(),
|
reports_dir.path(),
|
||||||
&config.unique_reports,
|
&config.unique_reports,
|
||||||
&config.reports,
|
&config.reports,
|
||||||
&config.no_repro,
|
&config.no_repro,
|
||||||
|
&jr_client,
|
||||||
);
|
);
|
||||||
|
|
||||||
let inputs = SyncedDir {
|
let inputs = SyncedDir {
|
||||||
@ -156,7 +164,7 @@ pub async fn spawn(config: SupervisorConfig) -> Result<(), Error> {
|
|||||||
delay_with_jitter(delay).await;
|
delay_with_jitter(delay).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let monitor_inputs = inputs.monitor_results(new_coverage, false);
|
let monitor_inputs = inputs.monitor_results(new_coverage, false, &jr_client);
|
||||||
let inputs_sync_cancellation = CancellationToken::new(); // never actually cancelled
|
let inputs_sync_cancellation = CancellationToken::new(); // never actually cancelled
|
||||||
let inputs_sync_task =
|
let inputs_sync_task =
|
||||||
inputs.continuous_sync(Pull, config.ensemble_sync_delay, &inputs_sync_cancellation);
|
inputs.continuous_sync(Pull, config.ensemble_sync_delay, &inputs_sync_cancellation);
|
||||||
@ -444,6 +452,7 @@ mod tests {
|
|||||||
task_id: Default::default(),
|
task_id: Default::default(),
|
||||||
instance_id: Default::default(),
|
instance_id: Default::default(),
|
||||||
heartbeat_queue: Default::default(),
|
heartbeat_queue: Default::default(),
|
||||||
|
job_result_queue: Default::default(),
|
||||||
instance_telemetry_key: Default::default(),
|
instance_telemetry_key: Default::default(),
|
||||||
microsoft_telemetry_key: Default::default(),
|
microsoft_telemetry_key: Default::default(),
|
||||||
logs: Default::default(),
|
logs: Default::default(),
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use crate::onefuzz::heartbeat::HeartbeatClient;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use onefuzz::heartbeat::HeartbeatClient;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::{self, Deserialize, Serialize};
|
use serde::{self, Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use crate::tasks::{
|
use crate::tasks::{
|
||||||
|
config::CommonConfig,
|
||||||
heartbeat::{HeartbeatSender, TaskHeartbeatClient},
|
heartbeat::{HeartbeatSender, TaskHeartbeatClient},
|
||||||
report::crash_report::{parse_report_file, CrashTestResult, RegressionReport},
|
report::crash_report::{parse_report_file, CrashTestResult, RegressionReport},
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use onefuzz::syncdir::SyncedDir;
|
use onefuzz::syncdir::SyncedDir;
|
||||||
|
use onefuzz_result::job_result::TaskJobResultClient;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@ -24,7 +26,7 @@ pub trait RegressionHandler {
|
|||||||
|
|
||||||
/// Runs the regression task
|
/// Runs the regression task
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
heartbeat_client: Option<TaskHeartbeatClient>,
|
common_config: &CommonConfig,
|
||||||
regression_reports: &SyncedDir,
|
regression_reports: &SyncedDir,
|
||||||
crashes: &SyncedDir,
|
crashes: &SyncedDir,
|
||||||
report_dirs: &[&SyncedDir],
|
report_dirs: &[&SyncedDir],
|
||||||
@ -35,6 +37,9 @@ pub async fn run(
|
|||||||
info!("starting regression task");
|
info!("starting regression task");
|
||||||
regression_reports.init().await?;
|
regression_reports.init().await?;
|
||||||
|
|
||||||
|
let heartbeat_client = common_config.init_heartbeat(None).await?;
|
||||||
|
let job_result_client = common_config.init_job_result().await?;
|
||||||
|
|
||||||
handle_crash_reports(
|
handle_crash_reports(
|
||||||
handler,
|
handler,
|
||||||
crashes,
|
crashes,
|
||||||
@ -42,6 +47,7 @@ pub async fn run(
|
|||||||
report_list,
|
report_list,
|
||||||
regression_reports,
|
regression_reports,
|
||||||
&heartbeat_client,
|
&heartbeat_client,
|
||||||
|
&job_result_client,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("handling crash reports")?;
|
.context("handling crash reports")?;
|
||||||
@ -52,6 +58,7 @@ pub async fn run(
|
|||||||
readonly_inputs,
|
readonly_inputs,
|
||||||
regression_reports,
|
regression_reports,
|
||||||
&heartbeat_client,
|
&heartbeat_client,
|
||||||
|
&job_result_client,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("handling inputs")?;
|
.context("handling inputs")?;
|
||||||
@ -71,6 +78,7 @@ pub async fn handle_inputs(
|
|||||||
readonly_inputs: &SyncedDir,
|
readonly_inputs: &SyncedDir,
|
||||||
regression_reports: &SyncedDir,
|
regression_reports: &SyncedDir,
|
||||||
heartbeat_client: &Option<TaskHeartbeatClient>,
|
heartbeat_client: &Option<TaskHeartbeatClient>,
|
||||||
|
job_result_client: &Option<TaskJobResultClient>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
readonly_inputs.init_pull().await?;
|
readonly_inputs.init_pull().await?;
|
||||||
let mut input_files = tokio::fs::read_dir(&readonly_inputs.local_path).await?;
|
let mut input_files = tokio::fs::read_dir(&readonly_inputs.local_path).await?;
|
||||||
@ -95,7 +103,7 @@ pub async fn handle_inputs(
|
|||||||
crash_test_result,
|
crash_test_result,
|
||||||
original_crash_test_result: None,
|
original_crash_test_result: None,
|
||||||
}
|
}
|
||||||
.save(None, regression_reports)
|
.save(None, regression_reports, job_result_client)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +117,7 @@ pub async fn handle_crash_reports(
|
|||||||
report_list: &Option<Vec<String>>,
|
report_list: &Option<Vec<String>>,
|
||||||
regression_reports: &SyncedDir,
|
regression_reports: &SyncedDir,
|
||||||
heartbeat_client: &Option<TaskHeartbeatClient>,
|
heartbeat_client: &Option<TaskHeartbeatClient>,
|
||||||
|
job_result_client: &Option<TaskJobResultClient>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// without crash report containers, skip this method
|
// without crash report containers, skip this method
|
||||||
if report_dirs.is_empty() {
|
if report_dirs.is_empty() {
|
||||||
@ -158,7 +167,7 @@ pub async fn handle_crash_reports(
|
|||||||
crash_test_result,
|
crash_test_result,
|
||||||
original_crash_test_result: Some(original_crash_test_result),
|
original_crash_test_result: Some(original_crash_test_result),
|
||||||
}
|
}
|
||||||
.save(Some(file_name), regression_reports)
|
.save(Some(file_name), regression_reports, job_result_client)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,6 @@ impl GenericRegressionTask {
|
|||||||
|
|
||||||
pub async fn run(&self) -> Result<()> {
|
pub async fn run(&self) -> Result<()> {
|
||||||
info!("Starting generic regression task");
|
info!("Starting generic regression task");
|
||||||
let heartbeat_client = self.config.common.init_heartbeat(None).await?;
|
|
||||||
|
|
||||||
let mut report_dirs = vec![];
|
let mut report_dirs = vec![];
|
||||||
for dir in vec![
|
for dir in vec![
|
||||||
@ -103,7 +102,7 @@ impl GenericRegressionTask {
|
|||||||
report_dirs.push(dir);
|
report_dirs.push(dir);
|
||||||
}
|
}
|
||||||
common::run(
|
common::run(
|
||||||
heartbeat_client,
|
&self.config.common,
|
||||||
&self.config.regression_reports,
|
&self.config.regression_reports,
|
||||||
&self.config.crashes,
|
&self.config.crashes,
|
||||||
&report_dirs,
|
&report_dirs,
|
||||||
|
@ -103,9 +103,8 @@ impl LibFuzzerRegressionTask {
|
|||||||
report_dirs.push(dir);
|
report_dirs.push(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
let heartbeat_client = self.config.common.init_heartbeat(None).await?;
|
|
||||||
common::run(
|
common::run(
|
||||||
heartbeat_client,
|
&self.config.common,
|
||||||
&self.config.regression_reports,
|
&self.config.regression_reports,
|
||||||
&self.config.crashes,
|
&self.config.crashes,
|
||||||
&report_dirs,
|
&report_dirs,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use onefuzz::{blob::BlobUrl, monitor::DirectoryMonitor, syncdir::SyncedDir};
|
use onefuzz::{blob::BlobUrl, monitor::DirectoryMonitor, syncdir::SyncedDir};
|
||||||
|
use onefuzz_result::job_result::{JobResultData, JobResultSender, TaskJobResultClient};
|
||||||
use onefuzz_telemetry::{
|
use onefuzz_telemetry::{
|
||||||
Event::{
|
Event::{
|
||||||
new_report, new_unable_to_reproduce, new_unique_report, regression_report,
|
new_report, new_unable_to_reproduce, new_unique_report, regression_report,
|
||||||
@ -12,6 +13,7 @@ use onefuzz_telemetry::{
|
|||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use stacktrace_parser::CrashLog;
|
use stacktrace_parser::CrashLog;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -111,6 +113,7 @@ impl RegressionReport {
|
|||||||
self,
|
self,
|
||||||
report_name: Option<String>,
|
report_name: Option<String>,
|
||||||
regression_reports: &SyncedDir,
|
regression_reports: &SyncedDir,
|
||||||
|
jr_client: &Option<TaskJobResultClient>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (event, name) = match &self.crash_test_result {
|
let (event, name) = match &self.crash_test_result {
|
||||||
CrashTestResult::CrashReport(report) => {
|
CrashTestResult::CrashReport(report) => {
|
||||||
@ -126,6 +129,15 @@ impl RegressionReport {
|
|||||||
if upload_or_save_local(&self, &name, regression_reports).await? {
|
if upload_or_save_local(&self, &name, regression_reports).await? {
|
||||||
event!(event; EventData::Path = name.clone());
|
event!(event; EventData::Path = name.clone());
|
||||||
metric!(event; 1.0; EventData::Path = name.clone());
|
metric!(event; 1.0; EventData::Path = name.clone());
|
||||||
|
|
||||||
|
if let Some(jr_client) = jr_client {
|
||||||
|
let _ = jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::NewRegressionReport,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -149,6 +161,7 @@ impl CrashTestResult {
|
|||||||
unique_reports: &Option<SyncedDir>,
|
unique_reports: &Option<SyncedDir>,
|
||||||
reports: &Option<SyncedDir>,
|
reports: &Option<SyncedDir>,
|
||||||
no_repro: &Option<SyncedDir>,
|
no_repro: &Option<SyncedDir>,
|
||||||
|
jr_client: &Option<TaskJobResultClient>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::CrashReport(report) => {
|
Self::CrashReport(report) => {
|
||||||
@ -158,6 +171,15 @@ impl CrashTestResult {
|
|||||||
if upload_or_save_local(&report, &name, unique_reports).await? {
|
if upload_or_save_local(&report, &name, unique_reports).await? {
|
||||||
event!(new_unique_report; EventData::Path = report.unique_blob_name());
|
event!(new_unique_report; EventData::Path = report.unique_blob_name());
|
||||||
metric!(new_unique_report; 1.0; EventData::Path = report.unique_blob_name());
|
metric!(new_unique_report; 1.0; EventData::Path = report.unique_blob_name());
|
||||||
|
|
||||||
|
if let Some(jr_client) = jr_client {
|
||||||
|
let _ = jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::NewUniqueReport,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +188,15 @@ impl CrashTestResult {
|
|||||||
if upload_or_save_local(&report, &name, reports).await? {
|
if upload_or_save_local(&report, &name, reports).await? {
|
||||||
event!(new_report; EventData::Path = report.blob_name());
|
event!(new_report; EventData::Path = report.blob_name());
|
||||||
metric!(new_report; 1.0; EventData::Path = report.blob_name());
|
metric!(new_report; 1.0; EventData::Path = report.blob_name());
|
||||||
|
|
||||||
|
if let Some(jr_client) = jr_client {
|
||||||
|
let _ = jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::NewReport,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,6 +207,15 @@ impl CrashTestResult {
|
|||||||
if upload_or_save_local(&report, &name, no_repro).await? {
|
if upload_or_save_local(&report, &name, no_repro).await? {
|
||||||
event!(new_unable_to_reproduce; EventData::Path = report.blob_name());
|
event!(new_unable_to_reproduce; EventData::Path = report.blob_name());
|
||||||
metric!(new_unable_to_reproduce; 1.0; EventData::Path = report.blob_name());
|
metric!(new_unable_to_reproduce; 1.0; EventData::Path = report.blob_name());
|
||||||
|
|
||||||
|
if let Some(jr_client) = jr_client {
|
||||||
|
let _ = jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::NoReproCrashingInput,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,6 +364,7 @@ pub async fn monitor_reports(
|
|||||||
unique_reports: &Option<SyncedDir>,
|
unique_reports: &Option<SyncedDir>,
|
||||||
reports: &Option<SyncedDir>,
|
reports: &Option<SyncedDir>,
|
||||||
no_crash: &Option<SyncedDir>,
|
no_crash: &Option<SyncedDir>,
|
||||||
|
jr_client: &Option<TaskJobResultClient>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if unique_reports.is_none() && reports.is_none() && no_crash.is_none() {
|
if unique_reports.is_none() && reports.is_none() && no_crash.is_none() {
|
||||||
debug!("no report directories configured");
|
debug!("no report directories configured");
|
||||||
@ -334,7 +375,9 @@ pub async fn monitor_reports(
|
|||||||
|
|
||||||
while let Some(file) = monitor.next_file().await? {
|
while let Some(file) = monitor.next_file().await? {
|
||||||
let result = parse_report_file(file).await?;
|
let result = parse_report_file(file).await?;
|
||||||
result.save(unique_reports, reports, no_crash).await?;
|
result
|
||||||
|
.save(unique_reports, reports, no_crash, jr_client)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -8,16 +8,6 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use onefuzz::expand::Expand;
|
|
||||||
use onefuzz::fs::set_executable;
|
|
||||||
use onefuzz::{blob::BlobUrl, sha256, syncdir::SyncedDir};
|
|
||||||
use reqwest::Url;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use storage_queue::{Message, QueueClient};
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use crate::tasks::report::crash_report::*;
|
use crate::tasks::report::crash_report::*;
|
||||||
use crate::tasks::report::dotnet::common::collect_exception_info;
|
use crate::tasks::report::dotnet::common::collect_exception_info;
|
||||||
use crate::tasks::{
|
use crate::tasks::{
|
||||||
@ -26,6 +16,16 @@ use crate::tasks::{
|
|||||||
heartbeat::{HeartbeatSender, TaskHeartbeatClient},
|
heartbeat::{HeartbeatSender, TaskHeartbeatClient},
|
||||||
utils::{default_bool_true, try_resolve_setup_relative_path},
|
utils::{default_bool_true, try_resolve_setup_relative_path},
|
||||||
};
|
};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use onefuzz::expand::Expand;
|
||||||
|
use onefuzz::fs::set_executable;
|
||||||
|
use onefuzz::{blob::BlobUrl, sha256, syncdir::SyncedDir};
|
||||||
|
use onefuzz_result::job_result::TaskJobResultClient;
|
||||||
|
use reqwest::Url;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use storage_queue::{Message, QueueClient};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
const DOTNET_DUMP_TOOL_NAME: &str = "dotnet-dump";
|
const DOTNET_DUMP_TOOL_NAME: &str = "dotnet-dump";
|
||||||
|
|
||||||
@ -114,15 +114,18 @@ impl DotnetCrashReportTask {
|
|||||||
pub struct AsanProcessor {
|
pub struct AsanProcessor {
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
heartbeat_client: Option<TaskHeartbeatClient>,
|
heartbeat_client: Option<TaskHeartbeatClient>,
|
||||||
|
job_result_client: Option<TaskJobResultClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsanProcessor {
|
impl AsanProcessor {
|
||||||
pub async fn new(config: Arc<Config>) -> Result<Self> {
|
pub async fn new(config: Arc<Config>) -> Result<Self> {
|
||||||
let heartbeat_client = config.common.init_heartbeat(None).await?;
|
let heartbeat_client = config.common.init_heartbeat(None).await?;
|
||||||
|
let job_result_client = config.common.init_job_result().await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
config,
|
||||||
heartbeat_client,
|
heartbeat_client,
|
||||||
|
job_result_client,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,6 +263,7 @@ impl Processor for AsanProcessor {
|
|||||||
&self.config.unique_reports,
|
&self.config.unique_reports,
|
||||||
&self.config.reports,
|
&self.config.reports,
|
||||||
&self.config.no_repro,
|
&self.config.no_repro,
|
||||||
|
&self.job_result_client,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ use async_trait::async_trait;
|
|||||||
use onefuzz::{
|
use onefuzz::{
|
||||||
blob::BlobUrl, input_tester::Tester, machine_id::MachineIdentity, sha256, syncdir::SyncedDir,
|
blob::BlobUrl, input_tester::Tester, machine_id::MachineIdentity, sha256, syncdir::SyncedDir,
|
||||||
};
|
};
|
||||||
|
use onefuzz_result::job_result::TaskJobResultClient;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
@ -73,7 +74,9 @@ impl ReportTask {
|
|||||||
pub async fn managed_run(&mut self) -> Result<()> {
|
pub async fn managed_run(&mut self) -> Result<()> {
|
||||||
info!("Starting generic crash report task");
|
info!("Starting generic crash report task");
|
||||||
let heartbeat_client = self.config.common.init_heartbeat(None).await?;
|
let heartbeat_client = self.config.common.init_heartbeat(None).await?;
|
||||||
let mut processor = GenericReportProcessor::new(&self.config, heartbeat_client);
|
let job_result_client = self.config.common.init_job_result().await?;
|
||||||
|
let mut processor =
|
||||||
|
GenericReportProcessor::new(&self.config, heartbeat_client, job_result_client);
|
||||||
|
|
||||||
#[allow(clippy::manual_flatten)]
|
#[allow(clippy::manual_flatten)]
|
||||||
for entry in [
|
for entry in [
|
||||||
@ -183,13 +186,19 @@ pub async fn test_input(args: TestInputArgs<'_>) -> Result<CrashTestResult> {
|
|||||||
pub struct GenericReportProcessor<'a> {
|
pub struct GenericReportProcessor<'a> {
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
heartbeat_client: Option<TaskHeartbeatClient>,
|
heartbeat_client: Option<TaskHeartbeatClient>,
|
||||||
|
job_result_client: Option<TaskJobResultClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenericReportProcessor<'a> {
|
impl<'a> GenericReportProcessor<'a> {
|
||||||
pub fn new(config: &'a Config, heartbeat_client: Option<TaskHeartbeatClient>) -> Self {
|
pub fn new(
|
||||||
|
config: &'a Config,
|
||||||
|
heartbeat_client: Option<TaskHeartbeatClient>,
|
||||||
|
job_result_client: Option<TaskJobResultClient>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
heartbeat_client,
|
heartbeat_client,
|
||||||
|
job_result_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +248,7 @@ impl<'a> Processor for GenericReportProcessor<'a> {
|
|||||||
&self.config.unique_reports,
|
&self.config.unique_reports,
|
||||||
&self.config.reports,
|
&self.config.reports,
|
||||||
&self.config.no_repro,
|
&self.config.no_repro,
|
||||||
|
&self.job_result_client,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("saving report failed")
|
.context("saving report failed")
|
||||||
|
@ -13,6 +13,7 @@ use async_trait::async_trait;
|
|||||||
use onefuzz::{
|
use onefuzz::{
|
||||||
blob::BlobUrl, libfuzzer::LibFuzzer, machine_id::MachineIdentity, sha256, syncdir::SyncedDir,
|
blob::BlobUrl, libfuzzer::LibFuzzer, machine_id::MachineIdentity, sha256, syncdir::SyncedDir,
|
||||||
};
|
};
|
||||||
|
use onefuzz_result::job_result::TaskJobResultClient;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
@ -196,15 +197,18 @@ pub async fn test_input(args: TestInputArgs<'_>) -> Result<CrashTestResult> {
|
|||||||
pub struct AsanProcessor {
|
pub struct AsanProcessor {
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
heartbeat_client: Option<TaskHeartbeatClient>,
|
heartbeat_client: Option<TaskHeartbeatClient>,
|
||||||
|
job_result_client: Option<TaskJobResultClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsanProcessor {
|
impl AsanProcessor {
|
||||||
pub async fn new(config: Arc<Config>) -> Result<Self> {
|
pub async fn new(config: Arc<Config>) -> Result<Self> {
|
||||||
let heartbeat_client = config.common.init_heartbeat(None).await?;
|
let heartbeat_client = config.common.init_heartbeat(None).await?;
|
||||||
|
let job_result_client = config.common.init_job_result().await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
config,
|
||||||
heartbeat_client,
|
heartbeat_client,
|
||||||
|
job_result_client,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,6 +261,7 @@ impl Processor for AsanProcessor {
|
|||||||
&self.config.unique_reports,
|
&self.config.unique_reports,
|
||||||
&self.config.reports,
|
&self.config.reports,
|
||||||
&self.config.no_repro,
|
&self.config.no_repro,
|
||||||
|
&self.job_result_client,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ tempfile = "3.7.0"
|
|||||||
process_control = "4.0"
|
process_control = "4.0"
|
||||||
reqwest-retry = { path = "../reqwest-retry" }
|
reqwest-retry = { path = "../reqwest-retry" }
|
||||||
onefuzz-telemetry = { path = "../onefuzz-telemetry" }
|
onefuzz-telemetry = { path = "../onefuzz-telemetry" }
|
||||||
|
onefuzz-result = { path = "../onefuzz-result" }
|
||||||
stacktrace-parser = { path = "../stacktrace-parser" }
|
stacktrace-parser = { path = "../stacktrace-parser" }
|
||||||
backoff = { version = "0.4", features = ["tokio"] }
|
backoff = { version = "0.4", features = ["tokio"] }
|
||||||
|
|
||||||
|
@ -11,10 +11,12 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
|
use onefuzz_result::job_result::{JobResultData, JobResultSender, TaskJobResultClient};
|
||||||
use onefuzz_telemetry::{Event, EventData};
|
use onefuzz_telemetry::{Event, EventData};
|
||||||
use reqwest::{StatusCode, Url};
|
use reqwest::{StatusCode, Url};
|
||||||
use reqwest_retry::{RetryCheck, SendRetry, DEFAULT_RETRY_PERIOD, MAX_RETRY_ATTEMPTS};
|
use reqwest_retry::{RetryCheck, SendRetry, DEFAULT_RETRY_PERIOD, MAX_RETRY_ATTEMPTS};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::{env::current_dir, path::PathBuf, str, time::Duration};
|
use std::{env::current_dir, path::PathBuf, str, time::Duration};
|
||||||
use tokio::{fs, select};
|
use tokio::{fs, select};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@ -241,6 +243,7 @@ impl SyncedDir {
|
|||||||
url: BlobContainerUrl,
|
url: BlobContainerUrl,
|
||||||
event: Event,
|
event: Event,
|
||||||
ignore_dotfiles: bool,
|
ignore_dotfiles: bool,
|
||||||
|
jr_client: &Option<TaskJobResultClient>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
debug!("monitoring {}", path.display());
|
debug!("monitoring {}", path.display());
|
||||||
|
|
||||||
@ -265,9 +268,39 @@ impl SyncedDir {
|
|||||||
if ignore_dotfiles && file_name_event_str.starts_with('.') {
|
if ignore_dotfiles && file_name_event_str.starts_with('.') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
event!(event.clone(); EventData::Path = file_name_event_str);
|
event!(event.clone(); EventData::Path = file_name_event_str);
|
||||||
metric!(event.clone(); 1.0; EventData::Path = file_name_str_metric_str);
|
metric!(event.clone(); 1.0; EventData::Path = file_name_str_metric_str);
|
||||||
|
if let Some(jr_client) = jr_client {
|
||||||
|
match event {
|
||||||
|
Event::new_result => {
|
||||||
|
jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::NewCrashingInput,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Event::new_coverage => {
|
||||||
|
jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::CoverageData,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Event::new_crashdump => {
|
||||||
|
jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::NewCrashDump,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Unhandled job result!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let destination = path.join(file_name);
|
let destination = path.join(file_name);
|
||||||
if let Err(err) = fs::copy(&item, &destination).await {
|
if let Err(err) = fs::copy(&item, &destination).await {
|
||||||
let error_message = format!(
|
let error_message = format!(
|
||||||
@ -305,6 +338,29 @@ impl SyncedDir {
|
|||||||
|
|
||||||
event!(event.clone(); EventData::Path = file_name_event_str);
|
event!(event.clone(); EventData::Path = file_name_event_str);
|
||||||
metric!(event.clone(); 1.0; EventData::Path = file_name_str_metric_str);
|
metric!(event.clone(); 1.0; EventData::Path = file_name_str_metric_str);
|
||||||
|
if let Some(jr_client) = jr_client {
|
||||||
|
match event {
|
||||||
|
Event::new_result => {
|
||||||
|
jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::NewCrashingInput,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Event::new_coverage => {
|
||||||
|
jr_client
|
||||||
|
.send_direct(
|
||||||
|
JobResultData::CoverageData,
|
||||||
|
HashMap::from([("count".to_string(), 1.0)]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Unhandled job result!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Err(err) = uploader.upload(item.clone()).await {
|
if let Err(err) = uploader.upload(item.clone()).await {
|
||||||
let error_message = format!(
|
let error_message = format!(
|
||||||
"Couldn't upload file. path:{} dir:{} err:{:?}",
|
"Couldn't upload file. path:{} dir:{} err:{:?}",
|
||||||
@ -336,7 +392,12 @@ impl SyncedDir {
|
|||||||
/// The intent of this is to support use cases where we usually want a directory
|
/// The intent of this is to support use cases where we usually want a directory
|
||||||
/// to be initialized, but a user-supplied binary, (such as AFL) logically owns
|
/// to be initialized, but a user-supplied binary, (such as AFL) logically owns
|
||||||
/// a directory, and may reset it.
|
/// a directory, and may reset it.
|
||||||
pub async fn monitor_results(&self, event: Event, ignore_dotfiles: bool) -> Result<()> {
|
pub async fn monitor_results(
|
||||||
|
&self,
|
||||||
|
event: Event,
|
||||||
|
ignore_dotfiles: bool,
|
||||||
|
job_result_client: &Option<TaskJobResultClient>,
|
||||||
|
) -> Result<()> {
|
||||||
if let Some(url) = self.remote_path.clone() {
|
if let Some(url) = self.remote_path.clone() {
|
||||||
loop {
|
loop {
|
||||||
debug!("waiting to monitor {}", self.local_path.display());
|
debug!("waiting to monitor {}", self.local_path.display());
|
||||||
@ -355,6 +416,7 @@ impl SyncedDir {
|
|||||||
url.clone(),
|
url.clone(),
|
||||||
event.clone(),
|
event.clone(),
|
||||||
ignore_dotfiles,
|
ignore_dotfiles,
|
||||||
|
job_result_client,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ var storageAccountFuncQueuesParams = [
|
|||||||
'update-queue'
|
'update-queue'
|
||||||
'webhooks'
|
'webhooks'
|
||||||
'signalr-events'
|
'signalr-events'
|
||||||
'custom-metrics'
|
'job-result'
|
||||||
]
|
]
|
||||||
var fileChangesQueueIndex = 0
|
var fileChangesQueueIndex = 0
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user