/// <summary> /// Build a flame graph to a svg file. /// </summary> /// <param name="root">Root node for a flame graph tree.</param> /// <param name="path">Target svg file path.</param> /// <returns>True when build succeeded. Otherwise, false.</returns> public bool BuildTo(IFlameGraphNode root, string path) { if (path is null) { throw new System.ArgumentNullException(nameof(path)); } using (Stream sourceStream = Build(root)) { if (sourceStream == null) { return(false); } string dirName = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dirName)) { Directory.CreateDirectory(dirName); } using (FileStream outputStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write)) { sourceStream.Position = 0; sourceStream.CopyTo(outputStream); } return(true); } }
/// <summary> /// Build a flame graph and return the svg as a stream. /// </summary> /// <param name="root">Root node for a flame graph tree.</param> /// <returns>A memory stream that contains the svg content.</returns> public MemoryStream Build(IFlameGraphNode root) { if (root == null) { return(null); } if (_options.AutoHeight) { var baselineWidth = _options.Width - GraphMargin * 2; int actualDepth = _depthCounter.GetDepth(root, (node) => node.Metric / root.Metric * baselineWidth > 1); _maxDepth = actualDepth; Height = (_maxDepth + 1) * _options.RowHeight + _options.HeaderHeight; } SvgDocument svgDoc; svgDoc = BuildSvgDocument(); var group = svgDoc.Children.OfType <SvgGroup>().FirstOrDefault(); AppendTitle(group); Build(group, root, width: Width - GraphMargin * 2, left: GraphMargin, depth: 0, parent: null); MemoryStream outputStream = new MemoryStream(); svgDoc.Write(outputStream); outputStream.Position = 0; return(outputStream); }
public int GetDepth(IFlameGraphNode baseline, Predicate <IFlameGraphNode> filter = null) { if (baseline == null) { return(0); } // Default filter that always true. filter = filter ?? ((node) => true); IEnumerable <IFlameGraphNode> workingItems = new List <IFlameGraphNode> { baseline }; int level = 0; while (workingItems.Count() > 0) { level++; workingItems = workingItems.SelectMany(item => item.Children.NullAsEmpty()).Where((node) => filter(node)); } return(level); }
private Color GetDefaultFrameBackground(IFlameGraphNode node) { return(Color.DarkOrange); }
private void Build( SvgGroup workingGroup, IFlameGraphNode node, SvgUnit width, SvgUnit left, int depth, IFlameGraphNode parent) { // Reach the max depth if (depth > _maxDepth) { return; } // Adjust for space between columns. SvgUnit adjustedWidth = width - 1; if (adjustedWidth < 0) { return; } // Adjust height for space between rows SvgUnit top = Height - (depth + 1) * RowHeight; SvgUnit adjustedHeight = RowHeight - 1; Debug.Assert(adjustedHeight > 0); var unitGroup = new SvgGroup(); workingGroup.Children.Add(unitGroup); unitGroup.CustomAttributes.Add("class", "unit_g"); // Add tooltip for full content var title = new SvgTitle() { Content = node.Content }; // onclick="zoom(this)" unitGroup.CustomAttributes.Add("onclick", @"zoom(this)"); // onmouseover="s('sh_xfree (4 samples, 0.13%)')" unitGroup.CustomAttributes.Add("onmouseover", $@"s('{node.Content} ({node.Metric.ToString("0.00")} ms)')"); // onmouseout="c()" unitGroup.CustomAttributes.Add("onmouseout", @"c()"); unitGroup.Children.Add(title); // Create the rectangle: var rect = new SvgRectangle { Fill = new SvgColourServer(_getFrameBackground(node)), X = left, Y = top, CornerRadiusX = 1, CornerRadiusY = 1, Width = adjustedWidth, Height = adjustedHeight, }; unitGroup.Children.Add(rect); var text = new SvgText(GetFitText(node.Content, adjustedWidth)) { Y = { top + RowHeight - 5 }, X = { left + TextMargin }, FontSize = 12, FontWeight = SvgFontWeight.W500, Fill = new SvgColourServer(Color.Black), }; unitGroup.Children.Add(text); // Process children elements int?childrenCount = node.Children?.Count(); if (childrenCount.HasValue) { double metricsSum = node.Children.Sum(item => item.Metric); if (metricsSum == 0) { return; } double metricsRatio = metricsSum / node.Metric; Debug.Assert(metricsRatio <= 1); SvgUnit childrenWidth = (float)(width * metricsRatio); SvgUnit childLeft = left + (float)(width - childrenWidth) / 2; // Center the child foreach (var child in node.Children) { SvgUnit ratioWidth = (float)(child.Metric / metricsSum * childrenWidth); Build(workingGroup, child, ratioWidth, childLeft, depth + 1, node); childLeft += ratioWidth; } } }