blob: b4afddf9333f75eb24d52a27824b263a6c62e355 [file] [log] [blame]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package flamegraph
import (
"bytes"
"fmt"
"html/template"
"os"
"path/filepath"
"time"
"github.com/apache/skywalking-cli/internal/logger"
api "skywalking.apache.org/repo/goapi/query"
"github.com/urfave/cli/v2"
)
func DisplayByTrace(ctx *cli.Context, analysis api.ProfileAnalyzation) error {
trees := make([]*ProfilingDataTree, 0)
for _, tree := range analysis.Trees {
elements := make([]ProfilingDataStackElement, 0)
for _, e := range tree.Elements {
elements = append(elements, &traceStackElementAdapter{e})
}
trees = append(trees, &ProfilingDataTree{Elements: elements})
}
return display(ctx, trees)
}
func DisplayByEBPF(ctx *cli.Context, analysis *api.EBPFProfilingAnalyzation) error {
trees := make([]*ProfilingDataTree, 0)
for _, tree := range analysis.Trees {
elements := make([]ProfilingDataStackElement, 0)
for _, e := range tree.Elements {
elements = append(elements, &eBPFStackElementAdapter{e})
}
trees = append(trees, &ProfilingDataTree{Elements: elements})
}
return display(ctx, trees)
}
func display(_ *cli.Context, trees []*ProfilingDataTree) error {
if len(trees) == 0 {
return fmt.Errorf("could not find the analysis data")
}
// generate flame graph file path for write
flameGraphPath, err := generateFlameGraphFile()
if err != nil {
return err
}
// build data
data := make(map[string]interface{})
elements, maxDepth := buildFlameGraphElements(trees)
data["elements"] = elements
data["maxDepth"] = maxDepth + 1
data["canvasHeight"] = maxDepth*16 + 30
// render template
return renderFlameGraphAndWrite(flameGraphPath, data)
}
func renderFlameGraphAndWrite(path string, data map[string]interface{}) error {
// render template
var b bytes.Buffer
tmpl, err := template.New("flameGraphTemplate").Parse(flameGraphHTML)
if err != nil {
return fmt.Errorf("failed to parse flame graph template: %v", err)
}
if err = tmpl.Execute(&b, data); err != nil {
return fmt.Errorf("failed to render flame graph: %v", err)
}
// write to file
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("could not create the flame graph file, %v", err)
}
_, err = file.Write(b.Bytes())
if err != nil {
return fmt.Errorf("could not write the flame graph to file, %v", err)
}
logger.Log.Infof("success write the flame graph to: %s", path)
return nil
}
func generateFlameGraphFile() (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("could not get current directory: %v", err)
}
flameGraphPath := filepath.Join(wd, buildFileName())
return flameGraphPath, nil
}
func buildFileName() string {
return fmt.Sprintf("flame_graph_%d.html", time.Now().Unix())
}
func buildFlameGraphElements(trees []*ProfilingDataTree) (result []*StackElement, maxDepth int64) {
// adding the root element
root := &StackElement{Symbol: "all"}
result = append(result, root)
// build elements
var left int64
for _, t := range trees {
result, left = buildFlameGraphChildElements(result, t.Elements, nil, left)
}
// calculate the root total count
for _, r := range result {
if r.ParentID == "0" {
root.Count += r.Count
}
if maxDepth < r.Depth {
maxDepth = r.Depth
}
}
return result, maxDepth
}
func buildFlameGraphChildElements(renderElements []*StackElement, dataElements []ProfilingDataStackElement,
parent *StackElement, relativeLeft int64) (result []*StackElement, left int64) {
parentID := "0"
var depth int64 = 1
left = relativeLeft
if parent != nil {
parentID = parent.ID
depth = parent.Depth + 1
left += parent.Left
}
es := findProfilingElementByParentID(dataElements, parentID)
for _, element := range es {
current := GenerateStackElementByProfilingData(element, depth, left)
renderElements = append(renderElements, current)
left += element.DumpCount()
renderElements, _ = buildFlameGraphChildElements(renderElements, dataElements, current, relativeLeft)
}
return renderElements, left
}
func findProfilingElementByParentID(elements []ProfilingDataStackElement, parentID string) []ProfilingDataStackElement {
children := make([]ProfilingDataStackElement, 0)
for _, e := range elements {
if e.ParentID() == parentID {
children = append(children, e)
}
}
return children
}