Files
UnrealEngineUWP/Engine/Source/Programs/CrashReporter/CrashReportProcess/ReportQueue.cs
Ben Marsh 3de35115ca Copying //UE4/Dev-Core to //UE4/Dev-Main (Source: //UE4/Dev-Core @ 3283640)
#lockdown Nick.Penwarden
#rb none

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

Change 3229011 on 2016/12/09 by Steve.Robb

	Licensee version updated in FWorldTileInfo::Read().

	https://udn.unrealengine.com/questions/325874/fworldtileinfo-not-passing-fileversionlicenseeue4.html

Change 3230493 on 2016/12/12 by Robert.Manuszewski

	Adding a check against assembling the reference token stream while streaming without locking GC.

Change 3230515 on 2016/12/12 by Steve.Robb

	GetStaticEnum and GetStaticStruct removed.
	Various generated code tidy-ups.

Change 3230522 on 2016/12/12 by Steve.Robb

	UHT no longer complains about bases with different prefixes.
	References to obsolete DependsOn removed.

Change 3230528 on 2016/12/12 by Steve.Robb

	ReferenceChainSearch tidyups.

Change 3234235 on 2016/12/14 by Robert.Manuszewski

	PR #2695: fix comments (Contributed by wyhily2010)

Change 3234237 on 2016/12/14 by Robert.Manuszewski

	PR #2614: [GenericPlatformFile] New Function, GetTimeStampLocal, returns file time stamp in local time instead of UTC   Rama (Contributed by EverNewJoy)

Change 3236214 on 2016/12/15 by Robert.Manuszewski

	PR# 1988 : Allow absolute path in -UserDir=<Path> argument (contributed by bozaro)

Change 3236582 on 2016/12/15 by Robert.Manuszewski

	Allow commandline use in shipping builds

	#jira UE-24613

Change 3236591 on 2016/12/15 by Robert.Manuszewski

	Removed unnecessary console variable logspam

	#jira UE-24614

Change 3236737 on 2016/12/15 by Steve.Robb

	Fixes to non-contiguous enums in OSS.

Change 3239686 on 2016/12/19 by Chris.Wood

	Fixed CompressionHelper method UE4CompressFileGZIP() that leaked a file handle when a compression error occurred (CRP v1.2.12)
	[UE-39910] - CrashReportProcess leaks file handles and doesn't cleanup folders after compression fails during output to S3

Change 3240687 on 2016/12/20 by Chris.Wood

	Improved CrashReportProcess retry logic to avoid stuck threads when CRW fails to add crashes (CRP 1.2.13)
	[UE-39941] - Improve CrashReportProcess retry logic when CR website returns failed response to AddCrash Request

Change 3246347 on 2017/01/04 by Steve.Robb

	Readability, debuggability and standards improvements.

Change 3249122 on 2017/01/06 by Steve.Robb

	Generic FPaths::Combine, allowing a mix of string argument types and unlimited arity.

Change 3249580 on 2017/01/06 by Steve.Robb

	Fix for TArray::HeapSort when array contains pointers.

	See: https://answers.unrealengine.com/questions/545533/bug-heapsort-with-tarray-of-pointers-fails-to-comp.html

Change 3250593 on 2017/01/09 by Robert.Manuszewski

	PR #3046: UE-39578: Added none to invalid filenames (Contributed by projectgheist)

Change 3250596 on 2017/01/09 by Robert.Manuszewski

	PR #3094: Fixing typo in comments for LODColoration in BaseEngine.ini - UE-40196 (Contributed by sanjay-nambiar)

Change 3250599 on 2017/01/09 by Robert.Manuszewski

	PR #3096: Fixed Log message in ExclusiveLoadPackageTimeTracker : UE-37583 (Contributed by sanjay-nambiar)

Change 3250863 on 2017/01/09 by Steve.Robb

	Build configuration option to force the use of the Debug version of UnrealHeaderTool.

Change 3250994 on 2017/01/09 by Ben.Zeigler

	Remove bad or redundant ini redirects. These did not work with the old system but were silently ignored, my new system throws warnings about them

Change 3251000 on 2017/01/09 by Ben.Zeigler

	#jira UE-39599 Add FCoreRedirects which replaces and unifies the redirect systems in LinkerLoad, K2Node, Enum, and TaggedProperty. This fixes various bugs and makes things uniform.
	It will parse the previous ini files, or load out of a [CoreRedirects] section in any loaded ini file
	The old redirect system can be re-enabled by setting USE_CORE_REDIRECTS to 0 in CoreRedirects.h. This will be removed eventually
	Some refactors to pass in information needed by the new system that the old system didn't need
	Add LoadTimeVerbose stats for processing redirects and enable that group during -LoadTimeFile

Change 3253580 on 2017/01/11 by Graeme.Thornton

	Added some validation of the class index in exportmap entries

	#jira UE-37873

Change 3253777 on 2017/01/11 by Graeme.Thornton

	Increase SerialSize and SerialOffset in FObjectExport to 64bits, to handle super large files

	#jira UE-39946

Change 3257750 on 2017/01/13 by Ben.Zeigler

	Fix issue where incorrectly set up animation node redirects (were ActiveClassRedirects, should have been ActiveStructRedirects) didn't work in the new redirect system because it validated more.
	Added backward compatibilty code and fixed some conflicts in the ini.

Change 3261176 on 2017/01/17 by Ben.Zeigler

	#jira UE-40746 Fix redundant ini redirect
	#jira UE-40725 Fix section of Match3 defaultengine.ini that appears to have been accidentally duplicated from baseengine.ini several years ago

Change 3261915 on 2017/01/18 by Steve.Robb

	Fixes to localized printf formats.

Change 3262142 on 2017/01/18 by Ben.Zeigler

	Remove runtime code for old ActiveClassRedirects and related systems.
	It was already disabled and the old ini format is still parsed and converted to FCoreRedirects at runtime so there should be no functionality change.
	Merged the deprecated tagged property and enum redirect ini parsing into LinkerLoad, and remove the RemapImports step entirely as it's part of FixupImportMap.

Change 3263596 on 2017/01/19 by Gil.Gribb

	UE4 - Fixed many bugs with the event driven loader and allowed it to work at boot time.

Change 3263597 on 2017/01/19 by Gil.Gribb

	UE4 - Allowed UnrealPak to do a better job with EDL pak files when the order provided is old or from the cooker. Several minor tweaks to low level async IO stuff in support of switch experiments.

Change 3263922 on 2017/01/19 by Gil.Gribb

	UE4 - Fixed a bug with nativized blueprints that was introduced with the boot time EDL changes.

Change 3264131 on 2017/01/19 by Robert.Manuszewski

	Simple app to test hard to repro bugs

Change 3264849 on 2017/01/19 by Ben.Zeigler

	Change FParse::Value to treat ) like , for parsing to handle config parsing struct format. This fixes cases where lines end with bool or FName variables that aren't written out quoted:
	+ClassRedirects=(OldName="LandscapeProxy",NewName="LandscapeStreamingProxy",InstanceOnly=True)

Change 3265232 on 2017/01/19 by Ben.Zeigler

	#jira UE-39599 Finish class redirect refactor by cleaning up BaseEngine.ini
	Move plugin-specific redirects to new plugin ini files
	Move all redirects from BaseEngine.ini prior to 4.11 to native registration in FCoreRedirects. Needed to split up functions to avoid long compile times
	Move all redirects after 4.11 to new ini format
	Some related blueprint fixup code changes, these weren't cooperating well with some ini redirects

Change 3265490 on 2017/01/20 by Steve.Robb

	Prevent engine reinstancing on hot reload.

	#jira UE-40765

Change 3265593 on 2017/01/20 by Gil.Gribb

	UE4 - Stored a copy of the callback in async read request so that we don't need to worry about lifetime so we can capture variables as needed. Also fixed race in audio streaming.

Change 3266003 on 2017/01/20 by Gil.Gribb

	UE4 - Fixed bug which would cause a fatal error when cooking subobjects that were pending kill.

Change 3267433 on 2017/01/22 by Gil.Gribb

	UE4 - Fixed a bug with EDL at boot time which caused a fatal error with unfired imports.

Change 3267677 on 2017/01/23 by Steve.Robb

	Fix for whitespace before UCLASS() causing compile errors.

	#jira UE-24110

Change 3267685 on 2017/01/23 by Steve.Robb

	First pass of fixes to printf-style calls to only use TCHAR[] specifiers.

Change 3267746 on 2017/01/23 by Steven.Hutton

	Resolve offline work

	Changes to repositories to support better handling of db connections.

Change 3267865 on 2017/01/23 by Steve.Robb

	Clarification of TArray::FindLastByPredicate() and FString::FindLastCharByPredicate().

	#fyi nick.darnell

Change 3268075 on 2017/01/23 by Gil.Gribb

	UE4 - Fixed another bug with RF_PendingKill subobjects and the new loader.

Change 3268447 on 2017/01/23 by Gil.Gribb

	Fortnite - Removed calls to ::StaticClass() before main starts; this is not allowed.

Change 3269491 on 2017/01/24 by Gil.Gribb

	UE4 - Cancelling async loading with the EDL loader now prints a warning and does a flush instead.

Change 3269492 on 2017/01/24 by Gil.Gribb

	UE4 - Suppressed a few EDL cook wanrings.

Change 3270085 on 2017/01/24 by Gil.Gribb

	UE4 - Remove pak highwater spam.

Change 3270089 on 2017/01/24 by Gil.Gribb

	UE4 - fix random bug with memory counting and some vertex buffer

Change 3271246 on 2017/01/25 by Chris.Wood

	Fixed CrashReportProcess pipeline for Mac and Linux crashes lacking machine Ids (CRP v1.2.14)
	[UE-40605] - Machine ID is not being shown on the crashreporter website

Change 3271827 on 2017/01/25 by Steve.Robb

	C4946 warning disabled in third party headers (triggers in Clang/LLVM).

Change 3271874 on 2017/01/25 by Steve.Robb

	Fix for missing error check after header preparsing.

Change 3271911 on 2017/01/25 by Steve.Robb

	ObjectMacros.h now automatically included by generated headers.

	#fyi jamie.dale

Change 3273125 on 2017/01/26 by Steve.Robb

	Check to ensure that a .generated.h header is included by headers which have exported types, to avoid crazy compiler errors.

	#fyi james.golding

Change 3273209 on 2017/01/26 by Steve.Robb

	UnrealCodeAnalyzer compilation fixes.

Change 3274917 on 2017/01/27 by Steve.Robb

	GC disabled when recompiling child BPs, as is already the case for the parent (CL# 2731120).
	Now-unused field removed.

Change 3279091 on 2017/01/31 by Ben.Marsh

	UBT: Remove code paths which assume relative paths based on a particular CWD. Use the absolute paths stored in UnrealBuildTool.RootDirectory/UnrealBuildTool.EngineDirectory instead.

Change 3279195 on 2017/01/31 by Gil.Gribb

	Turned EDL on for orion

Change 3279493 on 2017/01/31 by Ben.Zeigler

	#jira UE-41341 Redo redirector fixups that got undone in merge from Main

Change 3280284 on 2017/01/31 by Ben.Zeigler

	#jira UE-41357 Fix typo in vehicle redirect. Also fix base crash when converting old content with nodes that don't exist.
	Fix issues with loading plugin ini files. They weren't properly "diffing" against the base/default source file so my redirect typo fix didn't propagate.
	Some general config system refactors on Josh's advice, and make base.ini optional if reading out of a non-standard engine directory
	Engine plugin ini are now BasePlugin.ini, game plugins are still DefaultPlugin.ini.
	Fix crash when loading old content pointing to nonexistent node type. It will still error/ensure but won't crash.

Change 3280299 on 2017/01/31 by Gil.Gribb

	possibly fix edl at boot with orion server....though was no-repro

Change 3280386 on 2017/01/31 by Ben.Zeigler

	Header include fixes for -nopch, fixes incremental build

Change 3280557 on 2017/01/31 by Ben.Zeigler

	Fix Config crash. FConfigFile's copy constructor is apparently not safe and resulted in garbage memory in some cases

Change 3280817 on 2017/02/01 by Steve.Robb

	Unused SmartCastProperty removed.

Change 3280897 on 2017/02/01 by Chris.Wood

	Improved CRP shutdown code to abort AddCrash requests when cancel is requested (CRP v1.2.15)
	[UE-41338] - Fix CRP shutdown when website isn't accepting new crashes

	Also, improved shutdown code to try to avoid occassional exception when writing out the report index. Looks like it isn't shutting down worker threads cleanly sometimes. Added more logging to this too.

Change 3280989 on 2017/02/01 by Gil.Gribb

	New unrealpak binaries

Change 3281416 on 2017/02/01 by Michael.Trepka

	Updated UnrealPak binaries for Mac

Change 3282457 on 2017/02/01 by Ben.Zeigler

	#jira UE-41425 Protect against issues with streamable manager requests recursively completing by caching the array locally.
	This code is safer in general in my local version so just doing a quick fix for now

Change 3282619 on 2017/02/01 by Arciel.Rekman

	Linux: update UnrealPak.

[CL 3283649 by Ben Marsh in Main branch]
2017-02-02 14:41:50 -05:00

708 lines
26 KiB
C#

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
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.LoginId = ReportData.LoginId;
if (string.IsNullOrWhiteSpace(OutCrashContext.PrimaryCrashProperties.MachineId))
{
// Set MachineId from LoginId if the metadata is too new to contain MachineId
OutCrashContext.PrimaryCrashProperties.MachineId = OutCrashContext.PrimaryCrashProperties.LoginId;
}
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
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>
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);
int UncompressResult = NativeMethods.UncompressMemoryZlib(UncompressedBufferArray, CompressedBufferArray);
if (UncompressResult < 0)
{
CrashReporterProcessServicer.WriteFailure("! DecompressReport() failed in UncompressMemoryZlib() with " + NativeMethods.GetZlibError(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);
/// <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;
}
}