This repository has been archived by the owner on Jul 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 182
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Produce a DGML graph from the ApiPort data (#695)
* 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
1 parent
ac782cc
commit a90b32b
Showing
13 changed files
with
585 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
src/lib/Microsoft.Fx.Portability.Reports.DGML/DGMLManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
138
src/lib/Microsoft.Fx.Portability.Reports.DGML/DGMLOutputWriter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} | ||
} |
Oops, something went wrong.