TUN-8768: add job report to diagnostic zipfile

## Summary

Add a new job that write to a file the result of all of the other tasks along with possible errors. This file is also added to the root of the diagnostic zip file.

 Closes TUN-8768
This commit is contained in:
Luis Neto 2024-12-04 10:32:49 -08:00
parent f12036c2da
commit f07d04d129
2 changed files with 103 additions and 27 deletions

View File

@ -33,4 +33,5 @@ const (
tunnelStateBaseName = "tunnelstate.json" tunnelStateBaseName = "tunnelstate.json"
cliConfigurationBaseName = "cli-configuration.json" cliConfigurationBaseName = "cli-configuration.json"
configurationBaseName = "configuration.json" configurationBaseName = "configuration.json"
taskResultBaseName = "task-result.json"
) )

View File

@ -17,6 +17,29 @@ import (
network "github.com/cloudflare/cloudflared/diagnostic/network" network "github.com/cloudflare/cloudflared/diagnostic/network"
) )
const (
taskSuccess = "success"
taskFailure = "failure"
jobReportName = "job report"
tunnelStateJobName = "tunnel state"
systemInformationJobName = "system information"
goroutineJobName = "goroutine profile"
heapJobName = "heap profile"
metricsJobName = "metrics"
logInformationJobName = "log information"
rawNetworkInformationJobName = "raw network information"
networkInformationJobName = "network information"
cliConfigurationJobName = "cli configuration"
configurationJobName = "configuration"
)
// Struct used to hold the results of different routines executing the network collection.
type taskResult struct {
Result string `json:"result,omitempty"`
Err error `json:"error,omitempty"`
path string
}
// Struct used to hold the results of different routines executing the network collection. // Struct used to hold the results of different routines executing the network collection.
type networkCollectionResult struct { type networkCollectionResult struct {
name string name string
@ -335,54 +358,54 @@ func createJobs(
rawNetworkCollectorFunc, jsonNetworkCollectorFunc := networkInformationCollectors() rawNetworkCollectorFunc, jsonNetworkCollectorFunc := networkInformationCollectors()
jobs := []collectJob{ jobs := []collectJob{
{ {
jobName: "tunnel state", jobName: tunnelStateJobName,
fn: tunnelStateCollectEndpointAdapter(client, tunnel, tunnelStateBaseName), fn: tunnelStateCollectEndpointAdapter(client, tunnel, tunnelStateBaseName),
bypass: false, bypass: false,
}, },
{ {
jobName: "system information", jobName: systemInformationJobName,
fn: collectFromEndpointAdapter(client.GetSystemInformation, systemInformationBaseName), fn: collectFromEndpointAdapter(client.GetSystemInformation, systemInformationBaseName),
bypass: noDiagSystem, bypass: noDiagSystem,
}, },
{ {
jobName: "goroutine profile", jobName: goroutineJobName,
fn: collectFromEndpointAdapter(client.GetGoroutineDump, goroutinePprofBaseName), fn: collectFromEndpointAdapter(client.GetGoroutineDump, goroutinePprofBaseName),
bypass: noDiagRuntime, bypass: noDiagRuntime,
}, },
{ {
jobName: "heap profile", jobName: heapJobName,
fn: collectFromEndpointAdapter(client.GetMemoryDump, heapPprofBaseName), fn: collectFromEndpointAdapter(client.GetMemoryDump, heapPprofBaseName),
bypass: noDiagRuntime, bypass: noDiagRuntime,
}, },
{ {
jobName: "metrics", jobName: metricsJobName,
fn: collectFromEndpointAdapter(client.GetMetrics, metricsBaseName), fn: collectFromEndpointAdapter(client.GetMetrics, metricsBaseName),
bypass: noDiagMetrics, bypass: noDiagMetrics,
}, },
{ {
jobName: "log information", jobName: logInformationJobName,
fn: func(ctx context.Context) (string, error) { fn: func(ctx context.Context) (string, error) {
return collectLogs(ctx, client, diagContainer, diagPod) return collectLogs(ctx, client, diagContainer, diagPod)
}, },
bypass: noDiagLogs, bypass: noDiagLogs,
}, },
{ {
jobName: "raw network information", jobName: rawNetworkInformationJobName,
fn: rawNetworkCollectorFunc, fn: rawNetworkCollectorFunc,
bypass: noDiagNetwork, bypass: noDiagNetwork,
}, },
{ {
jobName: "network information", jobName: networkInformationJobName,
fn: jsonNetworkCollectorFunc, fn: jsonNetworkCollectorFunc,
bypass: noDiagNetwork, bypass: noDiagNetwork,
}, },
{ {
jobName: "cli configuration", jobName: cliConfigurationJobName,
fn: collectFromEndpointAdapter(client.GetCliConfiguration, cliConfigurationBaseName), fn: collectFromEndpointAdapter(client.GetCliConfiguration, cliConfigurationBaseName),
bypass: false, bypass: false,
}, },
{ {
jobName: "configuration", jobName: configurationJobName,
fn: collectFromEndpointAdapter(client.GetTunnelConfiguration, configurationBaseName), fn: collectFromEndpointAdapter(client.GetTunnelConfiguration, configurationBaseName),
bypass: false, bypass: false,
}, },
@ -391,6 +414,69 @@ func createJobs(
return jobs return jobs
} }
func createTaskReport(taskReport map[string]taskResult) (string, error) {
dumpHandle, err := os.Create(filepath.Join(os.TempDir(), taskResultBaseName))
if err != nil {
return "", ErrCreatingTemporaryFile
}
defer dumpHandle.Close()
err = json.NewEncoder(dumpHandle).Encode(taskReport)
if err != nil {
return "", fmt.Errorf("error encoding task results: %w", err)
}
return dumpHandle.Name(), nil
}
func runJobs(ctx context.Context, jobs []collectJob, log *zerolog.Logger) map[string]taskResult {
jobReport := make(map[string]taskResult, len(jobs))
for _, job := range jobs {
if job.bypass {
continue
}
log.Info().Msgf("Collecting %s...", job.jobName)
path, err := job.fn(ctx)
var result taskResult
if err != nil {
result = taskResult{Result: taskFailure, Err: err, path: path}
log.Error().Err(err).Msgf("Job: %s finished with error.", job.jobName)
} else {
result = taskResult{Result: taskSuccess, Err: nil, path: path}
log.Info().Msgf("Collected %s.", job.jobName)
}
jobReport[job.jobName] = result
}
taskReportName, err := createTaskReport(jobReport)
var result taskResult
if err != nil {
result = taskResult{
Result: taskFailure,
path: taskReportName,
Err: err,
}
} else {
result = taskResult{
Result: taskSuccess,
path: taskReportName,
Err: nil,
}
}
jobReport[jobReportName] = result
return jobReport
}
func RunDiagnostic( func RunDiagnostic(
log *zerolog.Logger, log *zerolog.Logger,
options Options, options Options,
@ -410,7 +496,6 @@ func RunDiagnostic(
defer cancel() defer cancel()
paths := make([]string, 0)
jobs := createJobs( jobs := createJobs(
client, client,
tunnel, tunnel,
@ -423,27 +508,17 @@ func RunDiagnostic(
options.Toggles.NoDiagNetwork, options.Toggles.NoDiagNetwork,
) )
for _, job := range jobs { jobsReport := runJobs(ctx, jobs, log)
if job.bypass { paths := make([]string, 0)
continue
}
log.Info().Msgf("Collecting %s...", job.jobName) for _, v := range jobsReport {
path, err := job.fn(ctx) paths = append(paths, v.path)
defer func() { defer func() {
if !errors.Is(err, ErrCreatingTemporaryFile) { if !errors.Is(v.Err, ErrCreatingTemporaryFile) {
os.Remove(path) os.Remove(v.path)
} }
}() }()
if err != nil {
return nil, err
}
log.Info().Msgf("Collected %s.", job.jobName)
paths = append(paths, path)
} }
zipfile, err := CreateDiagnosticZipFile(zipName, paths) zipfile, err := CreateDiagnosticZipFile(zipName, paths)