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

Flame Graph support on Web UI #188

Merged
merged 45 commits into from
Nov 21, 2017
Merged

Conversation

spiermar
Copy link
Contributor

Implements #166

Details:

  • Basic flame graph implementation under http output.
  • Tested with both CPU and Memory profiles.
  • Endpoint is /flamegraph, and there's a link in /,
  • Opted not to use the directed graph to generate the flame graph since it doesn't have the detailed information required. Parsed the raw samples instead.
  • Not doing any filtering in the samples.
  • Output is mostly self-contained and could be saved. It should also be easy to add an option to export the html file.
  • Making available all sample types in the UI, via a drop-down. e.g.: CPU profile will have samples and cpu.
  • Displaying some profile metadata like profile time and duration.
  • Converting nanoseconds to seconds. I'm not sure if this is ideal, but same thing is being done in the main view. Happy to revert it if necessary.
  • Using a bootstrap layout now, but happy to revert it if you believe it would be better to keep a simple layout without the bootstrap dependency.
  • Implements all features mentioned by @brendangregg, except different palettes, which is under development and requires changes in the D3 plugin.
  • We are continuously adding more features to the D3 plugin, and those should be easily assimilated by this.

A demo can be found in pprof_flame.html

Happy to make any changes deemed necessary.

@codecov-io
Copy link

codecov-io commented Aug 10, 2017

Codecov Report

Merging #188 into master will increase coverage by 0.55%.
The diff coverage is 95.68%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #188      +/-   ##
==========================================
+ Coverage   65.29%   65.84%   +0.55%     
==========================================
  Files          34       35       +1     
  Lines        7212     7346     +134     
==========================================
+ Hits         4709     4837     +128     
- Misses       2109     2113       +4     
- Partials      394      396       +2
Impacted Files Coverage Δ
internal/driver/webui.go 58.44% <100%> (+0.19%) ⬆️
internal/driver/webhtml.go 100% <100%> (ø) ⬆️
internal/driver/flamegraph.go 85% <85%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 71d5bad...7b1f77e. Read the comment docs.

@aalexand
Copy link
Collaborator

Given that pprof is vendored into Golang sources and given that it's used internally at Google, I am skeptical about us being able to accept the source which <script>'s a number of locations on the Internet. @rsc @rauls5382 @rakyll

@spiermar
Copy link
Contributor Author

@aalexand If that's a concern, it should be possible to source those files from pprof's http server.

@rauls5382
Copy link
Contributor

Thank you. This looks pretty cool.

I agree we should freeze the version of all scripts/plugins and serve them from the pprof http server. That way pprof won't be broken if there are incompatible changes in the future.

The Go distribution would have to vendor any dependent packages as well, similar to what they already do to pick up the ianlancetaylor/demangle package. If they decide against it (which I doubt), we could find a way to make it easy to trim that functionality via plugins.

@spiermar
Copy link
Contributor Author

Thanks @rauls5382

Versioning shouldn't theoretically be a problem, since all external resource references are versioned CDN urls. Security-wise, the external resource could be compromised, so I've added integrity check to all resources to be on the safe side.

If vendor the dependent packages is a better alternative, there are a few different ways of getting this done, and I'm not sure what would be best option, from a pprof perspective. Let me know and I can make the changes.

@rakyll
Copy link
Member

rakyll commented Aug 10, 2017

@spiermar Can you compile all the external scripts in a single one and embed the final artifact in a Go file? Having said that, I am not sure how licensing will work. @aalexand, @rauls5382, do we need a separate in the repo with a licensing file to be able to vendor the external dependencies?

@spiermar
Copy link
Contributor Author

spiermar commented Aug 10, 2017

@rakyll already have the scripts to create combined and minified vendor.js and vendor.css files. Licensing is the part needs checking. The flame graph plugin is Apache 2.0, so not a problem. d3 is BSD-3 and d3-tip is MIT. Already removed the lodash dependency in the last commit, so not a problem anymore. Bootstrap could be removed.

@aalexand
Copy link
Collaborator

@rakyll Yes, I would probably expect something like third_party/d3 and third_party/flame-something-something for the dependencies along with the LICENSE files in there. It would be good to pre-check with the Go team maybe. What concerns me the most is having pprof which is a local tool fetch any resources from elsewhere on the web. Security aside, pprof and "go tool pprof" should be functional without requiring internet access.

@spiermar
Copy link
Contributor Author

I'll make the changes later today

@aalexand
Copy link
Collaborator

https://github.com/google/pprof/blob/master/third_party/svg/svgpan.go is along the lines of what we want.

@spiermar
Copy link
Contributor Author

JavaScript resources are inline now and I've also removed the bootstrap dependency. License files are also checked in.
Considered using the minified versions of the scripts, but decided to follow the same pattern from the svgPan third party library.
Same for inline vs. independent resource endpoints. Inlining is not ideal for multiple reasons, but followed the same pattern as svgPan. On the plus side, the HTML can be saved this way.
View could benefit from a few UI/UX improvements, but it's working.

@aalexand
Copy link
Collaborator

Thanks. Some comments:

  • The code coverage went down. Can you add tests to cover the new code?
  • I tried opening the flame graph with a 5 MB profile.pb.gz file (which I can't immediately share), the graph view choked on that. Do you want to test with some large profiles to see whether there are some optimizations to do?
  • I would expect a bit more unification between the graph view and the flame graph view. More specifically:
    • The metric selection (e.g. samples vs. cpu) should be present on both pages.
    • The filtering like focus / ignore / hide / show should be present on both pages.
    • There should be a way back from the flame view back to the graph.

@spiermar
Copy link
Contributor Author

  • Yes, had it on my list to add a few tests.
  • The sluggishness on large flame graphs comes from the large number of rect that need to be drawn. Generally because of tons of small value elements that can hardly be seen without zooming. I'm looking into hiding small frames (minimum size for flame elements spiermar/d3-flame-graph#51), but that's not done yet. A temporary fix might be filtering before creating the data structure, like the directed graph, but that reduces accuracy. I can add filtering, but would also like to give the user an option to disable it.
  • Agree with the metric selection on both views. I just need to understand how to get that out of the report to add a selection in the other view.
  • The focus/ignore/hide/show filtering is a bit more tricky since they are not implemented in the plugin and generally not how users interact with flame graphs. Zooming and searching are the basic interactions. @brendangregg, any thoughts?
  • I will add a link back to the graph.

@spiermar
Copy link
Contributor Author

@aalexand Finally got a bit of time to get back to this. The call_tree option is done, and also the percentage of total. That's all I had on my list. Let me know how that looks.

Copy link
Collaborator

@aalexand aalexand left a comment

Choose a reason for hiding this comment

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

Thanks!

Is the current code rebased to the latest master?

package d3tip

// D3TIP returns the d3-tip.js file
const D3TIP = `
Copy link
Collaborator

Choose a reason for hiding this comment

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

D3TIP is not in style compliant with Go style. Should be thisStyle (for package private) or ThisStyle (for public). For this variable, how about something like JSSource or Source?

This comment applies to d3.go and d3_flame_graph.go as well.


// D3TIP returns the d3-tip.js file
const D3TIP = `
{{define "d3tipscript"}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

For consistency with svgpan.go can we move the template definition part out of this file and only have the JS source constant defined here?

This comment applies to d3.go and d3_flame_graph.go as well.


// percentage computes the percentage of total of a value, and encodes
// it as a string. At least two digits of precision are printed.
func percentage(value, total int64) string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I I think we should do #265 and use measurement.Percentage in the code below so that this function can be dropped and so that the percentage formatting is enforced to be consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I wait until it gets merged?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I merged that, you can rebase.

// Calculate root value
rootValue := int64(0)
for _, n := range nodes[0:nroots] {
rootValue = rootValue + n.Cum
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: this can be done near nroots++ above, no need to have a separate loop.

@spiermar
Copy link
Contributor Author

@aalexand Yes, it was rebased.

Copy link
Collaborator

@aalexand aalexand left a comment

Choose a reason for hiding this comment

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

I also wonder if the size of the dependencies can be further reduced. I saw d3 is composed of microlibraries. Do we need all of d3 here or we can limit the dependency to a couple of those?

@spiermar
Copy link
Contributor Author

I could create a custom D3 build. Let me check how much that would reduce the bundle size.

@aalexand
Copy link
Collaborator

@spiermar Great, thanks a lot, that's much smaller! Can you add a README.md in the d3 directory which documents how the steps you did can be repeated to upgrade from upstream when we need to?

// flamegraph generates a web page containing a flamegraph.
func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
// Force the call tree so that the graph is a tree.
rpt, errList := ui.makeReport(w, req, []string{"svg"}, "call_tree", "true")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please update this to

        // Force the call tree so that the graph is a tree. Also do not trim the tree
        // so that the flame graph contains all functions.
        rpt, errList := ui.makeReport(w, req, []string{"svg"}, "call_tree", "true", "trim", "false")

The GetDOT call trims the tree by default, and for the flame graph I think it's reasonable to override that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Up to you. I prefer to have the flame graph contain everything, but that's not the default behavior for the directed graph, so I wasn't really sure what would the users prefer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The graph does it by default because displaying the full graph for large programs becomes messy. Flame graph appears to be compact and readable enough on some larger profiles I tried so I think trimming the tree would be more confusing that useful here.


// Source returns the d3-tip.js file
const Source = `
{{define "d3tipscript"}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

How difficult it is to push out the template directive out of these files? This is a nit, I am just trying to make all third party files consistent here - like svgpan they should only contain the JS source as is, without the additional directives so that they are plain simple and once updated to a newer version can be easily upgraded without accidentally losing any content.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be simple, just move the directives. It's being called only in one place. Will check it later tonight.

@aalexand
Copy link
Collaborator

@spiermar Overall I think it looks good now. One thing: would you be open (and would it be difficult) to switch the flame graph to grow down rather than up? We found recently that with deep flame graphs it looks more natural in the UI when they grow down and you have to scroll to get the finer level of details (deeper elements) rather than having to scroll just to see the root. See the attached example.
6p3qt0nedam

@spiermar
Copy link
Contributor Author

@aalexand it's in the roadmap for the d3 flame graph plugin (spiermar/d3-flame-graph#73), but I don't have a target date for the release yet.

@brendangregg
Copy link

My original flamegraph.pl did this layout too (icicle), but here's how I interpret each flame graph:

  1. sweep top down and look for what's on-CPU, especially any large plateaus, to identify what the CPU is doing.
  2. then sweep bottom-up to see how we got there, which is generally quicker after knowing (1).

Last week I had large plateau of UTF-8 processing (51%), then swept bottom up to look for UTF-8 or string-related functions, skipping the rest (a lot of framework frames). This meant I could find the relevant UTF-8/string frames more quickly, after knowing the on-CPU context first.

Another recent one was gzip. Another was kernel network interrupt processing -- reading that bottom up would be a waste of time, since what's ultimately burning CPU is unrelated to the user-level program that's running.

Reading bottom-up (root first) still gets the job done. My point is that knowing some on-CPU context first can make that quicker, as you have a clue as to what you're looking for.

I've used the icicle layout when reversing the stack order as well, so that the top frame remains what is on-CPU, and beneath it is ancestry.

That's one of the big reasons we wanted to rewrite this in d3, so that flipping the layout direction and merge direction could be button presses in an interactive d3 graph. Doing it with my Perl program meant rerunning the Perl program.

@aalexand
Copy link
Collaborator

@spiermar Note a couple of other open comments I made.

@spiermar
Copy link
Contributor Author

@aalexand regarding the D3 build, sure. I used rollup to build the distribution and the configuration is on my Github page (https://github.com/spiermar/d3-pprof). With that it's just a single command to generate the build. If it's Ok to link to the repo, I'll add the instructions to a README.md file on pprof and also update the repo with the instructions.

@aalexand
Copy link
Collaborator

@spiermar I'd prefer to include self-contained instructions on how to do the build inside of pprof than point to another repo. This is to keep the deps minimal, even if just for the build process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants