Home | History | Annotate | Download | only in profile
      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 profile
     16 
     17 import (
     18 	"bytes"
     19 	"fmt"
     20 	"reflect"
     21 	"strconv"
     22 	"strings"
     23 	"testing"
     24 )
     25 
     26 func TestLegacyProfileType(t *testing.T) {
     27 	type testcase struct {
     28 		sampleTypes []string
     29 		typeSet     [][]string
     30 		want        bool
     31 		setName     string
     32 	}
     33 
     34 	heap := heapzSampleTypes
     35 	cont := contentionzSampleTypes
     36 	testcases := []testcase{
     37 		// True cases
     38 		{[]string{"allocations", "size"}, heap, true, "heapzSampleTypes"},
     39 		{[]string{"objects", "space"}, heap, true, "heapzSampleTypes"},
     40 		{[]string{"inuse_objects", "inuse_space"}, heap, true, "heapzSampleTypes"},
     41 		{[]string{"alloc_objects", "alloc_space"}, heap, true, "heapzSampleTypes"},
     42 		{[]string{"contentions", "delay"}, cont, true, "contentionzSampleTypes"},
     43 		// False cases
     44 		{[]string{"objects"}, heap, false, "heapzSampleTypes"},
     45 		{[]string{"objects", "unknown"}, heap, false, "heapzSampleTypes"},
     46 		{[]string{"contentions", "delay"}, heap, false, "heapzSampleTypes"},
     47 		{[]string{"samples", "cpu"}, heap, false, "heapzSampleTypes"},
     48 		{[]string{"samples", "cpu"}, cont, false, "contentionzSampleTypes"},
     49 	}
     50 
     51 	for _, tc := range testcases {
     52 		p := profileOfType(tc.sampleTypes)
     53 		if got := isProfileType(p, tc.typeSet); got != tc.want {
     54 			t.Error("isProfileType({"+strings.Join(tc.sampleTypes, ",")+"},", tc.setName, "), got", got, "want", tc.want)
     55 		}
     56 	}
     57 }
     58 
     59 func TestCpuParse(t *testing.T) {
     60 	// profileString is a legacy encoded profile, represnted by words separated by ":"
     61 	// Each sample has the form value : N : stack1..stackN
     62 	// EOF is represented as "0:1:0"
     63 	profileString := "1:3:100:999:100:"                                      // sample with bogus 999 and duplicate leaf
     64 	profileString += "1:5:200:999:200:501:502:"                              // sample with bogus 999 and duplicate leaf
     65 	profileString += "1:12:300:999:300:601:602:603:604:605:606:607:608:609:" // sample with bogus 999 and duplicate leaf
     66 	profileString += "0:1:0000"                                              // EOF -- must use 4 bytes for the final zero
     67 
     68 	p, err := cpuProfile([]byte(profileString), 1, parseString)
     69 	if err != nil {
     70 		t.Fatal(err)
     71 	}
     72 
     73 	if err := checkTestSample(p, []uint64{100}); err != nil {
     74 		t.Error(err)
     75 	}
     76 	if err := checkTestSample(p, []uint64{200, 500, 501}); err != nil {
     77 		t.Error(err)
     78 	}
     79 	if err := checkTestSample(p, []uint64{300, 600, 601, 602, 603, 604, 605, 606, 607, 608}); err != nil {
     80 		t.Error(err)
     81 	}
     82 }
     83 
     84 func parseString(b []byte) (uint64, []byte) {
     85 	slices := bytes.SplitN(b, []byte(":"), 2)
     86 	var value, remainder []byte
     87 	if len(slices) > 0 {
     88 		value = slices[0]
     89 	}
     90 	if len(slices) > 1 {
     91 		remainder = slices[1]
     92 	}
     93 	v, _ := strconv.ParseUint(string(value), 10, 64)
     94 	return v, remainder
     95 }
     96 
     97 func checkTestSample(p *Profile, want []uint64) error {
     98 	for _, s := range p.Sample {
     99 		got := []uint64{}
    100 		for _, l := range s.Location {
    101 			got = append(got, l.Address)
    102 		}
    103 		if reflect.DeepEqual(got, want) {
    104 			return nil
    105 		}
    106 	}
    107 	return fmt.Errorf("Could not find sample : %v", want)
    108 }
    109 
    110 // profileOfType creates an empty profile with only sample types set,
    111 // for testing purposes only.
    112 func profileOfType(sampleTypes []string) *Profile {
    113 	p := new(Profile)
    114 	p.SampleType = make([]*ValueType, len(sampleTypes))
    115 	for i, t := range sampleTypes {
    116 		p.SampleType[i] = new(ValueType)
    117 		p.SampleType[i].Type = t
    118 	}
    119 	return p
    120 }
    121 
    122 func TestParseMappingEntry(t *testing.T) {
    123 	for _, test := range []*struct {
    124 		entry string
    125 		want  *Mapping
    126 	}{
    127 		{
    128 			entry: "00400000-02e00000 r-xp 00000000 00:00 0",
    129 			want: &Mapping{
    130 				Start: 0x400000,
    131 				Limit: 0x2e00000,
    132 			},
    133 		},
    134 		{
    135 			entry: "02e00000-02e8a000 r-xp 02a00000 00:00 15953927    /foo/bin",
    136 			want: &Mapping{
    137 				Start:  0x2e00000,
    138 				Limit:  0x2e8a000,
    139 				Offset: 0x2a00000,
    140 				File:   "/foo/bin",
    141 			},
    142 		},
    143 		{
    144 			entry: "02e00000-02e8a000 r-xp 000000 00:00 15953927    [vdso]",
    145 			want: &Mapping{
    146 				Start: 0x2e00000,
    147 				Limit: 0x2e8a000,
    148 				File:  "[vdso]",
    149 			},
    150 		},
    151 		{
    152 			entry: "  02e00000-02e8a000: /foo/bin (@2a00000)",
    153 			want: &Mapping{
    154 				Start:  0x2e00000,
    155 				Limit:  0x2e8a000,
    156 				Offset: 0x2a00000,
    157 				File:   "/foo/bin",
    158 			},
    159 		},
    160 		{
    161 			entry: "  02e00000-02e8a000: /foo/bin (deleted)",
    162 			want: &Mapping{
    163 				Start: 0x2e00000,
    164 				Limit: 0x2e8a000,
    165 				File:  "/foo/bin",
    166 			},
    167 		},
    168 		{
    169 			entry: "  02e00000-02e8a000: /foo/bin",
    170 			want: &Mapping{
    171 				Start: 0x2e00000,
    172 				Limit: 0x2e8a000,
    173 				File:  "/foo/bin",
    174 			},
    175 		},
    176 		{
    177 			entry: "  02e00000-02e8a000: [vdso]",
    178 			want: &Mapping{
    179 				Start: 0x2e00000,
    180 				Limit: 0x2e8a000,
    181 				File:  "[vdso]",
    182 			},
    183 		},
    184 		{entry: "0xff6810563000 0xff6810565000 r-xp abc_exe 87c4d547f895cfd6a370e08dc5c5ee7bd4199d5b",
    185 			want: &Mapping{
    186 				Start:   0xff6810563000,
    187 				Limit:   0xff6810565000,
    188 				File:    "abc_exe",
    189 				BuildID: "87c4d547f895cfd6a370e08dc5c5ee7bd4199d5b",
    190 			},
    191 		},
    192 		{entry: "7f5e5435e000-7f5e5455e000 --xp 00002000 00:00 1531        myprogram",
    193 			want: &Mapping{
    194 				Start:  0x7f5e5435e000,
    195 				Limit:  0x7f5e5455e000,
    196 				Offset: 0x2000,
    197 				File:   "myprogram",
    198 			},
    199 		},
    200 		{entry: "7f7472710000-7f7472722000 r-xp 00000000 fc:00 790190      /usr/lib/libfantastic-1.2.so",
    201 			want: &Mapping{
    202 				Start: 0x7f7472710000,
    203 				Limit: 0x7f7472722000,
    204 				File:  "/usr/lib/libfantastic-1.2.so",
    205 			},
    206 		},
    207 		{entry: "7f47a542f000-7f47a5447000: /lib/libpthread-2.15.so",
    208 			want: &Mapping{
    209 				Start: 0x7f47a542f000,
    210 				Limit: 0x7f47a5447000,
    211 				File:  "/lib/libpthread-2.15.so",
    212 			},
    213 		},
    214 		{entry: "0x40000-0x80000 /path/to/binary      (@FF00)            abc123456",
    215 			want: &Mapping{
    216 				Start:   0x40000,
    217 				Limit:   0x80000,
    218 				File:    "/path/to/binary",
    219 				Offset:  0xFF00,
    220 				BuildID: "abc123456",
    221 			},
    222 		},
    223 		{entry: "W1220 15:07:15.201776    8272 logger.cc:12033] --- Memory map: ---\n" +
    224 			"0x40000-0x80000 /path/to/binary      (@FF00)            abc123456",
    225 			want: &Mapping{
    226 				Start:   0x40000,
    227 				Limit:   0x80000,
    228 				File:    "/path/to/binary",
    229 				Offset:  0xFF00,
    230 				BuildID: "abc123456",
    231 			},
    232 		},
    233 		{entry: "W1220 15:07:15.201776    8272 logger.cc:12033] --- Memory map: ---\n" +
    234 			"W1220 15:07:15.202776    8272 logger.cc:12036]   0x40000-0x80000 /path/to/binary      (@FF00)            abc123456",
    235 			want: &Mapping{
    236 				Start:   0x40000,
    237 				Limit:   0x80000,
    238 				File:    "/path/to/binary",
    239 				Offset:  0xFF00,
    240 				BuildID: "abc123456",
    241 			},
    242 		},
    243 		{entry: "7f5e5435e000-7f5e5455e000 ---p 00002000 00:00 1531        myprogram",
    244 			want: nil,
    245 		},
    246 	} {
    247 		got, err := ParseProcMaps(strings.NewReader(test.entry))
    248 		if err != nil {
    249 			t.Errorf("%s: %v", test.entry, err)
    250 			continue
    251 		}
    252 		if test.want == nil {
    253 			if got, want := len(got), 0; got != want {
    254 				t.Errorf("%s: got %d mappings, want %d", test.entry, got, want)
    255 			}
    256 			continue
    257 		}
    258 		if got, want := len(got), 1; got != want {
    259 			t.Errorf("%s: got %d mappings, want %d", test.entry, got, want)
    260 			continue
    261 		}
    262 		if !reflect.DeepEqual(test.want, got[0]) {
    263 			t.Errorf("%s want=%v got=%v", test.entry, test.want, got[0])
    264 		}
    265 	}
    266 }
    267 
    268 func TestParseThreadProfileWithInvalidAddress(t *testing.T) {
    269 	profile := `
    270 --- threadz 1 ---
    271 
    272 --- Thread 7eff063d9940 (name: main/25376) stack: ---
    273   PC: 0x40b688 0x4d5f51 0x40be31 0x473add693e639c6f0
    274 --- Memory map: ---
    275   00400000-00fcb000: /home/rsilvera/cppbench/cppbench_server_main.unstripped
    276 	`
    277 	wantErr := "failed to parse as hex 64-bit number: 0x473add693e639c6f0"
    278 	if _, gotErr := parseThread([]byte(profile)); !strings.Contains(gotErr.Error(), wantErr) {
    279 		t.Errorf("parseThread(): got error %q, want error containing %q", gotErr, wantErr)
    280 	}
    281 }
    282 
    283 func TestParseGoCount(t *testing.T) {
    284 	for _, test := range []struct {
    285 		in  string
    286 		typ string
    287 	}{
    288 		{
    289 			in: `# ignored comment
    290 
    291 threadcreate profile: total 123
    292 `,
    293 			typ: "threadcreate",
    294 		},
    295 		{
    296 			in: `
    297 # ignored comment
    298 goroutine profile: total 123456
    299 `,
    300 			typ: "goroutine",
    301 		},
    302 		{
    303 			in: `
    304 sub/dir-ect_o.ry profile: total 999
    305 `,
    306 			typ: "sub/dir-ect_o.ry",
    307 		},
    308 	} {
    309 		t.Run(test.typ, func(t *testing.T) {
    310 			p, err := parseGoCount([]byte(test.in))
    311 			if err != nil {
    312 				t.Fatalf("parseGoCount(%q) = %v", test.in, err)
    313 			}
    314 			if typ := p.PeriodType.Type; typ != test.typ {
    315 				t.Fatalf("parseGoCount(%q).PeriodType.Type = %q want %q", test.in, typ, test.typ)
    316 			}
    317 		})
    318 	}
    319 }
    320