diff --git a/src/cmd/pprof/internal/commands/commands.go b/src/cmd/pprof/internal/commands/commands.go index 51397a3c60d..167e57f1f4b 100644 --- a/src/cmd/pprof/internal/commands/commands.go +++ b/src/cmd/pprof/internal/commands/commands.go @@ -42,7 +42,7 @@ type Completer func(prefix string) string type PostProcessor func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error // PProf returns the basic pprof report-generation commands -func PProf(c Completer, interactive **bool, svgpan **string) Commands { +func PProf(c Completer, interactive **bool) Commands { return Commands{ // Commands that require no post-processing. "tags": {nil, report.Tags, nil, false, "Outputs all tags in the profile"}, @@ -66,13 +66,13 @@ func PProf(c Completer, interactive **bool, svgpan **string) Commands { "ps": {c, report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format"}, // Save SVG output into a file after including svgpan library - "svg": {c, report.Dot, saveSVGToFile(svgpan), false, "Outputs a graph in SVG format"}, + "svg": {c, report.Dot, saveSVGToFile(), false, "Outputs a graph in SVG format"}, // Visualize postprocessed dot output "eog": {c, report.Dot, invokeVisualizer(interactive, invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog"}, "evince": {c, report.Dot, invokeVisualizer(interactive, invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince"}, "gv": {c, report.Dot, invokeVisualizer(interactive, invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv"}, - "web": {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(svgpan), "svg", browsers()), false, "Visualize graph through web browser"}, + "web": {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(), "svg", browsers()), false, "Visualize graph through web browser"}, // Visualize HTML directly generated by report. "weblist": {c, report.WebList, invokeVisualizer(interactive, awayFromTTY("html"), "html", browsers()), true, "Output annotated source in HTML for functions matching regexp or address"}, @@ -169,14 +169,14 @@ func invokeDot(format string) PostProcessor { } } -func saveSVGToFile(svgpan **string) PostProcessor { +func saveSVGToFile() PostProcessor { generateSVG := invokeDot("svg") divert := awayFromTTY("svg") return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error { baseSVG := &bytes.Buffer{} generateSVG(input, baseSVG, ui) massaged := &bytes.Buffer{} - fmt.Fprint(massaged, svg.Massage(*baseSVG, **svgpan)) + fmt.Fprint(massaged, svg.Massage(*baseSVG)) return divert(massaged, output, ui) } } diff --git a/src/cmd/pprof/internal/driver/driver.go b/src/cmd/pprof/internal/driver/driver.go index acb29d13aa3..7f345a3bc27 100644 --- a/src/cmd/pprof/internal/driver/driver.go +++ b/src/cmd/pprof/internal/driver/driver.go @@ -429,7 +429,6 @@ type flags struct { flagCommands map[string]*bool // pprof commands without parameters flagParamCommands map[string]*string // pprof commands with parameters - flagSVGPan *string // URL to fetch the SVG Pan library flagOutput *string // Output file name flagCum *bool // Sort by cumulative data @@ -625,7 +624,6 @@ func getFlags(flag plugin.FlagSet, overrides commands.Commands, ui plugin.UI) (* flagBase: flag.String("base", "", "Source for base profile for comparison"), flagDropNegative: flag.Bool("drop_negative", false, "Ignore negative differences"), - flagSVGPan: flag.String("svgpan", "https://www.cyberz.org/projects/SVGPan/SVGPan.js", "URL for SVGPan Library"), // Data sorting criteria. flagCum: flag.Bool("cum", false, "Sort by cumulative data"), // Graph handling options. @@ -670,8 +668,7 @@ func getFlags(flag plugin.FlagSet, overrides commands.Commands, ui plugin.UI) (* // Flags used during command processing interactive := &f.flagInteractive - svgpan := &f.flagSVGPan - f.commands = commands.PProf(functionCompleter, interactive, svgpan) + f.commands = commands.PProf(functionCompleter, interactive) // Override commands for name, cmd := range overrides { diff --git a/src/cmd/pprof/internal/svg/svg.go b/src/cmd/pprof/internal/svg/svg.go index aa65a1a08de..fbde103ef4f 100644 --- a/src/cmd/pprof/internal/svg/svg.go +++ b/src/cmd/pprof/internal/svg/svg.go @@ -19,16 +19,13 @@ var ( // Massage enhances the SVG output from DOT to provide bettern // panning inside a web browser. It uses the SVGPan library, which is -// accessed through the svgPan URL. -func Massage(in bytes.Buffer, svgPan string) string { +// included directly. +func Massage(in bytes.Buffer) string { svg := string(in.Bytes()) // Work around for dot bug which misses quoting some ampersands, // resulting on unparsable SVG. svg = strings.Replace(svg, "&;", "&;", -1) - if svgPan == "" { - return svg - } //Dot's SVG output is // @@ -43,8 +40,7 @@ func Massage(in bytes.Buffer, svgPan string) string { // // - // - + // // // // ... @@ -60,7 +56,7 @@ func Massage(in bytes.Buffer, svgPan string) string { if loc := graphId.FindStringIndex(svg); loc != nil { svg = svg[:loc[0]] + - `` + + `` + `` + svg[loc[0]:] } diff --git a/src/cmd/pprof/internal/svg/svgpan.go b/src/cmd/pprof/internal/svg/svgpan.go new file mode 100644 index 00000000000..4975b103e31 --- /dev/null +++ b/src/cmd/pprof/internal/svg/svgpan.go @@ -0,0 +1,291 @@ +// SVG pan and zoom library. +// See copyright notice in string constant below. + +package svg + +// https://www.cyberz.org/projects/SVGPan/SVGPan.js + +const svgPanJS = ` +/** + * SVGPan library 1.2.1 + * ====================== + * + * Given an unique existing element with id "viewport" (or when missing, the first g + * element), including the the library into any SVG adds the following capabilities: + * + * - Mouse panning + * - Mouse zooming (using the wheel) + * - Object dragging + * + * You can configure the behaviour of the pan/zoom/drag with the variables + * listed in the CONFIGURATION section of this file. + * + * Known issues: + * + * - Zooming (while panning) on Safari has still some issues + * + * Releases: + * + * 1.2.1, Mon Jul 4 00:33:18 CEST 2011, Andrea Leofreddi + * - Fixed a regression with mouse wheel (now working on Firefox 5) + * - Working with viewBox attribute (#4) + * - Added "use strict;" and fixed resulting warnings (#5) + * - Added configuration variables, dragging is disabled by default (#3) + * + * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui + * Fixed a bug with browser mouse handler interaction + * + * 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui + * Updated the zoom code to support the mouse wheel on Safari/Chrome + * + * 1.0, Andrea Leofreddi + * First release + * + * This code is licensed under the following BSD license: + * + * Copyright 2009-2010 Andrea Leofreddi . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ` + "``AS IS''" + ` AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Andrea Leofreddi. + */ + +"use strict"; + +/// CONFIGURATION +/// ====> + +var enablePan = 1; // 1 or 0: enable or disable panning (default enabled) +var enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled) +var enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled) + +/// <==== +/// END OF CONFIGURATION + +var root = document.documentElement; + +var state = 'none', svgRoot, stateTarget, stateOrigin, stateTf; + +setupHandlers(root); + +/** + * Register handlers + */ +function setupHandlers(root){ + setAttributes(root, { + "onmouseup" : "handleMouseUp(evt)", + "onmousedown" : "handleMouseDown(evt)", + "onmousemove" : "handleMouseMove(evt)", + //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element + }); + + if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0) + window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari + else + window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others +} + +/** + * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable. + */ +function getRoot(root) { + if(typeof(svgRoot) == "undefined") { + var g = null; + + g = root.getElementById("viewport"); + + if(g == null) + g = root.getElementsByTagName('g')[0]; + + if(g == null) + alert('Unable to obtain SVG root element'); + + setCTM(g, g.getCTM()); + + g.removeAttribute("viewBox"); + + svgRoot = g; + } + + return svgRoot; +} + +/** + * Instance an SVGPoint object with given event coordinates. + */ +function getEventPoint(evt) { + var p = root.createSVGPoint(); + + p.x = evt.clientX; + p.y = evt.clientY; + + return p; +} + +/** + * Sets the current transform matrix of an element. + */ +function setCTM(element, matrix) { + var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")"; + + element.setAttribute("transform", s); +} + +/** + * Dumps a matrix to a string (useful for debug). + */ +function dumpMatrix(matrix) { + var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]"; + + return s; +} + +/** + * Sets attributes of an element. + */ +function setAttributes(element, attributes){ + for (var i in attributes) + element.setAttributeNS(null, i, attributes[i]); +} + +/** + * Handle mouse wheel event. + */ +function handleMouseWheel(evt) { + if(!enableZoom) + return; + + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + var delta; + + if(evt.wheelDelta) + delta = evt.wheelDelta / 3600; // Chrome/Safari + else + delta = evt.detail / -90; // Mozilla + + var z = 1 + delta; // Zoom factor: 0.9/1.1 + + var g = getRoot(svgDoc); + + var p = getEventPoint(evt); + + p = p.matrixTransform(g.getCTM().inverse()); + + // Compute new scale matrix in current mouse position + var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y); + + setCTM(g, g.getCTM().multiply(k)); + + if(typeof(stateTf) == "undefined") + stateTf = g.getCTM().inverse(); + + stateTf = stateTf.multiply(k.inverse()); +} + +/** + * Handle mouse move event. + */ +function handleMouseMove(evt) { + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + var g = getRoot(svgDoc); + + if(state == 'pan' && enablePan) { + // Pan mode + var p = getEventPoint(evt).matrixTransform(stateTf); + + setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y)); + } else if(state == 'drag' && enableDrag) { + // Drag mode + var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse()); + + setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM())); + + stateOrigin = p; + } +} + +/** + * Handle click event. + */ +function handleMouseDown(evt) { + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + var g = getRoot(svgDoc); + + if( + evt.target.tagName == "svg" + || !enableDrag // Pan anyway when drag is disabled and the user clicked on an element + ) { + // Pan mode + state = 'pan'; + + stateTf = g.getCTM().inverse(); + + stateOrigin = getEventPoint(evt).matrixTransform(stateTf); + } else { + // Drag mode + state = 'drag'; + + stateTarget = evt.target; + + stateTf = g.getCTM().inverse(); + + stateOrigin = getEventPoint(evt).matrixTransform(stateTf); + } +} + +/** + * Handle mouse button release event. + */ +function handleMouseUp(evt) { + if(evt.preventDefault) + evt.preventDefault(); + + evt.returnValue = false; + + var svgDoc = evt.target.ownerDocument; + + if(state == 'pan' || state == 'drag') { + // Quit pan mode + state = ''; + } +} + +`