| // Copyright 2014 Google Inc. All Rights Reserved. |
| // |
| // Licensed 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 graph |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "path/filepath" |
| "reflect" |
| "strconv" |
| "strings" |
| "testing" |
| |
| "github.com/google/pprof/internal/proftest" |
| ) |
| |
| var updateFlag = flag.Bool("update", false, "Update the golden files") |
| |
| func TestComposeWithStandardGraph(t *testing.T) { |
| g := baseGraph() |
| a, c := baseAttrsAndConfig() |
| |
| var buf bytes.Buffer |
| ComposeDot(&buf, g, a, c) |
| |
| compareGraphs(t, buf.Bytes(), "compose1.dot") |
| } |
| |
| func TestComposeWithNodeAttributesAndZeroFlat(t *testing.T) { |
| g := baseGraph() |
| a, c := baseAttrsAndConfig() |
| |
| // Set NodeAttributes for Node 1. |
| a.Nodes[g.Nodes[0]] = &DotNodeAttributes{ |
| Shape: "folder", |
| Bold: true, |
| Peripheries: 2, |
| URL: "www.google.com", |
| Formatter: func(ni *NodeInfo) string { |
| return strings.ToUpper(ni.Name) |
| }, |
| } |
| |
| // Set Flat value to zero on Node 2. |
| g.Nodes[1].Flat = 0 |
| |
| var buf bytes.Buffer |
| ComposeDot(&buf, g, a, c) |
| |
| compareGraphs(t, buf.Bytes(), "compose2.dot") |
| } |
| |
| func TestComposeWithTagsAndResidualEdge(t *testing.T) { |
| g := baseGraph() |
| a, c := baseAttrsAndConfig() |
| |
| // Add tags to Node 1. |
| g.Nodes[0].LabelTags["a"] = &Tag{ |
| Name: "tag1", |
| Cum: 10, |
| Flat: 10, |
| } |
| g.Nodes[0].NumericTags[""] = TagMap{ |
| "b": &Tag{ |
| Name: "tag2", |
| Cum: 20, |
| Flat: 20, |
| Unit: "ms", |
| }, |
| } |
| |
| // Set edge to be Residual. |
| g.Nodes[0].Out[g.Nodes[1]].Residual = true |
| |
| var buf bytes.Buffer |
| ComposeDot(&buf, g, a, c) |
| |
| compareGraphs(t, buf.Bytes(), "compose3.dot") |
| } |
| |
| func TestComposeWithNestedTags(t *testing.T) { |
| g := baseGraph() |
| a, c := baseAttrsAndConfig() |
| |
| // Add tags to Node 1. |
| g.Nodes[0].LabelTags["tag1"] = &Tag{ |
| Name: "tag1", |
| Cum: 10, |
| Flat: 10, |
| } |
| g.Nodes[0].NumericTags["tag1"] = TagMap{ |
| "tag2": &Tag{ |
| Name: "tag2", |
| Cum: 20, |
| Flat: 20, |
| Unit: "ms", |
| }, |
| } |
| |
| var buf bytes.Buffer |
| ComposeDot(&buf, g, a, c) |
| |
| compareGraphs(t, buf.Bytes(), "compose5.dot") |
| } |
| |
| func TestComposeWithEmptyGraph(t *testing.T) { |
| g := &Graph{} |
| a, c := baseAttrsAndConfig() |
| |
| var buf bytes.Buffer |
| ComposeDot(&buf, g, a, c) |
| |
| compareGraphs(t, buf.Bytes(), "compose4.dot") |
| } |
| |
| func TestComposeWithStandardGraphAndURL(t *testing.T) { |
| g := baseGraph() |
| a, c := baseAttrsAndConfig() |
| c.LegendURL = "http://example.com" |
| |
| var buf bytes.Buffer |
| ComposeDot(&buf, g, a, c) |
| |
| compareGraphs(t, buf.Bytes(), "compose6.dot") |
| } |
| |
| func baseGraph() *Graph { |
| src := &Node{ |
| Info: NodeInfo{Name: "src"}, |
| Flat: 10, |
| Cum: 25, |
| In: make(EdgeMap), |
| Out: make(EdgeMap), |
| LabelTags: make(TagMap), |
| NumericTags: make(map[string]TagMap), |
| } |
| dest := &Node{ |
| Info: NodeInfo{Name: "dest"}, |
| Flat: 15, |
| Cum: 25, |
| In: make(EdgeMap), |
| Out: make(EdgeMap), |
| LabelTags: make(TagMap), |
| NumericTags: make(map[string]TagMap), |
| } |
| edge := &Edge{ |
| Src: src, |
| Dest: dest, |
| Weight: 10, |
| } |
| src.Out[dest] = edge |
| src.In[src] = edge |
| return &Graph{ |
| Nodes: Nodes{ |
| src, |
| dest, |
| }, |
| } |
| } |
| |
| func baseAttrsAndConfig() (*DotAttributes, *DotConfig) { |
| a := &DotAttributes{ |
| Nodes: make(map[*Node]*DotNodeAttributes), |
| } |
| c := &DotConfig{ |
| Title: "testtitle", |
| Labels: []string{"label1", "label2"}, |
| Total: 100, |
| FormatValue: func(v int64) string { |
| return strconv.FormatInt(v, 10) |
| }, |
| } |
| return a, c |
| } |
| |
| func compareGraphs(t *testing.T, got []byte, wantFile string) { |
| wantFile = filepath.Join("testdata", wantFile) |
| want, err := ioutil.ReadFile(wantFile) |
| if err != nil { |
| t.Fatalf("error reading test file %s: %v", wantFile, err) |
| } |
| |
| if string(got) != string(want) { |
| d, err := proftest.Diff(got, want) |
| if err != nil { |
| t.Fatalf("error finding diff: %v", err) |
| } |
| t.Errorf("Compose incorrectly wrote %s", string(d)) |
| if *updateFlag { |
| err := ioutil.WriteFile(wantFile, got, 0644) |
| if err != nil { |
| t.Errorf("failed to update the golden file %q: %v", wantFile, err) |
| } |
| } |
| } |
| } |
| |
| func TestNodeletCountCapping(t *testing.T) { |
| labelTags := make(TagMap) |
| for i := 0; i < 10; i++ { |
| name := fmt.Sprintf("tag-%d", i) |
| labelTags[name] = &Tag{ |
| Name: name, |
| Flat: 10, |
| Cum: 10, |
| } |
| } |
| numTags := make(TagMap) |
| for i := 0; i < 10; i++ { |
| name := fmt.Sprintf("num-tag-%d", i) |
| numTags[name] = &Tag{ |
| Name: name, |
| Unit: "mb", |
| Value: 16, |
| Flat: 10, |
| Cum: 10, |
| } |
| } |
| node1 := &Node{ |
| Info: NodeInfo{Name: "node1-with-tags"}, |
| Flat: 10, |
| Cum: 10, |
| NumericTags: map[string]TagMap{"": numTags}, |
| LabelTags: labelTags, |
| } |
| node2 := &Node{ |
| Info: NodeInfo{Name: "node2"}, |
| Flat: 15, |
| Cum: 15, |
| } |
| node3 := &Node{ |
| Info: NodeInfo{Name: "node3"}, |
| Flat: 15, |
| Cum: 15, |
| } |
| g := &Graph{ |
| Nodes: Nodes{ |
| node1, |
| node2, |
| node3, |
| }, |
| } |
| for n := 1; n <= 3; n++ { |
| input := maxNodelets + n |
| if got, want := len(g.SelectTopNodes(input, true)), n; got != want { |
| t.Errorf("SelectTopNodes(%d): got %d nodes, want %d", input, got, want) |
| } |
| } |
| } |
| |
| func TestMultilinePrintableName(t *testing.T) { |
| ni := &NodeInfo{ |
| Name: "test1.test2::test3", |
| File: "src/file.cc", |
| Address: 123, |
| Lineno: 999, |
| } |
| |
| want := fmt.Sprintf(`%016x\ntest1\ntest2\ntest3\nfile.cc:999\n`, 123) |
| if got := multilinePrintableName(ni); got != want { |
| t.Errorf("multilinePrintableName(%#v) == %q, want %q", ni, got, want) |
| } |
| } |
| |
| func TestTagCollapse(t *testing.T) { |
| |
| makeTag := func(name, unit string, value, flat, cum int64) *Tag { |
| return &Tag{name, unit, value, flat, 0, cum, 0} |
| } |
| |
| tagSource := []*Tag{ |
| makeTag("12mb", "mb", 12, 100, 100), |
| makeTag("1kb", "kb", 1, 1, 1), |
| makeTag("1mb", "mb", 1, 1000, 1000), |
| makeTag("2048mb", "mb", 2048, 1000, 1000), |
| makeTag("1b", "b", 1, 100, 100), |
| makeTag("2b", "b", 2, 100, 100), |
| makeTag("7b", "b", 7, 100, 100), |
| } |
| |
| tagWant := [][]*Tag{ |
| { |
| makeTag("1B..2GB", "", 0, 2401, 2401), |
| }, |
| { |
| makeTag("2GB", "", 0, 1000, 1000), |
| makeTag("1B..12MB", "", 0, 1401, 1401), |
| }, |
| { |
| makeTag("2GB", "", 0, 1000, 1000), |
| makeTag("12MB", "", 0, 100, 100), |
| makeTag("1B..1MB", "", 0, 1301, 1301), |
| }, |
| { |
| makeTag("2GB", "", 0, 1000, 1000), |
| makeTag("1MB", "", 0, 1000, 1000), |
| makeTag("2B..1kB", "", 0, 201, 201), |
| makeTag("1B", "", 0, 100, 100), |
| makeTag("12MB", "", 0, 100, 100), |
| }, |
| } |
| |
| for _, tc := range tagWant { |
| var got, want []*Tag |
| b := builder{nil, &DotAttributes{}, &DotConfig{}} |
| got = b.collapsedTags(tagSource, len(tc), true) |
| want = SortTags(tc, true) |
| |
| if !reflect.DeepEqual(got, want) { |
| t.Errorf("collapse to %d, got:\n%v\nwant:\n%v", len(tc), tagString(got), tagString(want)) |
| } |
| } |
| } |
| |
| func tagString(t []*Tag) string { |
| var ret []string |
| for _, s := range t { |
| ret = append(ret, fmt.Sprintln(s)) |
| } |
| return strings.Join(ret, ":") |
| } |