Home | History | Annotate | Download | only in driver
      1 // Copyright 2017 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 driver
     16 
     17 import (
     18 	"fmt"
     19 	"io/ioutil"
     20 	"net"
     21 	"net/http"
     22 	"net/http/httptest"
     23 	"net/url"
     24 	"os/exec"
     25 	"regexp"
     26 	"sync"
     27 	"testing"
     28 	"time"
     29 
     30 	"runtime"
     31 
     32 	"github.com/google/pprof/internal/plugin"
     33 	"github.com/google/pprof/profile"
     34 )
     35 
     36 func TestWebInterface(t *testing.T) {
     37 	// This test starts a web browser in a background goroutine
     38 	// after a 500ms delay. Sometimes the test exits before it
     39 	// can run the browser, but sometimes the browser does open.
     40 	// That's obviously unacceptable.
     41 	defer time.Sleep(2 * time.Second) // to see the browser open
     42 	t.Skip("golang.org/issue/22651")
     43 
     44 	if runtime.GOOS == "nacl" {
     45 		t.Skip("test assumes tcp available")
     46 	}
     47 
     48 	prof := makeFakeProfile()
     49 
     50 	// Custom http server creator
     51 	var server *httptest.Server
     52 	serverCreated := make(chan bool)
     53 	creator := func(a *plugin.HTTPServerArgs) error {
     54 		server = httptest.NewServer(http.HandlerFunc(
     55 			func(w http.ResponseWriter, r *http.Request) {
     56 				if h := a.Handlers[r.URL.Path]; h != nil {
     57 					h.ServeHTTP(w, r)
     58 				}
     59 			}))
     60 		serverCreated <- true
     61 		return nil
     62 	}
     63 
     64 	// Start server and wait for it to be initialized
     65 	go serveWebInterface("unused:1234", prof, &plugin.Options{
     66 		Obj:        fakeObjTool{},
     67 		UI:         &stdUI{},
     68 		HTTPServer: creator,
     69 	})
     70 	<-serverCreated
     71 	defer server.Close()
     72 
     73 	haveDot := false
     74 	if _, err := exec.LookPath("dot"); err == nil {
     75 		haveDot = true
     76 	}
     77 
     78 	type testCase struct {
     79 		path    string
     80 		want    []string
     81 		needDot bool
     82 	}
     83 	testcases := []testCase{
     84 		{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
     85 		{"/top", []string{`"Name":"F2","InlineLabel":"","Flat":200,"Cum":300,"FlatFormat":"200ms","CumFormat":"300ms"}`}, false},
     86 		{"/source?f=" + url.QueryEscape("F[12]"),
     87 			[]string{"F1", "F2", "300ms +line1"}, false},
     88 		{"/peek?f=" + url.QueryEscape("F[12]"),
     89 			[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},
     90 		{"/disasm?f=" + url.QueryEscape("F[12]"),
     91 			[]string{"f1:asm", "f2:asm"}, false},
     92 	}
     93 	for _, c := range testcases {
     94 		if c.needDot && !haveDot {
     95 			t.Log("skipping", c.path, "since dot (graphviz) does not seem to be installed")
     96 			continue
     97 		}
     98 
     99 		res, err := http.Get(server.URL + c.path)
    100 		if err != nil {
    101 			t.Error("could not fetch", c.path, err)
    102 			continue
    103 		}
    104 		data, err := ioutil.ReadAll(res.Body)
    105 		if err != nil {
    106 			t.Error("could not read response", c.path, err)
    107 			continue
    108 		}
    109 		result := string(data)
    110 		for _, w := range c.want {
    111 			if match, _ := regexp.MatchString(w, result); !match {
    112 				t.Errorf("response for %s does not match "+
    113 					"expected pattern '%s'; "+
    114 					"actual result:\n%s", c.path, w, result)
    115 			}
    116 		}
    117 	}
    118 
    119 	// Also fetch all the test case URLs in parallel to test thread
    120 	// safety when run under the race detector.
    121 	var wg sync.WaitGroup
    122 	for _, c := range testcases {
    123 		if c.needDot && !haveDot {
    124 			continue
    125 		}
    126 		path := server.URL + c.path
    127 		for count := 0; count < 2; count++ {
    128 			wg.Add(1)
    129 			go func() {
    130 				http.Get(path)
    131 				wg.Done()
    132 			}()
    133 		}
    134 	}
    135 	wg.Wait()
    136 
    137 	time.Sleep(5 * time.Second)
    138 }
    139 
    140 // Implement fake object file support.
    141 
    142 const addrBase = 0x1000
    143 const fakeSource = "testdata/file1000.src"
    144 
    145 type fakeObj struct{}
    146 
    147 func (f fakeObj) Close() error    { return nil }
    148 func (f fakeObj) Name() string    { return "testbin" }
    149 func (f fakeObj) Base() uint64    { return 0 }
    150 func (f fakeObj) BuildID() string { return "" }
    151 func (f fakeObj) SourceLine(addr uint64) ([]plugin.Frame, error) {
    152 	return nil, fmt.Errorf("SourceLine unimplemented")
    153 }
    154 func (f fakeObj) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
    155 	return []*plugin.Sym{
    156 		{[]string{"F1"}, fakeSource, addrBase, addrBase + 10},
    157 		{[]string{"F2"}, fakeSource, addrBase + 10, addrBase + 20},
    158 		{[]string{"F3"}, fakeSource, addrBase + 20, addrBase + 30},
    159 	}, nil
    160 }
    161 
    162 type fakeObjTool struct{}
    163 
    164 func (obj fakeObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
    165 	return fakeObj{}, nil
    166 }
    167 
    168 func (obj fakeObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
    169 	return []plugin.Inst{
    170 		{Addr: addrBase + 0, Text: "f1:asm", Function: "F1"},
    171 		{Addr: addrBase + 10, Text: "f2:asm", Function: "F2"},
    172 		{Addr: addrBase + 20, Text: "d3:asm", Function: "F3"},
    173 	}, nil
    174 }
    175 
    176 func makeFakeProfile() *profile.Profile {
    177 	// Three functions: F1, F2, F3 with three lines, 11, 22, 33.
    178 	funcs := []*profile.Function{
    179 		{ID: 1, Name: "F1", Filename: fakeSource, StartLine: 3},
    180 		{ID: 2, Name: "F2", Filename: fakeSource, StartLine: 5},
    181 		{ID: 3, Name: "F3", Filename: fakeSource, StartLine: 7},
    182 	}
    183 	lines := []profile.Line{
    184 		{Function: funcs[0], Line: 11},
    185 		{Function: funcs[1], Line: 22},
    186 		{Function: funcs[2], Line: 33},
    187 	}
    188 	mapping := []*profile.Mapping{
    189 		{
    190 			ID:             1,
    191 			Start:          addrBase,
    192 			Limit:          addrBase + 10,
    193 			Offset:         0,
    194 			File:           "testbin",
    195 			HasFunctions:   true,
    196 			HasFilenames:   true,
    197 			HasLineNumbers: true,
    198 		},
    199 	}
    200 
    201 	// Three interesting addresses: base+{10,20,30}
    202 	locs := []*profile.Location{
    203 		{ID: 1, Address: addrBase + 10, Line: lines[0:1], Mapping: mapping[0]},
    204 		{ID: 2, Address: addrBase + 20, Line: lines[1:2], Mapping: mapping[0]},
    205 		{ID: 3, Address: addrBase + 30, Line: lines[2:3], Mapping: mapping[0]},
    206 	}
    207 
    208 	// Two stack traces.
    209 	return &profile.Profile{
    210 		PeriodType:    &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
    211 		Period:        1,
    212 		DurationNanos: 10e9,
    213 		SampleType: []*profile.ValueType{
    214 			{Type: "cpu", Unit: "milliseconds"},
    215 		},
    216 		Sample: []*profile.Sample{
    217 			{
    218 				Location: []*profile.Location{locs[2], locs[1], locs[0]},
    219 				Value:    []int64{100},
    220 			},
    221 			{
    222 				Location: []*profile.Location{locs[1], locs[0]},
    223 				Value:    []int64{200},
    224 			},
    225 		},
    226 		Location: locs,
    227 		Function: funcs,
    228 		Mapping:  mapping,
    229 	}
    230 }
    231 
    232 func TestIsLocalHost(t *testing.T) {
    233 	for _, s := range []string{"localhost:10000", "[::1]:10000", "127.0.0.1:10000"} {
    234 		host, _, err := net.SplitHostPort(s)
    235 		if err != nil {
    236 			t.Error("unexpected error when splitting", s)
    237 			continue
    238 		}
    239 		if !isLocalhost(host) {
    240 			t.Errorf("host %s from %s not considered local", host, s)
    241 		}
    242 	}
    243 }
    244