Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Commit

Permalink
Produce a DGML graph from the ApiPort data (#695)
Browse files Browse the repository at this point in the history
* Add project for generating a DGML file from the results.

* Capture information about the referenced assemblies for each user assembly

* Introduce a graph representation of the data

* Add methods to provide access to the raw number of APIs that are called (available and not)

* Display the portability of the references for each assembly
  • Loading branch information
AlexGhiondea authored and twsouthwick committed Sep 25, 2018
1 parent ac782cc commit a90b32b
Show file tree
Hide file tree
Showing 13 changed files with 585 additions and 2 deletions.
51 changes: 51 additions & 0 deletions PortabilityTools.sln
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiPort", "ApiPort", "{C2CF
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{CB5759DE-9D7B-4B21-89BC-E81920D611BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Fx.Portability.Reports.DGML", "src\lib\Microsoft.Fx.Portability.Reports.DGML\Microsoft.Fx.Portability.Reports.DGML.csproj", "{1B6E53A7-9180-4D79-9556-E5CE59483EA1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1046,6 +1048,54 @@ Global
{2DFBC0D7-E65F-4F85-9E60-5F6C4BA8F4FE}.Ubuntu_Release|x64.Build.0 = Release|Any CPU
{2DFBC0D7-E65F-4F85-9E60-5F6C4BA8F4FE}.Ubuntu_Release|x86.ActiveCfg = Release|Any CPU
{2DFBC0D7-E65F-4F85-9E60-5F6C4BA8F4FE}.Ubuntu_Release|x86.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|ARM.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|ARM.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|x64.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|x64.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|x86.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Debug|x86.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|Any CPU.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|ARM.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|ARM.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|x64.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|x64.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|x86.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Debug|x86.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|Any CPU.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|Any CPU.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|ARM.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|ARM.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|x64.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|x64.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|x86.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Osx_Release|x86.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|Any CPU.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|ARM.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|ARM.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|x64.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|x64.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|x86.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Release|x86.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|Any CPU.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|ARM.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|ARM.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|x64.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|x64.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|x86.ActiveCfg = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Debug|x86.Build.0 = Debug|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|Any CPU.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|Any CPU.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|ARM.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|ARM.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|x64.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|x64.Build.0 = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|x86.ActiveCfg = Release|Any CPU
{1B6E53A7-9180-4D79-9556-E5CE59483EA1}.Ubuntu_Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1076,6 +1126,7 @@ Global
{D66AC566-3B80-46F0-8687-3C5F4D203F1A} = {6234AABE-C4F3-4094-9C0D-FFD589235DBE}
{C2CF3FE7-5A24-4FEF-B833-86FBAC5D5731} = {7DC7AA2C-0401-495B-B42C-32F44085EBE6}
{CB5759DE-9D7B-4B21-89BC-E81920D611BB} = {7DC7AA2C-0401-495B-B42C-32F44085EBE6}
{1B6E53A7-9180-4D79-9556-E5CE59483EA1} = {D66AC566-3B80-46F0-8687-3C5F4D203F1A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E8B2DB2-4847-4909-8631-A995D50F10EF}
Expand Down
1 change: 1 addition & 0 deletions src/ApiPort/ApiPort/ApiPort.Offline.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<ProjectReference Include="..\..\lib\Microsoft.Fx.Portability.Offline\Microsoft.Fx.Portability.Offline.csproj" />
<ProjectReference Include="..\..\lib\Microsoft.Fx.Portability.Reports.DGML\Microsoft.Fx.Portability.Reports.DGML.csproj" />
<ProjectReference Include="..\..\lib\Microsoft.Fx.Portability.Reports.Excel\Microsoft.Fx.Portability.Reports.Excel.csproj" />
<ProjectReference Include="..\..\lib\Microsoft.Fx.Portability.Reports.Html\Microsoft.Fx.Portability.Reports.Html.csproj" Condition=" '$(TargetFramework)' == 'net461' " />
<ProjectReference Include="..\..\lib\Microsoft.Fx.Portability.Reports.Json\Microsoft.Fx.Portability.Reports.Json.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public DependencyFinderEngineHelper(IDependencyFilter assemblyFilter, MetadataRe
Location = file.Name,
AssemblyIdentity = metadataReader.FormatAssemblyInfo().ToString(),
FileVersion = file.Version ?? string.Empty,
TargetFrameworkMoniker = metadataReader.GetTargetFrameworkMoniker() ?? string.Empty
TargetFrameworkMoniker = metadataReader.GetTargetFrameworkMoniker() ?? string.Empty,
AssemblyReferences = ComputeAssemblyReferences(metadataReader)
};

// Get assembly info
Expand All @@ -39,6 +40,24 @@ public DependencyFinderEngineHelper(IDependencyFilter assemblyFilter, MetadataRe
_currentAssemblyName = _reader.GetString(assemblyDefinition.Name);
}

private IList<AssemblyReferenceInformation> ComputeAssemblyReferences(MetadataReader metadataReader)
{
var refs = new List<AssemblyReferenceInformation>();
foreach (var handle in _reader.AssemblyReferences)
{
try
{
var entry = _reader.GetAssemblyReference(handle);

refs.Add(metadataReader.FormatAssemblyInfo(entry));
}
catch (BadImageFormatException)
{
}
}
return refs;
}

public AssemblyInfo CallingAssembly { get; }

public IList<MemberDependency> MemberDependency { get; }
Expand Down
138 changes: 138 additions & 0 deletions src/lib/Microsoft.Fx.Portability.Reports.DGML/DGMLManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;

namespace Microsoft.Fx.Portability.Reports.DGML
{
/// <summary>
/// This class will manage the IDs that we generate for the DGML graph
/// </summary>
internal class DGMLManager
{
private readonly Dictionary<string, Guid> _nodesDictionary = new Dictionary<string, Guid>();

private readonly XElement nodes;

private readonly XElement links;

private readonly XNamespace _nameSpace = "http://schemas.microsoft.com/vs/2009/dgml";
#region DGML template
private readonly string _template =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<DirectedGraph xmlns=""http://schemas.microsoft.com/vs/2009/dgml"" Background=""grey"">
<Nodes>
</Nodes>
<Links>
</Links>
<Categories>
<Category Id=""VeryHigh"" Background=""#009933"" />
<Category Id=""High"" Background=""#ffff66"" />
<Category Id=""Medium"" Background=""#ff9900"" />
<Category Id=""MediumLow"" Background=""#ff3300"" />
<Category Id=""Low"" Background=""#990000"" />
<Category Id=""Target"" Background=""white"" />
<Category Id=""Unresolved"" Background=""red"" />
<Category Id=""Comment"" Label=""Comment"" Description=""Represents a user defined comment on the diagram"" NavigationActionLabel=""Comments"" />
</Categories>
<Properties>
<Property Id=""PortabilityIndex"" Label=""Portability Index"" DataType=""System.String"" />
</Properties>
<Styles>
<Style TargetType=""Node"" GroupLabel=""Comment"" ValueLabel=""Has comment"">
<Condition Expression = ""HasCategory('Comment')"" />
<Setter Property=""Background"" Value=""#FFFFFACD"" />
<Setter Property=""Stroke"" Value=""#FFE5C365"" />
<Setter Property=""StrokeThickness"" Value=""1"" />
<Setter Property=""NodeRadius"" Value=""2"" />
<Setter Property=""MaxWidth"" Value=""250"" />
</Style>
</Styles>W
</DirectedGraph>";
#endregion

private XDocument file;

public DGMLManager()
{
file = XDocument.Parse(_template);
XElement root = file.Root;

nodes = root.Element(_nameSpace + "Nodes");
links = root.Element(_nameSpace + "Links");
}

public void SetTitle(string title)
{
file.Root.SetAttributeValue("Title", title);
}

public bool TryGetId(string value, out Guid frameworkGuid)
{
return _nodesDictionary.TryGetValue(value, out frameworkGuid);
}

internal void AddLink(Guid source, Guid target, string category = null)
{
var element = new XElement(_nameSpace + "Link",
new XAttribute("Source", source),
new XAttribute("Target", target));

if (category != null)
{
element.SetAttributeValue("Category", category);
}

links.Add(element);
}

internal void AddNode(Guid commentGuid, string missingTypes, string category)
{
AddNode(commentGuid, missingTypes, category, null, null);
}

internal void AddNode(Guid id, string label, string category, double? portabilityIndex, string group)
{
var element = new XElement(_nameSpace + "Node",
new XAttribute("Id", id),
new XAttribute("Label", label),
new XAttribute("Category", category));

if (portabilityIndex != null)
{
element.SetAttributeValue("PortabilityIndex", portabilityIndex);
}

if (group != null)
{
element.SetAttributeValue("Group", group);
}

nodes.Add(element);
}

internal void Save(Stream stream)
{
using (var ms = new MemoryStream())
{
file.Save(ms);
ms.Position = 0;
ms.CopyTo(stream);
}
}

internal Guid GetOrCreateGuid(string nodeLabel)
{
if (!_nodesDictionary.TryGetValue(nodeLabel, out Guid guid))
{
guid = Guid.NewGuid();
_nodesDictionary.Add(nodeLabel, guid);
}

return guid;
}
}
}
138 changes: 138 additions & 0 deletions src/lib/Microsoft.Fx.Portability.Reports.DGML/DGMLOutputWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Fx.Portability.ObjectModel;
using Microsoft.Fx.Portability.Reporting;
using Microsoft.Fx.Portability.Reporting.ObjectModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text;
using System.Xml.Linq;

namespace Microsoft.Fx.Portability.Reports.DGML
{
public class DGMLOutputWriter : IReportWriter
{
public ResultFormatInformation Format => new ResultFormatInformation()
{
DisplayName = "DGML",
MimeType = "application/xml",
FileExtension = ".dgml"
};

private readonly DGMLManager dgml = new DGMLManager();

public void WriteStream(Stream stream, AnalyzeResponse response)
{
ReferenceGraph rg = ReferenceGraph.CreateGraph(response);

ReportingResult analysisResult = response.ReportingResult;
var targets = analysisResult.Targets;
GenerateTargetContainers(targets);
dgml.SetTitle(response.ApplicationName);

// For each target, let's generate the assemblies
foreach (var node in rg.Nodes.Keys)
{
for (int i = 0; i < targets.Count; i++)
{
double portabilityIndex = 0, portabilityIndexRefs = 0;
string missingTypes = null;
if (node.UsageData != null)
{
TargetUsageInfo usageInfo = node.UsageData[i];
portabilityIndex = node.GetPortabilityIndex(i);
portabilityIndexRefs = node.GetPortabilityIndexForReferences(i);

missingTypes = GenerateMissingTypes(node.Assembly, analysisResult, i);
}

// generate the node
string tfm = targets[i].FullName;
Guid nodeGuid = dgml.GetOrCreateGuid($"{node.Assembly},TFM:{tfm}");
string nodeTitle = $"{node.SimpleName}: {Math.Round(portabilityIndex * 100, 2)}%, References: {Math.Round(portabilityIndexRefs * 100, 2)}%";
string nodeCategory = node.IsMissing ? "Unresolved" : GetCategory(Math.Round(portabilityIndex * portabilityIndexRefs * 100, 2));

dgml.AddNode(nodeGuid, nodeTitle,
nodeCategory,
portabilityIndex,
group: missingTypes.Length == 0 ? null : "Collapsed");

if (dgml.TryGetId(tfm, out Guid frameworkGuid))
{
dgml.AddLink(frameworkGuid, nodeGuid, "Contains");
}

if (!string.IsNullOrEmpty(missingTypes))
{
Guid commentGuid = Guid.NewGuid();
dgml.AddNode(commentGuid, missingTypes, "Comment");
dgml.AddLink(nodeGuid, commentGuid, "Contains");
}
}
}

// generate the references.
foreach (var node in rg.Nodes.Keys)
{
for (int i = 0; i < targets.Count; i++)
{
// generate the node
string tfm = targets[i].FullName;
Guid nodeGuid = dgml.GetOrCreateGuid($"{node.Assembly},TFM:{tfm}");

foreach (var refNode in node.Nodes)
{
Guid refNodeGuid = dgml.GetOrCreateGuid($"{refNode.Assembly},TFM:{tfm}");
dgml.AddLink(nodeGuid, refNodeGuid);
}
}
}

dgml.Save(stream);

return;
}

private static string GenerateMissingTypes(string assembly, ReportingResult response, int i)
{
// for a given assembly identity and a given target usage, display the missing types
IEnumerable<MissingTypeInfo> missingTypesForAssembly = response.GetMissingTypes()
.Where(mt => mt.UsedIn.Any(x => x.AssemblyIdentity == assembly) && mt.IsMissing);

var missingTypesForFramework = missingTypesForAssembly
.Where(mt => mt.TargetStatus.ToList()[i] == "Not supported" || (mt.TargetVersionStatus.ToList()[i] > response.Targets[i].Version))
.Select(x => x.DocId).OrderBy(x => x);

return string.Join("\n", missingTypesForFramework);
}

private void GenerateTargetContainers(IList<FrameworkName> targets)
{
for (int i = 0; i < targets.Count; i++)
{
string targetFramework = targets[i].FullName;
Guid nodeGuid = dgml.GetOrCreateGuid(targetFramework);
dgml.AddNode(nodeGuid, targetFramework, "Target", null, group: "Expanded");
}
}

private static string GetCategory(double probabilityIndex)
{
if (probabilityIndex == 100.0)
return "VeryHigh";
if (probabilityIndex >= 75.0)
return "High";
if (probabilityIndex >= 50.0)
return "Medium";
if (probabilityIndex >= 30.0)
return "MediumLow";

return "Low";
}
}
}
Loading

0 comments on commit a90b32b

Please sign in to comment.