Files
UnrealEngineUWP/Engine/Source/Programs/CrashReporter/CrashReportProcess/ReportQueue.cs
Ben Zeigler 24a8d60723 Copying //UE4/Dev-Core to //UE4/Dev-Main (Source: //UE4/Dev-Core @ 3208226)
#lockdown Nick.Penwarden
#rb None

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3173153 on 2016/10/25 by Graeme.Thornton

	Pak signing changes
	 - Integrated into EDL loader
	 - Changed to not encrypt each CRC in the sig file, rather just store a single encryped signature of the entire sig file. Removes need to decrypt thousands of signatures at startup.

Change 3173531 on 2016/10/25 by Steven.Hutton

	Removing unused j query packages.

Change 3174743 on 2016/10/26 by Gil.Gribb

	UE4 - fixed COTF with EDL

Change 3177896 on 2016/10/28 by Steve.Robb

	TSharedPtr and TSharedRef aliasing constructors.
	Removal of static_asserts for TSharedPtr<UObject>.

Change 3180343 on 2016/10/31 by Steve.Robb

	Reimplementation of changes from CL#s 3050329 and 3105715 that were lost in merges 3094597 and 3105741.

Change 3181382 on 2016/11/01 by Steve.Robb

	Visual Studio debugger visualizers for delegates.

Change 3182738 on 2016/11/02 by Graeme.Thornton

	Re-enable signed archive reader so non-pakpreacher based reads still get signature checked

Change 3183420 on 2016/11/02 by Steve.Robb

	Fix to TIsZeroConstructType for TScriptDelegate.

Change 3184872 on 2016/11/03 by Robert.Manuszewski

	Fixing memory stomps in SSL certificate initialization (found with mallocstomp)

Change 3184873 on 2016/11/03 by Robert.Manuszewski

	Adding thread safety checks to async loading code

Change 3185535 on 2016/11/03 by Ben.Zeigler

	Fix it so calling CreateDefaultSubobject with bTransient = true sets the object transient flag. This fixes EDL Crashes involving components.

Change 3186636 on 2016/11/04 by Graeme.Thornton

	AES encryption integrated into EDL system
	Pak signing and AES encryption now configurable by ini files rather than magical text files

Change 3186637 on 2016/11/04 by Graeme.Thornton

	Configured pak signing and encryption in ShooterGame for reference

Change 3186639 on 2016/11/04 by Graeme.Thornton

	Encryption changes for Orion
	* Move pak signing keys into new INI format
	* Add AES key and enable INI file encryption

Change 3186661 on 2016/11/04 by Graeme.Thornton

	Change unrealpak command line params to accept AES key as a separete parameter

Change 3186670 on 2016/11/04 by Robert.Manuszewski

	Adding a null check before using a package pointer in Linker code

	#jira UE-38237

Change 3186775 on 2016/11/04 by Graeme.Thornton

	Fix UBT defines that come in as quoted strings, losing the quotes when passed to the compiler
	 - PS4 and Mac fixes. Other platforms might need fixing too!

Change 3186823 on 2016/11/04 by Graeme.Thornton

	Fixed an incorrect size check in the EDL pak signing code

Change 3186925 on 2016/11/04 by Graeme.Thornton

	Allow UnrealPak to read encryption settings from project ini files

Change 3189885 on 2016/11/08 by Graeme.Thornton

	Static analysis warning fix

Change 3190015 on 2016/11/08 by Robert.Manuszewski

	Thread safety fix for UBlueprintGeneratedClass::PostLoadDefaultObject while UBlueprintGeneratedClass::SerializeDefaultObject runs on the async loading thread

Change 3190253 on 2016/11/08 by Chris.Wood

	Improved MDD performance for on the CR server.
	[UE-37566] - Improve MDD performance on CR server

	Blocked MDD init'ing the crash handling code as it isn't desirable on the server.
	Removed redundant call to SetSymbolPathsFromModules() from CrashDebugHelper.

Change 3192993 on 2016/11/10 by Robert.Manuszewski

	Thread Heartbeat will no longer report the same hang multiple times.

Change 3193111 on 2016/11/10 by Robert.Manuszewski

	Minor change in the condition that detects the same hangs - allow the same callstacks from different threads

Change 3193168 on 2016/11/10 by Steve.Robb

	TSparseArray now reserves space in reverse so that new elements get added to the front of the allocation rather than the back, which is better for memory traversal and meets expectations more closely.

Change 3193171 on 2016/11/10 by Steve.Robb

	Easier debugging of FPendingRegistrantInfo map.

Change 3193188 on 2016/11/10 by Steve.Robb

	TAutoPointer deprecated.

Change 3193796 on 2016/11/10 by Graeme.Thornton

	Fix pak creation failure when no pak signing keys are supplied

Change 3194524 on 2016/11/11 by Graeme.Thornton

	Another static analysis warning fix

Change 3195119 on 2016/11/11 by Steve.Robb

	TAutoPtr deprecated.
	Fixes to use of TAutoPtr with incompatible memory deallocations (TAutoPtr with FMemory::Malloc and new[]).
	Some large headers moved into .cpp files.

Change 3196582 on 2016/11/14 by Gil.Gribb

	UE4 - Changed a check to a warning related to detaching linekrs twice. Seen in nativized BP version of platformer game.

Change 3196878 on 2016/11/14 by Steve.Robb

	TScopedPointer deprecated.

Change 3198061 on 2016/11/15 by Steve.Robb

	Class array is no longer regenerated when saving UClasses.

Change 3198065 on 2016/11/15 by Robert.Manuszewski

	Making AssembleReferenceTokenStream thread safe for blueprints loaded on the async loading thread.

Change 3198199 on 2016/11/15 by Robert.Manuszewski

	Pak platform file will now only be used if pak files exist regardless of command line paraks like -pak, -singedpak and -signed.

Change 3199954 on 2016/11/16 by Graeme.Thornton

	Removing USING_SIGNED_CONTENT

Change 3200221 on 2016/11/16 by Chris.Wood

	CrashReportProcess code cleanup - removing unused using directives

Change 3200232 on 2016/11/16 by Chris.Wood

	Multiple CrashReportProcess updates and improvements (CRP v1.2.6)
	UE-36248 - CRP scalability: All bulk storage or shared data to S3 or suitable network drives

	InvalidCrashReports now saved to S3 instead of local folder
	Removed option tosync MinidumpDiagnostics from Perforce
	Moved MinidumpDiagnostics from old Perforce synched location to its own folder in E:\Services (makes more sense with manual publishing)
	Added improved logging to Slack with option to monitor MDD performance
	Added hourly log folders to MDD logs
	Added support for types of crashes we don't want to symbolicate (using it to skip callstack gen for hang detected ensures)

Change 3200382 on 2016/11/16 by Robert.Manuszewski

	Async Loading code will now detach the linker when resetting async package loader to avoid situations when loading the same asset multiple times results in the following load request finding the old linker after the package has been loading but the async package hasn't been deleted yet (async package for the old request in limbo state but linker exists).

Change 3200562 on 2016/11/16 by Gil.Gribb

	UE4 - Fixed rare issue with reloading nativized blueprints with the EDL and a minor simplication.

Change 3201093 on 2016/11/16 by Ben.Zeigler

	#UE 38654 Fix EDL cooking to correctly search components created directly by UBlueprints, as well as the CDO components it already covered. Also explicitly mark subobject templates as editor only.
	Fix issue where the AssetImportData associated with Blueprint-owned Curves was ending up in the cooked subobject template list. Stopped it from creating those objects, and mark the class editor only.

Change 3201736 on 2016/11/17 by Steve.Robb

	Strtoi64 platform and TCString functions.

	#fyi robert.manuszewski

Change 3201938 on 2016/11/17 by Ben.Woodhouse

	Dummy integrate of the Square render version workaround (CL 3201913) with _accept target_ to prevent it being integrated to dev-core in future.

	Commandline:
	p4 integrate //Tasks/UE4/Dev-LoadTimes/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp@3201913,3201913 //UE4/Dev-Core/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp

	#fyi robert.manuszewski

Change 3203757 on 2016/11/18 by Robert.Manuszewski

	Removing debug code from async loading code.

Change 3203927 on 2016/11/18 by Robert.Manuszewski

	Fixing comments in the async loading code.

Change 3204851 on 2016/11/18 by Steve.Robb

	Metafunction for testing if a particular operator<< overload exists, e.g. THasInserterOperator<FArchive&, FMyType&>::Value.

Change 3204854 on 2016/11/18 by Steve.Robb

	UEnumProperty.

Change 3205027 on 2016/11/18 by Ben.Zeigler

	Add useful functions to FAssetPtr and TAssetSubclassOf that already existed on TAssetPtr
	Add Get() to TSubclassOf so it matches our other wrappers
	Fix TSubclassOf and TAssetSubclassOf to use the more efficient template method of checking class compatibility
	Comment and template cleanups for AssetPtr, StringAssetReference, LazyPtr, and SubclassOf

Change 3206334 on 2016/11/21 by Ben.Zeigler

	#UE-38773: Fix it so non-component template subobjects of CDOs are not included as creation dependencies for BP classes, also clean up GetPreloadDependencies as it was adding redundant and null entries
	#UE-38799: Fix it so WidgetTrees don't get picked up as subobjects, and add ensure at cook time to find null outers that would crash at runtime. Make sure the instanced widget trees are transient.
	Cook finishes but game is still crashing in some cases, so I might adjust this after other testing

Change 3206353 on 2016/11/21 by Ben.Zeigler

	Fix EnumProperty to handle EDL preload dependencies properly

Change 3206625 on 2016/11/21 by Ben.Zeigler

	Fix enum property crash at runtime by copying what array property does and making sure inner property is not transient

Change 3206937 on 2016/11/21 by Ben.Zeigler

	#jira UE-38905 Fix it so enums inside arrays are migrated properly, the enum tag is lost so use the current one
	Disable other nested enum migrations as they are unlikely to work. Array property tags need to be refactored to be safer
	Correctly save enum tag for enum properties, it was being set but not serialized

Change 3207002 on 2016/11/21 by Ben.Zeigler

	#jira UE-38799
	Fix it so per-widget copy of widget tree and all widgets inside are properly transient, they were being cooked before but never accessed.
	Fix case where non ClientOnly public objects nested instead ClientOnly objects would cook but fail to load, and add ensure to catch these cases in the future.
	If the full outer chain isn't available, it can't be loaded anyway, and this finds issues at cook time instead of load time.
	We should generally outlaw non-transient objects with transient outers, it does not do what people expect.

Change 3207032 on 2016/11/21 by Ben.Zeigler

	#jira UE-38654 Re-Fix EDL cooking with SCS-added components. They used to have the DefaultSubObject flag but no longer do

[CL 3208270 by Ben Zeigler in Main branch]
2016-11-22 18:45:44 -05:00

730 lines
26 KiB
C#

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Tools.CrashReporter.CrashReportCommon;
using Tools.DotNETCommon.XmlHandler;
namespace Tools.CrashReporter.CrashReportProcess
{
using ReportIdSet = HashSet<string>;
interface IReportQueue : IDisposable
{
int CheckForNewReports();
bool TryDequeueReport(out FGenericCrashContext Context);
void CleanLandingZone();
string QueueId { get; }
string LandingZonePath { get; }
}
abstract class ReportQueueBase : IReportQueue
{
// PROPERTIES
protected int QueueCount
{
get
{
lock (NewReportsLock)
{
return NewCrashContexts.Count;
}
}
}
protected int LastQueueSizeOnDisk { get; private set; }
public string QueueId
{
get { return QueueName; }
}
public string LandingZonePath
{
get { return LandingZone; }
}
protected abstract string QueueProcessingStartedEventName { get; }
// METHODS
protected ReportQueueBase(string InQueueName, string LandingZonePath, int InDecimateWaitingCountStart, int InDecimateWaitingCountEnd)
{
QueueName = InQueueName;
LandingZone = LandingZonePath;
DecimateWaitingCountStart = InDecimateWaitingCountStart;
DecimateWaitingCountEnd = InDecimateWaitingCountEnd;
CrashReporterProcessServicer.StatusReporter.AlertOnLowDisk(LandingZonePath, Config.Default.DiskSpaceAlertPercent);
}
/// <summary>
/// Look for new report folders and add them to the publicly available thread-safe queue.
/// </summary>
public int CheckForNewReports()
{
try
{
if (QueueCount >= Config.Default.MinDesiredMemoryQueueSize)
{
CrashReporterProcessServicer.WriteEvent(string.Format(
"CheckForNewReports: {0} skipped busy queue size {1} in {2}", QueueName, QueueCount, LandingZone));
}
else
{
var NewReportPath = "";
var ReportsInLandingZone = new ReportIdSet();
CrashReporterProcessServicer.StatusReporter.AlertOnLowDisk(LandingZone, Config.Default.DiskSpaceAlertPercent);
DirectoryInfo[] DirectoriesInLandingZone;
if (GetCrashesFromLandingZone(out DirectoriesInLandingZone))
{
LastQueueSizeOnDisk = DirectoriesInLandingZone.Length;
int EnqueuedCount = 0;
CrashReporterProcessServicer.WriteEvent(string.Format("CheckForNewReports: {0} reports in disk landing zone {1}",
DirectoriesInLandingZone.Length, LandingZone));
// Add any new reports
for (int DirIndex = 0; DirIndex < DirectoriesInLandingZone.Length && QueueCount < Config.Default.MaxMemoryQueueSize; DirIndex++)
{
var SubDirInfo = DirectoriesInLandingZone[DirIndex];
try
{
if (Directory.Exists(SubDirInfo.FullName))
{
NewReportPath = SubDirInfo.FullName;
ReportsInLandingZone.Add(NewReportPath);
if (!ReportsInLandingZoneLastTimeWeChecked.Contains(NewReportPath))
{
if (EnqueueNewReport(NewReportPath))
{
EnqueuedCount++;
}
}
}
}
catch (Exception Ex)
{
CrashReporterProcessServicer.WriteException("CheckForNewReportsInner: " + Ex, Ex);
ReportProcessor.MoveReportToInvalid(NewReportPath, "NEWRECORD_FAIL_" + DateTime.Now.Ticks);
}
}
ReportsInLandingZoneLastTimeWeChecked = ReportsInLandingZone;
CrashReporterProcessServicer.WriteEvent(string.Format(
"CheckForNewReports: {0} enqueued to queue size {1} from {2}", EnqueuedCount, QueueCount, LandingZone));
CrashReporterProcessServicer.WriteEvent(string.Format("CheckForNewReports: Enqueue rate {0:N1}/minute from {1}",
EnqueueCounter.EventsPerSecond*60, LandingZone));
}
else
{
LastQueueSizeOnDisk = 0;
}
}
}
catch (Exception Ex)
{
CrashReporterProcessServicer.WriteException("CheckForNewReportsOuter: " + Ex, Ex);
}
return GetTotalWaitingCount();
}
/// <summary>
/// Tidy up the landing zone folder
/// </summary>
public void CleanLandingZone()
{
var DirInfo = new DirectoryInfo(LandingZone);
foreach (var SubDirInfo in DirInfo.GetDirectories())
{
if (SubDirInfo.LastWriteTimeUtc.AddDays(Config.Default.DeleteWaitingReportsDays) < DateTime.UtcNow)
{
SubDirInfo.Delete(true);
}
}
}
public virtual void Dispose()
{
// Attempt to remove the queued crashes from the ReportIndex
lock (NewReportsLock)
{
CrashReporterProcessServicer.WriteEvent(string.Format("{0} shutting down", QueueName));
CrashReporterProcessServicer.WriteEvent(string.Format("{0} dequeuing {1} crashes for next time", QueueName, NewCrashContexts.Count));
while (NewCrashContexts.Count > 0)
{
try
{
string ReportName = Path.GetFileName(NewCrashContexts.Peek().CrashDirectory);
CrashReporterProcessServicer.ReportIndex.TryRemoveReport(ReportName);
}
finally
{
NewCrashContexts.Dequeue();
}
}
}
}
protected abstract int GetTotalWaitingCount();
protected virtual bool GetCrashesFromLandingZone(out DirectoryInfo[] OutDirectories)
{
// Check the landing zones for new reports
DirectoryInfo DirInfo = new DirectoryInfo(LandingZone);
// Couldn't find a landing zone, skip and try again later.
// Crash receiver will recreate them if needed.
if (!DirInfo.Exists)
{
CrashReporterProcessServicer.WriteFailure("LandingZone not found: " + LandingZone);
OutDirectories = new DirectoryInfo[0];
return false;
}
OutDirectories = DirInfo.GetDirectories().OrderBy(dirinfo => dirinfo.CreationTimeUtc).ToArray();
return true;
}
/// <summary> Looks for the CrashContext.runtime-xml, if found, will return a new instance of the FGenericCrashContext. </summary>
private static FGenericCrashContext FindCrashContext(string NewReportPath)
{
string CrashContextPath = Path.Combine(NewReportPath, FGenericCrashContext.CrashContextRuntimeXMLName);
bool bExist = File.Exists(CrashContextPath);
if (bExist)
{
return FGenericCrashContext.FromFile(CrashContextPath);
}
return null;
}
/// <summary> Converts WER metadata xml file to the crash context. </summary>
private static void ConvertMetadataToCrashContext(FReportData ReportData, string NewReportPath, ref FGenericCrashContext OutCrashContext)
{
if (OutCrashContext == null)
{
OutCrashContext = new FGenericCrashContext();
}
OutCrashContext.PrimaryCrashProperties.CrashVersion = (int)ECrashDescVersions.VER_1_NewCrashFormat;
//OutCrashContext.PrimaryCrashProperties.ProcessId = 0; don't overwrite valid ids, zero is default anyway
OutCrashContext.PrimaryCrashProperties.CrashGUID = new DirectoryInfo(NewReportPath).Name;
// OutCrashContext.PrimaryCrashProperties.IsInternalBuild
// OutCrashContext.PrimaryCrashProperties.IsPerforceBuild
// OutCrashContext.PrimaryCrashProperties.IsSourceDistribution
// OutCrashContext.PrimaryCrashProperties.SecondsSinceStart
OutCrashContext.PrimaryCrashProperties.GameName = ReportData.GameName;
// OutCrashContext.PrimaryCrashProperties.ExecutableName
// OutCrashContext.PrimaryCrashProperties.BuildConfiguration
// OutCrashContext.PrimaryCrashProperties.PlatformName
// OutCrashContext.PrimaryCrashProperties.PlatformNameIni
OutCrashContext.PrimaryCrashProperties.PlatformFullName = ReportData.Platform;
OutCrashContext.PrimaryCrashProperties.EngineMode = ReportData.EngineMode;
OutCrashContext.PrimaryCrashProperties.EngineVersion = ReportData.GetEngineVersion();
OutCrashContext.PrimaryCrashProperties.BuildVersion = ReportData.BuildVersion;
OutCrashContext.PrimaryCrashProperties.CommandLine = ReportData.CommandLine;
// OutCrashContext.PrimaryCrashProperties.LanguageLCID
// Create a locate and get the language.
int LanguageCode = 0;
int.TryParse(ReportData.Language, out LanguageCode);
try
{
if (LanguageCode > 0)
{
CultureInfo LanguageCI = new CultureInfo(LanguageCode);
OutCrashContext.PrimaryCrashProperties.AppDefaultLocale = LanguageCI.Name;
}
}
catch (Exception)
{
OutCrashContext.PrimaryCrashProperties.AppDefaultLocale = null;
}
if (string.IsNullOrEmpty(OutCrashContext.PrimaryCrashProperties.AppDefaultLocale))
{
// Default to en-US
OutCrashContext.PrimaryCrashProperties.AppDefaultLocale = "en-US";
}
// OutCrashContext.PrimaryCrashProperties.IsUE4Release
string WERUserName = ReportData.UserName;
if (!string.IsNullOrEmpty(WERUserName) || string.IsNullOrEmpty(OutCrashContext.PrimaryCrashProperties.UserName))
{
OutCrashContext.PrimaryCrashProperties.UserName = WERUserName;
}
OutCrashContext.PrimaryCrashProperties.BaseDir = ReportData.BaseDir;
// OutCrashContext.PrimaryCrashProperties.RootDir
OutCrashContext.PrimaryCrashProperties.MachineId = ReportData.MachineId;
OutCrashContext.PrimaryCrashProperties.EpicAccountId = ReportData.EpicAccountId;
// OutCrashContext.PrimaryCrashProperties.CallStack
// OutCrashContext.PrimaryCrashProperties.SourceContext
OutCrashContext.PrimaryCrashProperties.UserDescription = string.Join("\n", ReportData.UserDescription);
// OutCrashContext.PrimaryCrashProperties.CrashDumpMode
// OutCrashContext.PrimaryCrashProperties.Misc.NumberOfCores
// OutCrashContext.PrimaryCrashProperties.Misc.NumberOfCoresIncludingHyperthreads
// OutCrashContext.PrimaryCrashProperties.Misc.Is64bitOperatingSystem
// OutCrashContext.PrimaryCrashProperties.Misc.CPUVendor
// OutCrashContext.PrimaryCrashProperties.Misc.CPUBrand
// OutCrashContext.PrimaryCrashProperties.Misc.PrimaryGPUBrand
// OutCrashContext.PrimaryCrashProperties.Misc.OSVersionMajor
// OutCrashContext.PrimaryCrashProperties.Misc.OSVersionMinor
// OutCrashContext.PrimaryCrashProperties.Misc.AppDiskTotalNumberOfBytes
// OutCrashContext.PrimaryCrashProperties.Misc.AppDiskNumberOfFreeBytes
// OutCrashContext.PrimaryCrashProperties.MemoryStats.TotalPhysical
// OutCrashContext.PrimaryCrashProperties.MemoryStats.TotalVirtual
// OutCrashContext.PrimaryCrashProperties.MemoryStats.PageSize
// OutCrashContext.PrimaryCrashProperties.MemoryStats.TotalPhysicalGB
// OutCrashContext.PrimaryCrashProperties.MemoryStats.AvailablePhysical
// OutCrashContext.PrimaryCrashProperties.MemoryStats.AvailableVirtual
// OutCrashContext.PrimaryCrashProperties.MemoryStats.UsedPhysical
// OutCrashContext.PrimaryCrashProperties.MemoryStats.PeakUsedPhysical
// OutCrashContext.PrimaryCrashProperties.MemoryStats.UsedVirtual
// OutCrashContext.PrimaryCrashProperties.MemoryStats.PeakUsedVirtual
// OutCrashContext.PrimaryCrashProperties.MemoryStats.bIsOOM
// OutCrashContext.PrimaryCrashProperties.MemoryStats.OOMAllocationSize
// OutCrashContext.PrimaryCrashProperties.MemoryStats.OOMAllocationAlignment
OutCrashContext.PrimaryCrashProperties.TimeofCrash = new DateTime(ReportData.Ticks);
OutCrashContext.PrimaryCrashProperties.bAllowToBeContacted = ReportData.AllowToBeContacted;
OutCrashContext.PrimaryCrashProperties.DeploymentName = ReportData.DeploymentName;
OutCrashContext.PrimaryCrashProperties.IsEnsure = ReportData.IsEnsure;
OutCrashContext.PrimaryCrashProperties.IsAssert = ReportData.IsAssert;
OutCrashContext.PrimaryCrashProperties.CrashType = ReportData.CrashType;
GetErrorMessageFromMetadata(ReportData, OutCrashContext);
}
private static void GetErrorMessageFromMetadata(FReportData ReportData, FGenericCrashContext CrashContext)
{
if (string.IsNullOrEmpty(CrashContext.PrimaryCrashProperties.ErrorMessage))
{
CrashContext.PrimaryCrashProperties.ErrorMessage = string.Join("\n", ReportData.ErrorMessage);
}
}
/// <summary> Looks for the WER metadata xml file, if found, will return a new instance of the WERReportMetadata. </summary>
private static WERReportMetadata FindMetadata(string NewReportPath)
{
WERReportMetadata MetaData = null;
// Just to verify that the report is still there.
DirectoryInfo DirInfo = new DirectoryInfo(NewReportPath);
if (!DirInfo.Exists)
{
CrashReporterProcessServicer.WriteFailure("FindMetadata: Directory not found " + NewReportPath);
}
else
{
// Check to see if we wish to suppress processing of this report.
foreach (var Info in DirInfo.GetFiles("*.xml"))
{
var MetaDataToCheck = XmlHandler.ReadXml<WERReportMetadata>(Info.FullName);
if (CheckMetaData(MetaDataToCheck))
{
MetaData = MetaDataToCheck;
break;
}
}
}
return MetaData;
}
/// <summary> Looks for the Diagnostics.txt file and returns true if exists. </summary>
private static bool FindDiagnostics(string NewReportPath)
{
bool bFound = false;
DirectoryInfo DirInfo = new DirectoryInfo(NewReportPath);
foreach (var Info in DirInfo.GetFiles(CrashReporterConstants.DiagnosticsFileName))
{
bFound = true;
break;
}
return bFound;
}
/// <summary>
/// Optionally don't process some reports based on the Windows Error report meta data.
/// </summary>
/// <param name="WERData">The Windows Error Report meta data.</param>
/// <returns>false to reject the report.</returns>
private static bool CheckMetaData(WERReportMetadata WERData)
{
if (WERData == null)
{
return false;
}
// Reject any crashes with the invalid metadata.
if (WERData.ProblemSignatures == null || WERData.DynamicSignatures == null || WERData.OSVersionInformation == null)
{
return false;
}
// Reject any crashes from the minidump processor.
if (WERData.ProblemSignatures.Parameter0 != null && WERData.ProblemSignatures.Parameter0.ToLower() == "MinidumpDiagnostics".ToLower())
{
return false;
}
return true;
}
// From CrashUpload.cpp
/*
struct FCompressedCrashFile : FNoncopyable
{
int32 CurrentFileIndex; // 4 bytes for file index
FString Filename; // 4 bytes for length + 260 bytes for char data
TArray<uint8> Filedata; // 4 bytes for length + N bytes for data
}
*/
/// <summary> Enqueues a new crash. </summary>
private bool EnqueueNewReport(string NewReportPath)
{
string ReportName = Path.GetFileName(NewReportPath);
string CompressedReportPath = Path.Combine(NewReportPath, ReportName + ".ue4crash");
string MetadataPath = Path.Combine(NewReportPath, ReportName + ".xml");
bool bIsCompressed = File.Exists(CompressedReportPath) && File.Exists(MetadataPath);
if (bIsCompressed)
{
FCompressedCrashInformation CompressedCrashInformation = XmlHandler.ReadXml<FCompressedCrashInformation>(MetadataPath);
bool bResult = DecompressReport(CompressedReportPath, CompressedCrashInformation);
if (!bResult)
{
ReportProcessor.MoveReportToInvalid(NewReportPath, "DECOMPRESSION_FAIL_" + DateTime.Now.Ticks + "_" + ReportName);
return false;
}
else
{
// Rename metadata file to not interfere with the WERReportMetadata.
File.Move(MetadataPath, Path.ChangeExtension(MetadataPath, "processed_xml"));
}
}
// Unified crash reporting
FGenericCrashContext GenericContext = FindCrashContext(NewReportPath);
FGenericCrashContext Context = GenericContext;
bool bContextDirty = false;
WERReportMetadata MetaData = FindMetadata(NewReportPath);
if (MetaData != null)
{
if (Context == null)
{
// Missing crash context
FReportData ReportData = new FReportData(MetaData, NewReportPath);
ConvertMetadataToCrashContext(ReportData, NewReportPath, ref Context);
bContextDirty = true;
}
else if (!Context.HasProcessedData())
{
// Missing data - try to get from WER metadata
FReportData ReportData = new FReportData(MetaData, NewReportPath);
GetErrorMessageFromMetadata(ReportData, Context);
bContextDirty = true;
}
}
if (Context == null)
{
CrashReporterProcessServicer.WriteFailure("! NoCntx : Path=" + NewReportPath);
ReportProcessor.CleanReport(new DirectoryInfo(NewReportPath));
return false;
}
Context.CrashDirectory = NewReportPath;
Context.PrimaryCrashProperties.SetPlatformFullName();
// Added data from WER, save to the crash context file.
if (bContextDirty)
{
Context.ToFile();
}
FEngineVersion EngineVersion = new FEngineVersion(Context.PrimaryCrashProperties.EngineVersion);
uint BuiltFromCL = EngineVersion.Changelist;
string BranchName = EngineVersion.Branch;
if (string.IsNullOrEmpty(BranchName))
{
CrashReporterProcessServicer.WriteEvent("% Warning NoBranch: BuiltFromCL=" + string.Format("{0,7}", BuiltFromCL) + " Path=" + NewReportPath + " EngineVersion=" + Context.PrimaryCrashProperties.EngineVersion);
Context.PrimaryCrashProperties.ProcessorFailedMessage = "Engine version has no branch name. EngineVersion=" + Context.PrimaryCrashProperties.EngineVersion;
Context.ToFile();
}
else if (BranchName.Equals(CrashReporterConstants.LicenseeBranchName, StringComparison.InvariantCultureIgnoreCase))
{
CrashReporterProcessServicer.WriteEvent("% Warning branch is UE4-QA : BuiltFromCL=" + string.Format("{0,7}", BuiltFromCL) + " Path=" + NewReportPath);
Context.PrimaryCrashProperties.ProcessorFailedMessage = "Branch was the forbidden LicenseeBranchName=" + BranchName;
Context.ToFile();
}
// Look for the Diagnostics.txt, if we have a diagnostics file we can continue event if the CL is marked as broken.
bool bHasDiagnostics = FindDiagnostics(NewReportPath);
if (BuiltFromCL == 0 && (!bHasDiagnostics && !Context.HasProcessedData()))
{
// For now ignore all locally made crashes.
CrashReporterProcessServicer.WriteEvent("% Warning CL=0 and no useful data : BuiltFromCL=" + string.Format("{0,7}", BuiltFromCL) + " Path=" + NewReportPath);
Context.PrimaryCrashProperties.ProcessorFailedMessage = "Report from CL=0 has no diagnostics, callstack or error msg";
Context.ToFile();
}
// Check static reports index for duplicated reports
// This WILL happen when running multiple, non-mutually exclusive crash sources (e.g. Receiver + Data Router)
// This can be safely discontinued when all crashes come from the same SQS
// This DOES NOT scale to multiple CRP instances
// ReportIndex can be disabled by leaving the path empty in config
lock (ReportIndexLock)
{
if (!CrashReporterProcessServicer.ReportIndex.TryAddNewReport(ReportName))
{
// Crash report not accepted by index
CrashReporterProcessServicer.WriteEvent(string.Format(
"EnqueueNewReport: Duplicate report skipped {0} in queue {1}", NewReportPath, QueueName));
CrashReporterProcessServicer.StatusReporter.IncrementCount(StatusReportingEventNames.DuplicateRejected);
ReportProcessor.CleanReport(new DirectoryInfo(NewReportPath));
return false;
}
}
if (ShouldDecimateNextReport())
{
CrashReporterProcessServicer.WriteEvent(string.Format("EnqueueNewReport: Discarding Report due to backlog of {0} in queue {1}: Path={2}", GetTotalWaitingCount(), QueueName, NewReportPath));
CrashReporterProcessServicer.StatusReporter.IncrementCount(StatusReportingEventNames.ReportDiscarded);
ReportProcessor.CleanReport(new DirectoryInfo(NewReportPath));
return false;
}
lock (NewReportsLock)
{
NewCrashContexts.Enqueue(Context);
}
EnqueueCounter.AddEvent();
CrashReporterProcessServicer.StatusReporter.IncrementCount(StatusReportingEventNames.QueuedEvent);
CrashReporterProcessServicer.WriteEvent("+ Enqueued: BuiltFromCL=" + string.Format("{0,7}", BuiltFromCL) + " Path=" + NewReportPath);
return true;
}
/// <summary> Tries to dequeue a report from the list. </summary>
public bool TryDequeueReport(out FGenericCrashContext Context)
{
lock (NewReportsLock)
{
if (NewCrashContexts.Count > 0)
{
Context = NewCrashContexts.Dequeue();
DequeueCounter.AddEvent();
CrashReporterProcessServicer.StatusReporter.IncrementCount(QueueProcessingStartedEventName);
CrashReporterProcessServicer.WriteEvent(string.Format("- Dequeued: {0:N1}/minute BuiltFromCL={1,7} Path={2}", DequeueCounter.EventsPerSecond * 60, Context.PrimaryCrashProperties.EngineVersion, Context.CrashDirectory));
return true;
}
}
Context = null;
return false;
}
/// <summary>
/// Decompresses a compressed crash report.
/// </summary>
unsafe static protected bool DecompressReport(string CompressedReportPath, FCompressedCrashInformation Meta)
{
string ReportPath = Path.GetDirectoryName(CompressedReportPath);
using (FileStream FileStream = File.OpenRead(CompressedReportPath))
{
Int32 UncompressedSize = Meta.GetUncompressedSize();
Int32 CompressedSize = Meta.GetCompressedSize();
if (FileStream.Length != CompressedSize)
{
return false;
}
byte[] UncompressedBufferArray = new byte[UncompressedSize];
byte[] CompressedBufferArray = new byte[CompressedSize];
FileStream.Read(CompressedBufferArray, 0, CompressedSize);
fixed (byte* UncompressedBufferPtr = UncompressedBufferArray, CompressedBufferPtr = CompressedBufferArray)
{
Int32 UncompressResult = NativeMethods.UE4UncompressMemoryZLIB((IntPtr)UncompressedBufferPtr, UncompressedSize, (IntPtr)CompressedBufferPtr, CompressedSize);
if (UncompressResult < 0)
{
CrashReporterProcessServicer.WriteFailure("! DecompressReport() failed in UE4UncompressMemoryZLIB() with " + NativeMethods.GetUncompressError(UncompressResult));
CrashReporterProcessServicer.WriteFailure("! ZLibFail in DecompressReport(): Path=" + ReportPath);
return false;
}
}
using (BinaryReader BinaryData = new BinaryReader(new MemoryStream(UncompressedBufferArray, false)))
{
for (int FileIndex = 0; FileIndex < Meta.GetNumberOfFiles(); FileIndex++)
{
if (!WriteIncomingFile(BinaryData, FileIndex, ReportPath))
{
CrashReporterProcessServicer.WriteFailure("! DecompressReport() failed to write file index " + FileIndex + " Path=" + ReportPath);
return false;
}
}
}
}
return true;
}
protected static bool WriteIncomingFile(BinaryReader BinaryData, int FileIndex, string DestinationFolderPath)
{
try
{
Int32 CurrentFileIndex = BinaryData.ReadInt32();
if (CurrentFileIndex != FileIndex)
{
CrashReporterProcessServicer.WriteFailure("! WriteIncomingFile index mismatch: Required=" + FileIndex + " Read=" + CurrentFileIndex);
return false;
}
string Filename = FBinaryReaderHelper.ReadFixedSizeString(BinaryData);
string SafeFilename = GetSafeFilename(Filename);
Int32 FiledataLength = BinaryData.ReadInt32();
byte[] Filedata = BinaryData.ReadBytes(FiledataLength);
Directory.CreateDirectory(DestinationFolderPath);
File.WriteAllBytes(Path.Combine(DestinationFolderPath, SafeFilename), Filedata);
return true;
}
catch (Exception Ex)
{
throw new CrashReporterException(string.Format("! WriteIncomingFile failed writing FileIndex={0} FolderPath={1}", FileIndex, DestinationFolderPath), Ex);
}
}
private bool ShouldDecimateNextReport()
{
bool bShouldDecimate;
int TotalWaiting = GetTotalWaitingCount();
if (TotalWaiting < DecimateWaitingCountStart)
{
// Under lower threshold - keep all crashes
bShouldDecimate = false;
}
else if (TotalWaiting >= DecimateWaitingCountEnd)
{
// Over upper threshold - discard all crashes
bShouldDecimate = true;
}
else
{
// Between the upper and lower queue sizes we will keep crashes in proportion with the queue size
// e.g. Half way between the upper and lower limit - we will keep 50% of the crashes
float Range = DecimateWaitingCountEnd - DecimateWaitingCountStart;
float Value = TotalWaiting - DecimateWaitingCountStart;
DecimationCounter += Value / Range;
if (DecimationCounter > 1.0f)
{
DecimationCounter -= 1.0f;
bShouldDecimate = true;
}
else
{
bShouldDecimate = false;
}
}
if (bShouldDecimate)
{
CrashReporterProcessServicer.StatusReporter.Alert("ShouldDecimateNextReport - " + QueueName,
"Large backlog in " + QueueName + " queue is causing crashes to be discarded.",
Config.Default.SlackDecimateAlertRepeatMinimumMinutes);
}
return bShouldDecimate;
}
public static string GetSafeFilename(string UnsafeName)
{
return string.Join("X", UnsafeName.Split(Path.GetInvalidFileNameChars()));
}
// FIELDS
/// <summary> A list of freshly landed crash reports. </summary>
private readonly Queue<FGenericCrashContext> NewCrashContexts = new Queue<FGenericCrashContext>();
/// <summary> Object used to synchronize the access to the NewReport. </summary>
private readonly Object NewReportsLock = new Object();
private readonly EventCounter DequeueCounter = new EventCounter(TimeSpan.FromMinutes(10), 10);
/// <summary>Friendly queue name</summary>
private readonly string QueueName;
/// <summary>Incoming report path</summary>
protected readonly string LandingZone;
/// <summary>Report IDs that were in the folder on the last pass</summary>
private ReportIdSet ReportsInLandingZoneLastTimeWeChecked = new ReportIdSet();
private readonly EventCounter EnqueueCounter = new EventCounter(TimeSpan.FromMinutes(90), 20);
protected static Object ReportIndexLock = new Object();
/// <summary>
/// The number of waiting reports needed to trigger decimation (discarding a fraction of the incoming reports to stop runaway backlog)
/// </summary>
private readonly int DecimateWaitingCountStart = Int32.MaxValue;
/// <summary>
/// The number of waiting reports needed to cause all reports to be discarded
/// </summary>
private readonly int DecimateWaitingCountEnd = Int32.MaxValue;
private float DecimationCounter;
}
}
static class NativeMethods
{
[DllImport("CompressionHelper.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static Int32 UE4UncompressMemoryZLIB( /*void**/IntPtr UncompressedBuffer, Int32 UncompressedSize, /*const void**/IntPtr CompressedBuffer, Int32 CompressedSize);
public static string GetUncompressError(Int32 ErrorCode)
{
if (ErrorCode == 0) return "Z_OK";
else if (ErrorCode == 1) return "Z_STREAM_END";
else if (ErrorCode == 2) return "Z_NEED_DICT";
else if (ErrorCode == -1) return "Z_ERRNO";
else if (ErrorCode == -2) return "Z_STREAM_ERROR";
else if (ErrorCode == -3) return "Z_DATA_ERROR";
else if (ErrorCode == -4) return "Z_MEM_ERROR";
else if (ErrorCode == -5) return "Z_BUF_ERROR";
else if (ErrorCode == -6) return "Z_VERSION_ERROR";
else return "UnknownZLibError";
}
}