Home | History | Annotate | Download | only in graph
      1 // Copyright 2014 Google Inc. All Rights Reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package graph
     16 
     17 import (
     18 	"bytes"
     19 	"flag"
     20 	"fmt"
     21 	"io/ioutil"
     22 	"path/filepath"
     23 	"reflect"
     24 	"strconv"
     25 	"strings"
     26 	"testing"
     27 
     28 	"github.com/google/pprof/internal/proftest"
     29 )
     30 
     31 var updateFlag = flag.Bool("update", false, "Update the golden files")
     32 
     33 func TestComposeWithStandardGraph(t *testing.T) {
     34 	g := baseGraph()
     35 	a, c := baseAttrsAndConfig()
     36 
     37 	var buf bytes.Buffer
     38 	ComposeDot(&buf, g, a, c)
     39 
     40 	compareGraphs(t, buf.Bytes(), "compose1.dot")
     41 }
     42 
     43 func TestComposeWithNodeAttributesAndZeroFlat(t *testing.T) {
     44 	g := baseGraph()
     45 	a, c := baseAttrsAndConfig()
     46 
     47 	// Set NodeAttributes for Node 1.
     48 	a.Nodes[g.Nodes[0]] = &DotNodeAttributes{
     49 		Shape:       "folder",
     50 		Bold:        true,
     51 		Peripheries: 2,
     52 		URL:         "www.google.com",
     53 		Formatter: func(ni *NodeInfo) string {
     54 			return strings.ToUpper(ni.Name)
     55 		},
     56 	}
     57 
     58 	// Set Flat value to zero on Node 2.
     59 	g.Nodes[1].Flat = 0
     60 
     61 	var buf bytes.Buffer
     62 	ComposeDot(&buf, g, a, c)
     63 
     64 	compareGraphs(t, buf.Bytes(), "compose2.dot")
     65 }
     66 
     67 func TestComposeWithTagsAndResidualEdge(t *testing.T) {
     68 	g := baseGraph()
     69 	a, c := baseAttrsAndConfig()
     70 
     71 	// Add tags to Node 1.
     72 	g.Nodes[0].LabelTags["a"] = &Tag{
     73 		Name: "tag1",
     74 		Cum:  10,
     75 		Flat: 10,
     76 	}
     77 	g.Nodes[0].NumericTags[""] = TagMap{
     78 		"b": &Tag{
     79 			Name: "tag2",
     80 			Cum:  20,
     81 			Flat: 20,
     82 			Unit: "ms",
     83 		},
     84 	}
     85 
     86 	// Set edge to be Residual.
     87 	g.Nodes[0].Out[g.Nodes[1]].Residual = true
     88 
     89 	var buf bytes.Buffer
     90 	ComposeDot(&buf, g, a, c)
     91 
     92 	compareGraphs(t, buf.Bytes(), "compose3.dot")
     93 }
     94 
     95 func TestComposeWithNestedTags(t *testing.T) {
     96 	g := baseGraph()
     97 	a, c := baseAttrsAndConfig()
     98 
     99 	// Add tags to Node 1.
    100 	g.Nodes[0].LabelTags["tag1"] = &Tag{
    101 		Name: "tag1",
    102 		Cum:  10,
    103 		Flat: 10,
    104 	}
    105 	g.Nodes[0].NumericTags["tag1"] = TagMap{
    106 		"tag2": &Tag{
    107 			Name: "tag2",
    108 			Cum:  20,
    109 			Flat: 20,
    110 			Unit: "ms",
    111 		},
    112 	}
    113 
    114 	var buf bytes.Buffer
    115 	ComposeDot(&buf, g, a, c)
    116 
    117 	compareGraphs(t, buf.Bytes(), "compose5.dot")
    118 }
    119 
    120 func TestComposeWithEmptyGraph(t *testing.T) {
    121 	g := &Graph{}
    122 	a, c := baseAttrsAndConfig()
    123 
    124 	var buf bytes.Buffer
    125 	ComposeDot(&buf, g, a, c)
    126 
    127 	compareGraphs(t, buf.Bytes(), "compose4.dot")
    128 }
    129 
    130 func TestComposeWithStandardGraphAndURL(t *testing.T) {
    131 	g := baseGraph()
    132 	a, c := baseAttrsAndConfig()
    133 	c.LegendURL = "http://example.com"
    134 
    135 	var buf bytes.Buffer
    136 	ComposeDot(&buf, g, a, c)
    137 
    138 	compareGraphs(t, buf.Bytes(), "compose6.dot")
    139 }
    140 
    141 func baseGraph() *Graph {
    142 	src := &Node{
    143 		Info:        NodeInfo{Name: "src"},
    144 		Flat:        10,
    145 		Cum:         25,
    146 		In:          make(EdgeMap),
    147 		Out:         make(EdgeMap),
    148 		LabelTags:   make(TagMap),
    149 		NumericTags: make(map[string]TagMap),
    150 	}
    151 	dest := &Node{
    152 		Info:        NodeInfo{Name: "dest"},
    153 		Flat:        15,
    154 		Cum:         25,
    155 		In:          make(EdgeMap),
    156 		Out:         make(EdgeMap),
    157 		LabelTags:   make(TagMap),
    158 		NumericTags: make(map[string]TagMap),
    159 	}
    160 	edge := &Edge{
    161 		Src:    src,
    162 		Dest:   dest,
    163 		Weight: 10,
    164 	}
    165 	src.Out[dest] = edge
    166 	src.In[src] = edge
    167 	return &Graph{
    168 		Nodes: Nodes{
    169 			src,
    170 			dest,
    171 		},
    172 	}
    173 }
    174 
    175 func baseAttrsAndConfig() (*DotAttributes, *DotConfig) {
    176 	a := &DotAttributes{
    177 		Nodes: make(map[*Node]*DotNodeAttributes),
    178 	}
    179 	c := &DotConfig{
    180 		Title:  "testtitle",
    181 		Labels: []string{"label1", "label2"},
    182 		Total:  100,
    183 		FormatValue: func(v int64) string {
    184 			return strconv.FormatInt(v, 10)
    185 		},
    186 	}
    187 	return a, c
    188 }
    189 
    190 func compareGraphs(t *testing.T, got []byte, wantFile string) {
    191 	wantFile = filepath.Join("testdata", wantFile)
    192 	want, err := ioutil.ReadFile(wantFile)
    193 	if err != nil {
    194 		t.Fatalf("error reading test file %s: %v", wantFile, err)
    195 	}
    196 
    197 	if string(got) != string(want) {
    198 		d, err := proftest.Diff(got, want)
    199 		if err != nil {
    200 			t.Fatalf("error finding diff: %v", err)
    201 		}
    202 		t.Errorf("Compose incorrectly wrote %s", string(d))
    203 		if *updateFlag {
    204 			err := ioutil.WriteFile(wantFile, got, 0644)
    205 			if err != nil {
    206 				t.Errorf("failed to update the golden file %q: %v", wantFile, err)
    207 			}
    208 		}
    209 	}
    210 }
    211 
    212 func TestNodeletCountCapping(t *testing.T) {
    213 	labelTags := make(TagMap)
    214 	for i := 0; i < 10; i++ {
    215 		name := fmt.Sprintf("tag-%d", i)
    216 		labelTags[name] = &Tag{
    217 			Name: name,
    218 			Flat: 10,
    219 			Cum:  10,
    220 		}
    221 	}
    222 	numTags := make(TagMap)
    223 	for i := 0; i < 10; i++ {
    224 		name := fmt.Sprintf("num-tag-%d", i)
    225 		numTags[name] = &Tag{
    226 			Name:  name,
    227 			Unit:  "mb",
    228 			Value: 16,
    229 			Flat:  10,
    230 			Cum:   10,
    231 		}
    232 	}
    233 	node1 := &Node{
    234 		Info:        NodeInfo{Name: "node1-with-tags"},
    235 		Flat:        10,
    236 		Cum:         10,
    237 		NumericTags: map[string]TagMap{"": numTags},
    238 		LabelTags:   labelTags,
    239 	}
    240 	node2 := &Node{
    241 		Info: NodeInfo{Name: "node2"},
    242 		Flat: 15,
    243 		Cum:  15,
    244 	}
    245 	node3 := &Node{
    246 		Info: NodeInfo{Name: "node3"},
    247 		Flat: 15,
    248 		Cum:  15,
    249 	}
    250 	g := &Graph{
    251 		Nodes: Nodes{
    252 			node1,
    253 			node2,
    254 			node3,
    255 		},
    256 	}
    257 	for n := 1; n <= 3; n++ {
    258 		input := maxNodelets + n
    259 		if got, want := len(g.SelectTopNodes(input, true)), n; got != want {
    260 			t.Errorf("SelectTopNodes(%d): got %d nodes, want %d", input, got, want)
    261 		}
    262 	}
    263 }
    264 
    265 func TestMultilinePrintableName(t *testing.T) {
    266 	ni := &NodeInfo{
    267 		Name:    "test1.test2::test3",
    268 		File:    "src/file.cc",
    269 		Address: 123,
    270 		Lineno:  999,
    271 	}
    272 
    273 	want := fmt.Sprintf(`%016x\ntest1\ntest2\ntest3\nfile.cc:999\n`, 123)
    274 	if got := multilinePrintableName(ni); got != want {
    275 		t.Errorf("multilinePrintableName(%#v) == %q, want %q", ni, got, want)
    276 	}
    277 }
    278 
    279 func TestTagCollapse(t *testing.T) {
    280 
    281 	makeTag := func(name, unit string, value, flat, cum int64) *Tag {
    282 		return &Tag{name, unit, value, flat, 0, cum, 0}
    283 	}
    284 
    285 	tagSource := []*Tag{
    286 		makeTag("12mb", "mb", 12, 100, 100),
    287 		makeTag("1kb", "kb", 1, 1, 1),
    288 		makeTag("1mb", "mb", 1, 1000, 1000),
    289 		makeTag("2048mb", "mb", 2048, 1000, 1000),
    290 		makeTag("1b", "b", 1, 100, 100),
    291 		makeTag("2b", "b", 2, 100, 100),
    292 		makeTag("7b", "b", 7, 100, 100),
    293 	}
    294 
    295 	tagWant := [][]*Tag{
    296 		{
    297 			makeTag("1B..2GB", "", 0, 2401, 2401),
    298 		},
    299 		{
    300 			makeTag("2GB", "", 0, 1000, 1000),
    301 			makeTag("1B..12MB", "", 0, 1401, 1401),
    302 		},
    303 		{
    304 			makeTag("2GB", "", 0, 1000, 1000),
    305 			makeTag("12MB", "", 0, 100, 100),
    306 			makeTag("1B..1MB", "", 0, 1301, 1301),
    307 		},
    308 		{
    309 			makeTag("2GB", "", 0, 1000, 1000),
    310 			makeTag("1MB", "", 0, 1000, 1000),
    311 			makeTag("2B..1kB", "", 0, 201, 201),
    312 			makeTag("1B", "", 0, 100, 100),
    313 			makeTag("12MB", "", 0, 100, 100),
    314 		},
    315 	}
    316 
    317 	for _, tc := range tagWant {
    318 		var got, want []*Tag
    319 		b := builder{nil, &DotAttributes{}, &DotConfig{}}
    320 		got = b.collapsedTags(tagSource, len(tc), true)
    321 		want = SortTags(tc, true)
    322 
    323 		if !reflect.DeepEqual(got, want) {
    324 			t.Errorf("collapse to %d, got:\n%v\nwant:\n%v", len(tc), tagString(got), tagString(want))
    325 		}
    326 	}
    327 }
    328 
    329 func tagString(t []*Tag) string {
    330 	var ret []string
    331 	for _, s := range t {
    332 		ret = append(ret, fmt.Sprintln(s))
    333 	}
    334 	return strings.Join(ret, ":")
    335 }
    336