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