Files
Test/Library/PackageCache/com.unity.test-framework.performance@e8b39e3ef3d9/Editor/IncrementalRunResultsWriter.cs
T
2026-11-01 23:39:55 +02:00

135 lines
5.3 KiB
C#

using System;
using System.IO;
using Unity.PerformanceTesting.Data;
using UnityEditor;
using UnityEngine;
namespace Unity.PerformanceTesting.Editor
{
// Keeps a complete performance results file on disk while a run is still in progress, so that
// results already collected survive a crash, a test timeout or any other premature shutdown.
class IncrementalRunResultsWriter
{
// The results file is replaced by moving this file over it. A shutdown between removing the
// previous file and the move landing therefore leaves the complete results here and nothing
// at the results path, so whoever recovers the results of a run that went down has to look
// here when the results file itself is missing.
internal const string TempFileExtension = ".tmp";
const string k_RunTokenKey = "Unity.PerformanceTesting.IncrementalResults.RunToken";
const string k_WrittenRunTokenKey = "Unity.PerformanceTesting.IncrementalResults.WrittenRunToken";
Run m_Run;
bool m_RunStateInitialized;
bool m_WriteFailureLogged;
internal void BeginRun()
{
m_Run = null;
m_RunStateInitialized = true;
m_WriteFailureLogged = false;
// The results file of an earlier run is left in place until this run has something to
// replace it with, and is identified as not belonging to this run by the token.
SessionState.SetString(k_RunTokenKey, Guid.NewGuid().ToString("N"));
SessionState.EraseString(k_WrittenRunTokenKey);
}
internal void AppendTestResults(string resultsPath, string testOutput)
{
if (string.IsNullOrEmpty(resultsPath) || string.IsNullOrEmpty(testOutput))
return;
try
{
RestoreRunStateFromDisk(resultsPath);
if (!TestResultsParser.TryAppendPerformanceRunData(testOutput, ref m_Run))
return;
WriteResultsFile(resultsPath, m_Run);
SessionState.SetString(k_WrittenRunTokenKey, SessionState.GetString(k_RunTokenKey, string.Empty));
}
catch (Exception e)
{
// Only reported once because the run is still able to write its results when it finishes.
if (m_WriteFailureLogged)
return;
m_WriteFailureLogged = true;
Debug.LogWarning($"Failed to save in-progress performance results to '{resultsPath}'.\n{e}");
}
}
internal static void WriteResultsFile(string resultsPath, Run run)
{
var json = JsonUtility.ToJson(run, true);
CreateDirectoryIfNecessary(resultsPath);
// Staged through a temporary file so that a shutdown part way through the write cannot
// leave a truncated, unparsable results file behind. File.Replace is not dependable on
// every file system the results are written to, so the previous file is removed first.
// The temporary file is complete by the time the removal happens, which is what lets a
// reader treat it as the results of the run when the results file is not there.
var tempPath = GetTempFilePath(resultsPath);
File.WriteAllText(tempPath, json);
if (File.Exists(resultsPath))
{
File.Delete(resultsPath);
}
File.Move(tempPath, resultsPath);
}
internal static string GetTempFilePath(string resultsPath)
{
return resultsPath + TempFileExtension;
}
// A domain reload discards the accumulated run, so continue from the file on disk rather
// than replacing it with only the results gathered since the reload.
void RestoreRunStateFromDisk(string resultsPath)
{
if (m_RunStateInitialized)
return;
m_RunStateInitialized = true;
if (!WrittenResultsBelongToCurrentRun())
return;
// A replacement that never landed leaves the results of this run in the temporary file
// alone, so that file stands in for the results file when it is the only one present.
var pathToRestoreFrom = File.Exists(resultsPath) ? resultsPath : GetTempFilePath(resultsPath);
if (!File.Exists(pathToRestoreFrom))
return;
try
{
m_Run = JsonUtility.FromJson<Run>(File.ReadAllText(pathToRestoreFrom));
}
catch (Exception e)
{
Debug.LogWarning($"Failed to read the in-progress performance results file '{pathToRestoreFrom}'.\n{e}");
}
}
static bool WrittenResultsBelongToCurrentRun()
{
var runToken = SessionState.GetString(k_RunTokenKey, string.Empty);
return !string.IsNullOrEmpty(runToken)
&& runToken == SessionState.GetString(k_WrittenRunTokenKey, string.Empty);
}
static void CreateDirectoryIfNecessary(string filePath)
{
var directoryPath = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
}
}
}