Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade D3 and d3-flameGraph #216

Closed
wants to merge 1 commit into from
Closed

Conversation

Krinkle
Copy link
Contributor

@Krinkle Krinkle commented Nov 14, 2017

This builds on top of #215.

The upgrade is a fairly large commit because D3 and d3-flameGraph have to be updated at the same time, and they bring with them a few breaking changes that required the frontend glue in flamegraph.twig and xhgui-charts.js to be changed a little bit.

background: #fff;
border-radius: 6px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(0, 0, 0, 0.2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fallback border (hex or RGB) should precede RGBA border.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(pre-existing issue, unrelated)

@Krinkle
Copy link
Contributor Author

Krinkle commented Nov 14, 2017

When testing on this branch I notice that these upstream improvements expose a pre-existing bug in XHGui.

First, the upstream improvements:

  1. Chart labels now use their own unique class name (d3-flame-graph-label) and no longer clash with Bootstraps' label class. This removes various unwarranted style conflicts and fixes the legibility issue raised in Legibility issues on flame graph #211.

  2. Previously d3-flameGraph did not use the value of the stack data directly, but instead computed its own node values by recursively adding together all child nodes. This caused issues described at Flamegraph does not match table data and callgraph #212 and Flamegraph assumes nodes are self-time only #207. In short: All values, except the leaf nodes where way off. Often making the higher level nodes (including main()) sometimes report a value 10x or 100x higher than it should be. This has been fixed.

This second point, means the size of nodes in the flame graph is now accurate, and the hover tip shows the correct microsecond values now.

However, this given the width of the rectangles now being computed directly based on the node's own value relative to main, this means the values have to be valid. And it turns out, the data exported by XHGui via /run/flamegraph/data?id=:id is not always valid. At least, in my own testing I found that whenever the same function is called from two subtrees, the value of the last occurrence is wrongly re-used for all earlier references. Causing weird graphs like this:

1. main() - 43,000 µs
1.1. Something\here() - 1,800 µs # does not match 1,300 + 6,500
1.1.1. Something\else() – 1,300 µs
1.1.2. spl_autoload_call – 6,500 µs # was probably 300 µs?
1.2. Other\stuff() – 34,700 µs
1.3. spl_autoload_call – 6,500 µs

In current master this data corruption is not obvious at first because flameGraph computes the width based on their children, which means "Something\here" would (wrongly) be given 1800+1300+6500.

Instead, on this pull requests' branch, the graph can now be rendered in a way that has various rectangles overlapping each other because the children can sometimes be wider than their parent:

Before (master) After (this PR)
screen shot 2017-11-13 at 21 55 09 screen shot 2017-11-13 at 21 55 37

Note in the after image how some of the rectangles now have rectangles transparently behind them.

Krinkle added a commit to Krinkle/xhgui that referenced this pull request Nov 14, 2017
This ensures a test exists for cases where there are multiple
call stacks that have the same function in their leaf position.

This is a known use case that XHGui accounts with its calculateSelf()
method, but it seems the current getFlameGraphData() method uses
this data incorrectly.

This issue is mentioned at perftools#216 (comment)

Before I fix this, I'm first adding a test to show the current broken
behaviour.
Krinkle added a commit to Krinkle/xhgui that referenced this pull request Nov 14, 2017
This ensures a test exists for cases where there are multiple
call stacks that have the same function in their leaf position.

This is a known use case that XHGui accounts with its calculateSelf()
method, but it seems the current getFlameGraphData() method uses
this data incorrectly.

This issue is mentioned at perftools#216 (comment)

Before I fix this, I'm first adding a test to show the current broken
behaviour.
Krinkle added a commit to Krinkle/xhgui that referenced this pull request Nov 14, 2017
This ensures a test exists for cases where there are multiple
call stacks that have the same function in their leaf position.

This is a known use case that XHGui accounts with its calculateSelf()
method, but it seems the current getFlameGraphData() method uses
this data incorrectly.

This issue is mentioned at perftools#216 (comment)

Before I fix this, I'm first adding a test to show the current broken
behaviour.
@@ -72,12 +70,6 @@ d3.json("{{ url('run.flamegraph.data', {id: profile.id|trim }) }}", function(err
return console.warn(error);
}

flameGraph.height(getDepth(data.data) * cellHeight);

flameGraph.sort(function (a, b) {
Copy link
Contributor Author

@Krinkle Krinkle Nov 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: This is actually not redundant. Due to the code accessing data.name I mistakenly thought this is a name sort, but it's actually looking in XHGui's data object, not a D3 d.data object.

This is from fb8fac5 and needs to stay.

@markstory
Copy link
Member

The children being wider than the parents results in some very funny looking graphs locally. I'll see if I can get the calculations behind the flamegraph to add up better. The tests you contributed in #217 will be helpful in verifying any fixes.

### d3.flameGraph

Originally added in May 2016 as:
- d3.flameGraph [v0.4.2]
- d3.tip [v0.6.7] (its dependency)

At that time, d3.flameGraph required v3.x of d3, which XHGui
already bundled.
The latest version of d3.flameGraph requires v4.x of d4.

Update to:
- d3.flameGraph [v1.0.11-dev]
- d3.tip [v0.7.1] (its dependency)

Preserving two patches:
- d3.flameGraph.js: Override offset to 24px for d3.tip().
- d3.flameGraph.css: Omit d3-flame-graph-tip styles, we have our own.

Change inline script to account for upstream changes:
* Remove redundant 'height' override (matches the default: depth * cellHeight).
* Remove redundant 'sort' override (matches the default: by name ASC).
* Set digits=0 for Xhgui.formatNumber() instead of default digits=2,
  given that the digits are always ".00". This matches the way microsecond
  values are rendered elsewhere in XHGui.

## d3.js

Upgrade to 4.x (v4.0.8 exactly, same as d3.flameGraph uses now).

Apply changes required for D3 version 4 per
<https://github.com/d3/d3/blob/v4.6.0/CHANGES.md>

### xhgui-charts.js
* d3.layout.pie > d3.pie
* d3.time.format > d3.timeFormat

### flamegraph.twig
* 'cubic-in-out' > d3.easeCubic
* tip() html callback: 'name' and 'value are now in d.data.
* tip() html callback: d.dx replaced with d.x0 and d.x1
@Krinkle
Copy link
Contributor Author

Krinkle commented Jun 24, 2018

@markstory I see the tests were merged, but I'm unsure as to whether the problem was solved. Do all nodes now have values that are never less than the total of direct children?

Let me know if/when it is resolved, or what I can do to help, would be nice to move this along.

While the master version (last I checked) didn't appear broken, as mentioned in the previous comments, it is also broken due to the nodes being recursively summed. This has two notable side-effects: Proportions are off (small values are shown too small, large values are shown too large), and the total is way too high. However, it does have the benefit of masking the problem we found in this pull request.

@markstory
Copy link
Member

I don't think the root problem was solved. Last I remember there were different display issues with these changes. I haven't had much time to dedicate to this project as of late though.

Krinkle added a commit to Krinkle/xhgui that referenced this pull request Sep 23, 2018
This unfortunately never worked correctly due to a fundamental
limitation with the XHProf data format, which is that it only
records metadata per parent-child method combination, it cannot
be used to build a complete call tree.

The feature was added in pull perftools#177. More information about this
limitation is described in detail at perftools#219, perftools#216 and perftools#212.

A brief summary:

* The visualisation showed callstacks that did not actually exist,
  and was missing callstacks that did exist. (Due to assuming
  that every combination of a parent-child pair is valid, and
  due to it randomly assinging repeated calls to the first
  encountered parent.)

* The visualisation discarded all timing values from XHProf,
  except for the timing of leaf nodes (methods without children),
  which were then added up recursively. The end result was a
  visually well-balanced tree, but with timing values that were
  not related to the actual performance (upto 100x inflated), and
  the proportions were incorrect as well, making some code look
  fast instead of slow, and vice versa.

These are inherent problems that cannot be solved because the
information logically required to make a flamegraph (call stacks)
is not collected by XHProf.

This closes perftools#216, perftools#212, perftools#211, perftools#207.
This fixes perftools#212.
Krinkle added a commit to Krinkle/xhgui that referenced this pull request Sep 23, 2018
This unfortunately never worked correctly due to a fundamental
limitation with the XHProf data format, which is that it only
records metadata per parent-child method combination, it cannot
be used to build a complete call tree.

The feature was added in pull perftools#177. More information about this
limitation is described in detail at perftools#219, perftools#216 and perftools#212.

A brief summary:

* The visualisation showed callstacks that did not actually exist,
  and was missing callstacks that did exist. (Due to assuming
  that every combination of a parent-child pair is valid, and
  due to it randomly assinging repeated calls to the first
  encountered parent.)

* The visualisation discarded all timing values from XHProf,
  except for the timing of leaf nodes (methods without children),
  which were then added up recursively. The end result was a
  visually well-balanced tree, but with timing values that were
  not related to the actual performance (upto 100x inflated), and
  the proportions were incorrect as well, making some code look
  fast instead of slow, and vice versa.

These are inherent problems that cannot be solved because the
information logically required to make a flamegraph (call stacks)
is not collected by XHProf.

This closes perftools#216, perftools#212, perftools#211, perftools#207.
This fixes perftools#212.
@Krinkle
Copy link
Contributor Author

Krinkle commented Sep 23, 2018

Closing in favour of #246.

@Krinkle Krinkle closed this Sep 23, 2018
Krinkle added a commit to Krinkle/xhgui that referenced this pull request Sep 23, 2018
This unfortunately never worked correctly due to a fundamental
limitation with the XHProf data format, which is that it only
records metadata per parent-child method combination, it cannot
be used to build a complete call tree.

The feature was added in pull perftools#177. More information about this
limitation is described in detail at perftools#219, perftools#216 and perftools#212.

A brief summary:

* The visualisation showed callstacks that did not actually exist,
  and was missing callstacks that did exist. (Due to assuming
  that every combination of a parent-child pair is valid, and
  due to it randomly assinging repeated calls to the first
  encountered parent.)

* The visualisation discarded all timing values from XHProf,
  except for the timing of leaf nodes (methods without children),
  which were then added up recursively. The end result was a
  visually well-balanced tree, but with timing values that were
  not related to the actual performance (upto 100x inflated), and
  the proportions were incorrect as well, making some code look
  fast instead of slow, and vice versa.

These are inherent problems that cannot be solved because the
information logically required to make a flamegraph (call stacks)
is not collected by XHProf.

This closes perftools#212, perftools#211, perftools#207.
This fixes perftools#212.
@Krinkle Krinkle deleted the d3-upgrade branch January 2, 2019 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants