Create the SimpleStackFrameDeminfier which uses significantly less memory than the StackTraceDeminifier during runtime.

This commit is contained in:
Christian Gonzalez 2016-11-15 17:45:05 -08:00
Родитель 411d1a54ab
Коммит 64538ebc58
13 изменённых файлов: 137 добавлений и 177 удалений

Просмотреть файл

@ -12,17 +12,15 @@ namespace SourcemapToolkit.CallstackDeminifier
/// Returns a FunctionMap describing the locations of every funciton in the source code. /// Returns a FunctionMap describing the locations of every funciton in the source code.
/// The functions are to be sorted descending by start position. /// The functions are to be sorted descending by start position.
/// </summary> /// </summary>
public List<FunctionMapEntry> GenerateFunctionMap(StreamReader sourceCodeStreamReader, StreamReader sourceMapStreamReader) public List<FunctionMapEntry> GenerateFunctionMap(StreamReader sourceCodeStreamReader, SourceMap sourceMap)
{ {
if (sourceCodeStreamReader == null || sourceMapStreamReader == null) if (sourceCodeStreamReader == null || sourceMap == null)
{ {
return null; return null;
} }
List<FunctionMapEntry> result = ParseSourceCode(sourceMapStreamReader); List<FunctionMapEntry> result = ParseSourceCode(sourceCodeStreamReader);
SourceMapParser sourceMapParser = new SourceMapParser();
SourceMap sourceMap = sourceMapParser.ParseSourceMap(sourceMapStreamReader);
foreach (FunctionMapEntry functionMapEntry in result) foreach (FunctionMapEntry functionMapEntry in result)
{ {
functionMapEntry.DeminfifiedMethodName = GetDeminifiedMethodNameFromSourceMap(functionMapEntry, sourceMap); functionMapEntry.DeminfifiedMethodName = GetDeminifiedMethodNameFromSourceMap(functionMapEntry, sourceMap);

Просмотреть файл

@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using SourcemapToolkit.SourcemapParser;
namespace SourcemapToolkit.CallstackDeminifier namespace SourcemapToolkit.CallstackDeminifier
{ {
@ -10,12 +12,12 @@ namespace SourcemapToolkit.CallstackDeminifier
private readonly IFunctionMapGenerator _functionMapGenerator; private readonly IFunctionMapGenerator _functionMapGenerator;
private readonly KeyValueCache<string,List<FunctionMapEntry>> _functionMapCache; private readonly KeyValueCache<string,List<FunctionMapEntry>> _functionMapCache;
public FunctionMapStore(ISourceCodeProvider sourceCodeProvider, ISourceMapProvider sourceMapProvider) public FunctionMapStore(ISourceCodeProvider sourceCodeProvider, Func<string, SourceMap> sourceMapGetter)
{ {
_functionMapGenerator = new FunctionMapGenerator(); _functionMapGenerator = new FunctionMapGenerator();
_functionMapCache = new KeyValueCache<string, List<FunctionMapEntry>>(sourceCodeUrl => _functionMapGenerator.GenerateFunctionMap( _functionMapCache = new KeyValueCache<string, List<FunctionMapEntry>>(sourceCodeUrl => _functionMapGenerator.GenerateFunctionMap(
sourceCodeProvider.GetSourceCode(sourceCodeUrl), sourceCodeProvider.GetSourceCode(sourceCodeUrl),
sourceMapProvider.GetSourceMapContentsForCallstackUrl(sourceCodeUrl))); sourceMapGetter(sourceCodeUrl)));
} }
/// <summary> /// <summary>

Просмотреть файл

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using SourcemapToolkit.SourcemapParser;
namespace SourcemapToolkit.CallstackDeminifier namespace SourcemapToolkit.CallstackDeminifier
{ {
@ -9,6 +10,6 @@ namespace SourcemapToolkit.CallstackDeminifier
/// Returns a FunctionMap describing the locations of every funciton in the source code. /// Returns a FunctionMap describing the locations of every funciton in the source code.
/// The functions are to be sorted in decreasing order by start position. /// The functions are to be sorted in decreasing order by start position.
/// </summary> /// </summary>
List<FunctionMapEntry> GenerateFunctionMap(StreamReader sourceCodeStreamReader, StreamReader sourceMapStreamReader); List<FunctionMapEntry> GenerateFunctionMap(StreamReader sourceCodeStreamReader, SourceMap sourceMap);
} }
} }

Просмотреть файл

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
namespace SourcemapToolkit.CallstackDeminifier
{
/// <summary>
/// This class only deminfies the method name in a stack frame. It does not depend on having a source map available during runtime.
/// </summary>
internal class SimpleStackFrameDeminifier : IStackFrameDeminifier
{
protected readonly IFunctionMapConsumer _functionMapConsumer;
protected readonly IFunctionMapStore _functionMapStore;
public SimpleStackFrameDeminifier(IFunctionMapStore functionMapStore, IFunctionMapConsumer functionMapConsumer)
{
_functionMapStore = functionMapStore;
_functionMapConsumer = functionMapConsumer;
}
public virtual StackFrame DeminifyStackFrame(StackFrame stackFrame)
{
if (stackFrame == null)
{
throw new ArgumentNullException(nameof(stackFrame));
}
FunctionMapEntry wrappingFunction = null;
// This code deminifies the stack frame by finding the wrapping function in
// the generated code and then using the source map to find the name and
// and original source location.
List<FunctionMapEntry> functionMap = _functionMapStore.GetFunctionMapForSourceCode(stackFrame.FilePath);
if (functionMap != null)
{
wrappingFunction =
_functionMapConsumer.GetWrappingFunctionForSourceLocation(stackFrame.SourcePosition, functionMap);
}
return new StackFrame {MethodName = wrappingFunction?.DeminfifiedMethodName};
}
}
}

Просмотреть файл

@ -65,10 +65,12 @@
<Compile Include="IStackFrameDeminifier.cs" /> <Compile Include="IStackFrameDeminifier.cs" />
<Compile Include="IStackTraceParser.cs" /> <Compile Include="IStackTraceParser.cs" />
<Compile Include="KeyValueCache.cs" /> <Compile Include="KeyValueCache.cs" />
<Compile Include="SimpleStackFrameDeminifier.cs" />
<Compile Include="SourceMapStore.cs" /> <Compile Include="SourceMapStore.cs" />
<Compile Include="StackFrame.cs" /> <Compile Include="StackFrame.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StackFrameDeminifier.cs" /> <Compile Include="StackFrameDeminifier.cs" />
<Compile Include="StackTraceDeminfierFactory.cs" />
<Compile Include="StackTraceDeminifier.cs" /> <Compile Include="StackTraceDeminifier.cs" />
<Compile Include="StackTraceParser.cs" /> <Compile Include="StackTraceParser.cs" />
</ItemGroup> </ItemGroup>

Просмотреть файл

@ -1,70 +1,42 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using SourcemapToolkit.SourcemapParser; using SourcemapToolkit.SourcemapParser;
namespace SourcemapToolkit.CallstackDeminifier namespace SourcemapToolkit.CallstackDeminifier
{ {
/// <summary> /// <summary>
/// Class responsible for deminifying a single stack frame in a minified stack trace. /// Class responsible for deminifying a single stack frame in a minified stack trace.
/// </summary> /// </summary>
internal class StackFrameDeminifier : IStackFrameDeminifier internal class StackFrameDeminifier : SimpleStackFrameDeminifier
{ {
private readonly IFunctionMapConsumer _functionMapConsumer;
private readonly IFunctionMapStore _functionMapStore;
private readonly ISourceMapStore _sourceMapStore; private readonly ISourceMapStore _sourceMapStore;
public StackFrameDeminifier(ISourceMapStore sourceMapStore, IFunctionMapStore functionMapStore, IFunctionMapConsumer functionMapConsumer) public StackFrameDeminifier(ISourceMapStore sourceMapStore, IFunctionMapStore functionMapStore, IFunctionMapConsumer functionMapConsumer) : base (functionMapStore, functionMapConsumer)
{ {
_functionMapStore = functionMapStore;
_sourceMapStore = sourceMapStore; _sourceMapStore = sourceMapStore;
_functionMapConsumer = functionMapConsumer;
} }
/// <summary> /// <summary>
/// This method will deminify a single stack from from a minified stack trace. /// This method will deminify a single stack from from a minified stack trace.
/// </summary> /// </summary>
/// <returns>Returns a stack trace that has been translated to a best guess of the original source code. Any of the fields in the stack frame may be null</returns> /// <returns>Returns a stack trace that has been translated to a best guess of the original source code. Any of the fields in the stack frame may be null</returns>
public StackFrame DeminifyStackFrame(StackFrame stackFrame) public override StackFrame DeminifyStackFrame(StackFrame stackFrame)
{ {
if (stackFrame == null) if (stackFrame == null)
{ {
throw new ArgumentNullException(nameof(stackFrame)); throw new ArgumentNullException(nameof(stackFrame));
} }
FunctionMapEntry wrappingFunction = null; SourceMap sourceMap = _sourceMapStore.GetSourceMapForUrl(stackFrame.FilePath);
SourceMap sourceMap = _sourceMapStore.GetSourceMapForUrl(stackFrame.FilePath); SourcePosition generatedSourcePosition = stackFrame.SourcePosition;
// This code deminifies the stack frame by finding the wrapping function in StackFrame result = base.DeminifyStackFrame(stackFrame);
// the generated code and then using the source map to find the name and
// and original source location.
List<FunctionMapEntry> functionMap = _functionMapStore.GetFunctionMapForSourceCode(stackFrame.FilePath);
if (functionMap != null)
{
wrappingFunction =
_functionMapConsumer.GetWrappingFunctionForSourceLocation(stackFrame.SourcePosition, functionMap);
}
return ExtractFrameInformationFromSourceMap(wrappingFunction, sourceMap, stackFrame.SourcePosition); MappingEntry generatedSourcePositionMappingEntry = sourceMap?.GetMappingEntryForGeneratedSourcePosition(generatedSourcePosition);
} result.FilePath = generatedSourcePositionMappingEntry?.OriginalFileName;
result.SourcePosition = generatedSourcePositionMappingEntry?.OriginalSourcePosition;
/// <summary> return result;
/// Gets the information necessary for a deminified stack frame from the relevant source map.
/// </summary>
/// <param name="wrappingFunction">The function that wraps the current stack frame location</param>
/// <param name="sourceMap">The relevant source map for this generated code</param>
/// <param name="generatedSourcePosition">The location that should be translated to original source code location in the deminified stack frame.</param>
/// <returns>Returns a StackFrame object with best guess values for each property. Any of the properties may be null if no match was found.</returns>
internal static StackFrame ExtractFrameInformationFromSourceMap(FunctionMapEntry wrappingFunction, SourceMap sourceMap, SourcePosition generatedSourcePosition)
{
StackFrame result = new StackFrame();
result.MethodName = wrappingFunction.DeminfifiedMethodName;
MappingEntry generatedSourcePositionMappingEntry = sourceMap?.GetMappingEntryForGeneratedSourcePosition(generatedSourcePosition);
result.FilePath = generatedSourcePositionMappingEntry?.OriginalFileName;
result.SourcePosition = generatedSourcePositionMappingEntry?.OriginalSourcePosition;
return result;
} }
} }
} }

Просмотреть файл

@ -0,0 +1,56 @@
using System;
using SourcemapToolkit.SourcemapParser;
namespace SourcemapToolkit.CallstackDeminifier
{
public class StackTraceDeminfierFactory
{
private void ValidateArguments(ISourceMapProvider sourceMapProvider, ISourceCodeProvider generatedCodeProvider)
{
if (sourceMapProvider == null)
{
throw new ArgumentNullException(nameof(sourceMapProvider));
}
if (generatedCodeProvider == null)
{
throw new ArgumentNullException(nameof(generatedCodeProvider));
}
}
/// <summary>
/// Creates a StackTraceDeminifier with full capabilities. StackTrace deminifiers created with this method will keep source maps cached, and thus use significantly more memory during runtime than the MethodNameStackTraceDeminifier.
/// </summary>
/// <param name="sourceMapProvider">Consumers of the API should implement this interface, which provides the source map for a given JavaScript file. Throws ArgumentNullException if the parameter is set to null.</param>
/// <param name="generatedCodeProvider">Consumers of the API should implement this interface, which provides the contents of a JavaScript file. Throws ArgumentNullException if the parameter is set to null.</param>
public StackTraceDeminifier GetStackTraceDeminfier(ISourceMapProvider sourceMapProvider, ISourceCodeProvider generatedCodeProvider)
{
ValidateArguments(sourceMapProvider, generatedCodeProvider);
ISourceMapStore sourceMapStore = new SourceMapStore(sourceMapProvider);
IStackFrameDeminifier stackFrameDeminifier = new StackFrameDeminifier(sourceMapStore,
new FunctionMapStore(generatedCodeProvider, sourceMapStore.GetSourceMapForUrl), new FunctionMapConsumer());
IStackTraceParser stackTraceParser = new StackTraceParser();
return new StackTraceDeminifier(stackFrameDeminifier, stackTraceParser);
}
/// <summary>
/// Creates a StackTraceDeminifier that only deminifies the method names. StackTrace deminifiers created with this method will use significantly less memory during runtime than the
/// </summary>
/// <param name="sourceMapProvider">Consumers of the API should implement this interface, which provides the source map for a given JavaScript file. Throws ArgumentNullException if the parameter is set to null.</param>
/// <param name="generatedCodeProvider">Consumers of the API should implement this interface, which provides the contents of a JavaScript file. Throws ArgumentNullException if the parameter is set to null.</param>
public StackTraceDeminifier GetMethodNameOnlyStackTraceDeminfier(ISourceMapProvider sourceMapProvider, ISourceCodeProvider generatedCodeProvider)
{
ValidateArguments(sourceMapProvider, generatedCodeProvider);
SourceMapParser sourceMapParser = new SourceMapParser();
IStackFrameDeminifier stackFrameDeminifier = new SimpleStackFrameDeminifier(new FunctionMapStore(generatedCodeProvider, (url) => sourceMapParser.ParseSourceMap(sourceMapProvider.GetSourceMapContentsForCallstackUrl(url))), new FunctionMapConsumer());
IStackTraceParser stackTraceParser = new StackTraceParser();
return new StackTraceDeminifier(stackFrameDeminifier, stackTraceParser);
}
}
}

Просмотреть файл

@ -1,38 +1,17 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace SourcemapToolkit.CallstackDeminifier namespace SourcemapToolkit.CallstackDeminifier
{ {
/// <summary> /// <summary>
/// Public API for parsing and deminifying browser stack traces. /// This class is responsible for parsing a callstack string into
/// </summary> /// a list of StackFrame objects and providing the deminified version
public class StackTraceDeminifier /// of the stack frame.
/// </summary>
public class StackTraceDeminifier
{ {
private readonly IStackFrameDeminifier _stackFrameDeminifier; private readonly IStackFrameDeminifier _stackFrameDeminifier;
private readonly IStackTraceParser _stackTraceParser; private readonly IStackTraceParser _stackTraceParser;
/// <summary>
/// This class is responsible for parsing a callstack string into
/// a list of StackFrame objects and providing the deminified version
/// of the stack frame.
/// </summary>
/// <param name="sourceMapProvider">Consumers of the API should implement this interface, which provides the source map for a given JavaScript file. Throws ArgumentNullException if the parameter is set to null.</param>
/// <param name="generatedCodeProvider">Consumers of the API should implement this interface, which provides the contents of a JavaScript file. Throws ArgumentNullException if the parameter is set to null.</param>
public StackTraceDeminifier(ISourceMapProvider sourceMapProvider, ISourceCodeProvider generatedCodeProvider)
: this(new StackFrameDeminifier(new SourceMapStore(sourceMapProvider),
new FunctionMapStore(generatedCodeProvider, sourceMapProvider), new FunctionMapConsumer()), new StackTraceParser())
{
if (sourceMapProvider == null)
{
throw new ArgumentNullException(nameof(sourceMapProvider));
}
if (generatedCodeProvider == null)
{
throw new ArgumentNullException(nameof(generatedCodeProvider));
}
}
internal StackTraceDeminifier(IStackFrameDeminifier stackFrameDeminifier, IStackTraceParser stackTraceParser) internal StackTraceDeminifier(IStackFrameDeminifier stackFrameDeminifier, IStackTraceParser stackTraceParser)
{ {
_stackFrameDeminifier = stackFrameDeminifier; _stackFrameDeminifier = stackFrameDeminifier;

Просмотреть файл

@ -70,7 +70,6 @@
<Compile Include="StackTraceDeminifierUnitTests.cs" /> <Compile Include="StackTraceDeminifierUnitTests.cs" />
<Compile Include="StackTraceParserUnitTests.cs" /> <Compile Include="StackTraceParserUnitTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StackFrameDeminifierUnitTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\SourceMapToolkit.CallstackDeminifier\SourcemapToolkit.CallstackDeminifier.csproj"> <ProjectReference Include="..\..\src\SourceMapToolkit.CallstackDeminifier\SourcemapToolkit.CallstackDeminifier.csproj">

Просмотреть файл

@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using SourcemapToolkit.SourcemapParser;
namespace SourcemapToolkit.CallstackDeminifier.UnitTests
{
[TestClass]
public class StackFrameDeminifierUnitTests
{
private IStackFrameDeminifier GetStackFrameDeminifierWithMockDependencies(ISourceMapStore sourceMapStore = null, IFunctionMapStore functionMapStore = null, IFunctionMapConsumer functionMapConsumer = null)
{
if (sourceMapStore == null)
{
sourceMapStore = MockRepository.GenerateStrictMock<ISourceMapStore>();
}
if (functionMapStore == null)
{
functionMapStore = MockRepository.GenerateStrictMock<IFunctionMapStore>();
}
if (functionMapConsumer == null)
{
functionMapConsumer = MockRepository.GenerateStrictMock<IFunctionMapConsumer>();
}
return new StackFrameDeminifier(sourceMapStore, functionMapStore, functionMapConsumer);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void DeminifyStackFrame_NullInputStackFrame_ThrowsException()
{
// Arrange
IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies();
StackFrame stackFrame = null;
// Act
StackFrame deminifiedStackFrame = stackFrameDeminifier.DeminifyStackFrame(stackFrame);
}
[TestMethod]
public void ExtractFrameInformationFromSourceMap_HasMatchingGeneratedPositionMapping_ReturnsStackFrameWithSourcePositionAndFileName()
{
// Arrange
FunctionMapEntry functionMapEntry = null;
SourcePosition generatedSourcePosition = new SourcePosition
{
ZeroBasedColumnNumber = 25,
ZeroBasedLineNumber = 85
};
SourceMap sourceMap = MockRepository.GenerateStub<SourceMap>();
sourceMap.Stub(x => x.GetMappingEntryForGeneratedSourcePosition(generatedSourcePosition)).Return(new MappingEntry
{
OriginalSourcePosition = new SourcePosition { ZeroBasedColumnNumber = 10, ZeroBasedLineNumber = 20 }
});
// Act
StackFrame deminifiedStackFrame = StackFrameDeminifier.ExtractFrameInformationFromSourceMap(functionMapEntry, sourceMap, generatedSourcePosition);
// Assert
Assert.AreEqual(10, deminifiedStackFrame.SourcePosition.ZeroBasedColumnNumber);
Assert.AreEqual(20, deminifiedStackFrame.SourcePosition.ZeroBasedLineNumber);
}
}
}

Просмотреть файл

@ -20,7 +20,8 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
ISourceCodeProvider sourceCodeProvider = MockRepository.GenerateStrictMock<ISourceCodeProvider>(); ISourceCodeProvider sourceCodeProvider = MockRepository.GenerateStrictMock<ISourceCodeProvider>();
sourceCodeProvider.Stub(x => x.GetSourceCode("http://localhost:11323/crashcauser.js")).Return(UnitTestUtils.StreamReaderFromString(GeneratedCodeString)); sourceCodeProvider.Stub(x => x.GetSourceCode("http://localhost:11323/crashcauser.js")).Return(UnitTestUtils.StreamReaderFromString(GeneratedCodeString));
return new StackTraceDeminifier(sourceMapProvider, sourceCodeProvider); StackTraceDeminfierFactory stackTraceDeminfierFactory = new StackTraceDeminfierFactory();
return stackTraceDeminfierFactory.GetStackTraceDeminfier(sourceMapProvider, sourceCodeProvider);
} }
private void ValidateDeminifyStackTraceResults(DeminifyStackTraceResult results) private void ValidateDeminifyStackTraceResults(DeminifyStackTraceResult results)

Просмотреть файл

@ -18,8 +18,9 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
ISourceCodeProvider sourceCodeProvider = MockRepository.GenerateStrictMock<ISourceCodeProvider>(); ISourceCodeProvider sourceCodeProvider = MockRepository.GenerateStrictMock<ISourceCodeProvider>();
sourceCodeProvider.Stub(x => x.GetSourceCode("http://localhost:11323/crashcauser.min.js")).Return(UnitTestUtils.StreamReaderFromString(GeneratedCodeString)); sourceCodeProvider.Stub(x => x.GetSourceCode("http://localhost:11323/crashcauser.min.js")).Return(UnitTestUtils.StreamReaderFromString(GeneratedCodeString));
return new StackTraceDeminifier(sourceMapProvider, sourceCodeProvider); StackTraceDeminfierFactory stackTraceDeminfierFactory = new StackTraceDeminfierFactory();
} return stackTraceDeminfierFactory.GetStackTraceDeminfier(sourceMapProvider, sourceCodeProvider);
}
private static void ValidateDeminifyStackTraceResults(DeminifyStackTraceResult results) private static void ValidateDeminifyStackTraceResults(DeminifyStackTraceResult results)
{ {

Просмотреть файл

@ -8,30 +8,6 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
[TestClass] [TestClass]
public class StackTraceDeminifierUnitTests public class StackTraceDeminifierUnitTests
{ {
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void StackTraceDeminifier_NullSourceMapProvider_ThrowsException()
{
// Arrange
ISourceCodeProvider sourceCodeProvider = MockRepository.GenerateStrictMock<ISourceCodeProvider>();
ISourceMapProvider sourceMapProvider = null;
// Act
StackTraceDeminifier stackTraceDeminifier = new StackTraceDeminifier(sourceMapProvider, sourceCodeProvider);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void StackTraceDeminifier_NullSourceCodeProvider_ThrowsException()
{
// Arrange
ISourceCodeProvider sourceCodeProvider = null;
ISourceMapProvider sourceMapProvider = MockRepository.GenerateStrictMock<ISourceMapProvider>();
// Act
StackTraceDeminifier stackTraceDeminifier = new StackTraceDeminifier(sourceMapProvider, sourceCodeProvider);
}
[TestMethod] [TestMethod]
public void DeminifyStackTrace_UnableToParseStackTraceString_ReturnsEmptyList() public void DeminifyStackTrace_UnableToParseStackTraceString_ReturnsEmptyList()
{ {