diff --git a/diagnostic/consts.go b/diagnostic/consts.go index 87242434..b2da5304 100644 --- a/diagnostic/consts.go +++ b/diagnostic/consts.go @@ -29,5 +29,6 @@ const ( heapPprofBaseName = "heap.pprof" goroutinePprofBaseName = "goroutine.pprof" networkBaseName = "network.json" + rawNetworkBaseName = "raw-network.txt" tunnelStateBaseName = "tunnelstate.json" ) diff --git a/diagnostic/diagnostic.go b/diagnostic/diagnostic.go index 428d0f54..a4c182b5 100644 --- a/diagnostic/diagnostic.go +++ b/diagnostic/diagnostic.go @@ -141,7 +141,7 @@ func collectNetworkResultRoutine( } } -func collectNetworkInformation(ctx context.Context) (string, error) { +func gatherNetworkInformation(ctx context.Context) map[string]networkCollectionResult { networkCollector := network.NetworkCollectorImpl{} hostAndIPversionPairs := []struct { @@ -171,11 +171,64 @@ func collectNetworkInformation(ctx context.Context) (string, error) { // Wait for routines to end. wgroup.Wait() - resultMap := make(map[string][]*network.Hop) + resultMap := make(map[string]networkCollectionResult) for range len(hostAndIPversionPairs) { result := <-results - resultMap[result.name] = result.info + if result.err != nil { + continue + } + + resultMap[result.name] = result + } + + return resultMap +} + +func networkInformationCollectors() (rawNetworkCollector, jsonNetworkCollector collectFunc) { + // The network collector is an operation that takes most of the diagnostic time, thus, + // the sync.Once is used to memoize the result of the collector and then create different + // outputs. + var once sync.Once + + var resultMap map[string]networkCollectionResult + + rawNetworkCollector = func(ctx context.Context) (string, error) { + once.Do(func() { resultMap = gatherNetworkInformation(ctx) }) + + return rawNetworkInformationWriter(resultMap) + } + jsonNetworkCollector = func(ctx context.Context) (string, error) { + once.Do(func() { resultMap = gatherNetworkInformation(ctx) }) + + return jsonNetworkInformationWriter(resultMap) + } + + return rawNetworkCollector, jsonNetworkCollector +} + +func rawNetworkInformationWriter(resultMap map[string]networkCollectionResult) (string, error) { + networkDumpHandle, err := os.Create(filepath.Join(os.TempDir(), rawNetworkBaseName)) + if err != nil { + return "", ErrCreatingTemporaryFile + } + + defer networkDumpHandle.Close() + + for k, v := range resultMap { + _, err := networkDumpHandle.WriteString(k + "\n" + v.raw + "\n") + if err != nil { + return "", fmt.Errorf("error writing raw network information: %w", err) + } + } + + return networkDumpHandle.Name(), nil +} + +func jsonNetworkInformationWriter(resultMap map[string]networkCollectionResult) (string, error) { + jsonMap := make(map[string][]*network.Hop, len(resultMap)) + for k, v := range resultMap { + jsonMap[k] = v.info } networkDumpHandle, err := os.Create(filepath.Join(os.TempDir(), networkBaseName)) @@ -185,7 +238,7 @@ func collectNetworkInformation(ctx context.Context) (string, error) { defer networkDumpHandle.Close() - err = json.NewEncoder(networkDumpHandle).Encode(resultMap) + err = json.NewEncoder(networkDumpHandle).Encode(jsonMap) if err != nil { return "", fmt.Errorf("error encoding network information results: %w", err) } @@ -279,6 +332,7 @@ func createJobs( noDiagLogs bool, noDiagNetwork bool, ) []collectJob { + rawNetworkCollectorFunc, jsonNetworkCollectorFunc := networkInformationCollectors() jobs := []collectJob{ { jobName: "tunnel state", @@ -312,9 +366,14 @@ func createJobs( }, bypass: noDiagLogs, }, + { + jobName: "raw network information", + fn: rawNetworkCollectorFunc, + bypass: noDiagNetwork, + }, { jobName: "network information", - fn: collectNetworkInformation, + fn: jsonNetworkCollectorFunc, bypass: noDiagNetwork, }, } diff --git a/diagnostic/network/collector_utils.go b/diagnostic/network/collector_utils.go index f897fda6..bfc27849 100644 --- a/diagnostic/network/collector_utils.go +++ b/diagnostic/network/collector_utils.go @@ -39,7 +39,7 @@ func decodeNetworkOutputToFile(command *exec.Cmd, decodeLine DecodeLineFunc) ([] return nil, buf.String(), err } - return hops, "", nil + return hops, buf.String(), nil } func Decode(reader io.Reader, decodeLine DecodeLineFunc) ([]*Hop, error) {