github.com/grafana/pyroscope@v1.18.0/pkg/model/profile_test.go (about) 1 package model 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/require" 8 9 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 10 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 11 ) 12 13 func Test_extractMappingFilename(t *testing.T) { 14 assert.Equal(t, "app", extractMappingFilename(`app`)) 15 assert.Equal(t, "app", extractMappingFilename(`./app`)) 16 assert.Equal(t, "app", extractMappingFilename(`/usr/bin/app`)) 17 assert.Equal(t, "app", extractMappingFilename(`../../../app`)) 18 assert.Equal(t, "app", extractMappingFilename(`/usr/bin/app\`)) 19 assert.Equal(t, "app", extractMappingFilename(`/usr/bin/app\\`)) 20 assert.Equal(t, "my awesome app", extractMappingFilename(`/usr/bin/my awesome app`)) 21 assert.Equal(t, "app", extractMappingFilename(`/usr/bin/my\ awesome\ app`)) 22 23 assert.Equal(t, "app.exe", extractMappingFilename(`C:\\app.exe`)) 24 assert.Equal(t, "app.exe", extractMappingFilename(`C:\\./app.exe`)) 25 assert.Equal(t, "app.exe", extractMappingFilename(`./app.exe`)) 26 assert.Equal(t, "app.exe", extractMappingFilename(`./../app.exe`)) 27 assert.Equal(t, "app.exe", extractMappingFilename(`C:\\build\app.exe`)) 28 assert.Equal(t, "My App.exe", extractMappingFilename(`C:\\build\My App.exe`)) 29 assert.Equal(t, "Not My App.exe", extractMappingFilename(`C:\\build\Not My App.exe`)) 30 assert.Equal(t, "app.exe", extractMappingFilename(`\\app.exe`)) 31 assert.Equal(t, "app.exe", extractMappingFilename(`\\build\app.exe`)) 32 33 assert.Equal(t, "bin", extractMappingFilename(`/usr/bin/`)) 34 assert.Equal(t, "build", extractMappingFilename(`\\build\`)) 35 36 assert.Equal(t, "", extractMappingFilename("")) 37 assert.Equal(t, "", extractMappingFilename(`[vdso]`)) 38 assert.Equal(t, "", extractMappingFilename(`[vsyscall]`)) 39 assert.Equal(t, "", extractMappingFilename(`//anon`)) 40 assert.Equal(t, "not a path actually", extractMappingFilename(`not a path actually`)) 41 } 42 43 func Test_symbolsPartitionKeyForProfile(t *testing.T) { 44 tests := []struct { 45 name string 46 partitionLabel string 47 labels Labels 48 profile *profilev1.Profile 49 expected string 50 }{ 51 { 52 partitionLabel: "", 53 profile: &profilev1.Profile{Mapping: []*profilev1.Mapping{}}, 54 expected: "unknown", 55 }, 56 { 57 partitionLabel: "", 58 profile: &profilev1.Profile{Mapping: []*profilev1.Mapping{}}, 59 labels: Labels{{Name: LabelNameServiceName, Value: "service_foo"}}, 60 expected: "service_foo", 61 }, 62 { 63 partitionLabel: "", 64 profile: &profilev1.Profile{ 65 Mapping: []*profilev1.Mapping{{Filename: 1}}, 66 StringTable: []string{"", "filename"}, 67 }, 68 expected: "filename", 69 }, 70 { 71 partitionLabel: "partition", 72 profile: &profilev1.Profile{}, 73 labels: Labels{{Name: "partition", Value: "partitionValue"}}, 74 expected: "partitionValue", 75 }, 76 { // partition label is specified but not found: mapping is ignored. 77 partitionLabel: "partition", 78 profile: &profilev1.Profile{ 79 Mapping: []*profilev1.Mapping{{Filename: 1}}, 80 StringTable: []string{"", "valid_filename"}, 81 }, 82 expected: "unknown", 83 }, 84 { 85 partitionLabel: "partition", 86 profile: &profilev1.Profile{ 87 Mapping: []*profilev1.Mapping{{Filename: 1}}, 88 StringTable: []string{"", "valid_filename"}, 89 }, 90 expected: "partitionValue", 91 labels: Labels{{Name: "partition", Value: "partitionValue"}}, 92 }, 93 } 94 95 for _, tt := range tests { 96 t.Run(tt.name, func(t *testing.T) { 97 actual := symbolsPartitionKeyForProfile(tt.labels, tt.partitionLabel, tt.profile) 98 assert.Equal(t, tt.expected, actual) 99 }) 100 } 101 } 102 103 func Test_ParseProfileTypeSelector(t *testing.T) { 104 tests := []struct { 105 Name string 106 ID string 107 Want *typesv1.ProfileType 108 WantErr string 109 }{ 110 { 111 Name: "block_contentions_count", 112 ID: "block:contentions:count:contentions:count", 113 Want: &typesv1.ProfileType{ 114 Name: "block", 115 ID: "block:contentions:count:contentions:count", 116 SampleType: "contentions", 117 SampleUnit: "count", 118 PeriodType: "contentions", 119 PeriodUnit: "count", 120 }, 121 }, 122 { 123 Name: "mutex_delay_nanoseconds", 124 ID: "mutex:delay:nanoseconds:contentions:count", 125 Want: &typesv1.ProfileType{ 126 Name: "mutex", 127 ID: "mutex:delay:nanoseconds:contentions:count", 128 SampleType: "delay", 129 SampleUnit: "nanoseconds", 130 PeriodType: "contentions", 131 PeriodUnit: "count", 132 }, 133 }, 134 { 135 Name: "memory_alloc_space_bytes", 136 ID: "memory:alloc_space:bytes:space:bytes", 137 Want: &typesv1.ProfileType{ 138 Name: "memory", 139 ID: "memory:alloc_space:bytes:space:bytes", 140 SampleType: "alloc_space", 141 SampleUnit: "bytes", 142 PeriodType: "space", 143 PeriodUnit: "bytes", 144 }, 145 }, 146 { 147 Name: "too_few_parts", 148 ID: "memory:alloc_space:bytes:space", 149 WantErr: `profile-type selection must be of the form <name>:<sample-type>:<sample-unit>:<period-type>:<period-unit>(:delta), got(4): "memory:alloc_space:bytes:space"`, 150 }, 151 { 152 Name: "too_many_parts", 153 ID: "memory:alloc_space:bytes:space:bytes:extra:part", 154 WantErr: `profile-type selection must be of the form <name>:<sample-type>:<sample-unit>:<period-type>:<period-unit>(:delta), got(7): "memory:alloc_space:bytes:space:bytes:extra:part"`, 155 }, 156 { 157 Name: "empty_string", 158 ID: "", 159 WantErr: `profile-type selection must be of the form <name>:<sample-type>:<sample-unit>:<period-type>:<period-unit>(:delta), got(1): ""`, 160 }, 161 { 162 Name: "valid_with_delta", 163 ID: "cpu:samples:count:cpu:nanoseconds:delta", 164 Want: &typesv1.ProfileType{ 165 Name: "cpu", 166 ID: "cpu:samples:count:cpu:nanoseconds:delta", 167 SampleType: "samples", 168 SampleUnit: "count", 169 PeriodType: "cpu", 170 PeriodUnit: "nanoseconds", 171 }, 172 }, 173 } 174 175 for _, tt := range tests { 176 t.Run(tt.Name, func(t *testing.T) { 177 got, err := ParseProfileTypeSelector(tt.ID) 178 if tt.WantErr != "" { 179 require.Error(t, err) 180 require.EqualError(t, err, tt.WantErr) 181 } else { 182 require.NoError(t, err) 183 require.Equal(t, tt.Want, got) 184 } 185 }) 186 } 187 } 188 189 func Test_ParseProfileTypeSelector_ValidProfileTypes(t *testing.T) { 190 // Shamelessly copied from: https://github.com/grafana/profiles-drilldown/blob/4e261a8744034bddefdec757d5d2e1d8dc0ec2bb/src/shared/infrastructure/profile-metrics/profile-metrics.json#L93 191 var validProfileTypes = map[string]*typesv1.ProfileType{ 192 "block:contentions:count:contentions:count": { 193 ID: "block:contentions:count:contentions:count", 194 Name: "block", 195 SampleType: "contentions", 196 SampleUnit: "count", 197 PeriodType: "contentions", 198 PeriodUnit: "count", 199 }, 200 "block:delay:nanoseconds:contentions:count": { 201 ID: "block:delay:nanoseconds:contentions:count", 202 Name: "block", 203 SampleType: "delay", 204 SampleUnit: "nanoseconds", 205 PeriodType: "contentions", 206 PeriodUnit: "count", 207 }, 208 "goroutine:goroutine:count:goroutine:count": { 209 ID: "goroutine:goroutine:count:goroutine:count", 210 Name: "goroutine", 211 SampleType: "goroutine", 212 SampleUnit: "count", 213 PeriodType: "goroutine", 214 PeriodUnit: "count", 215 }, 216 "goroutines:goroutine:count:goroutine:count": { 217 ID: "goroutines:goroutine:count:goroutine:count", 218 Name: "goroutines", 219 SampleType: "goroutine", 220 SampleUnit: "count", 221 PeriodType: "goroutine", 222 PeriodUnit: "count", 223 }, 224 "memory:alloc_in_new_tlab_bytes:bytes::": { 225 ID: "memory:alloc_in_new_tlab_bytes:bytes::", 226 Name: "memory", 227 SampleType: "alloc_in_new_tlab_bytes", 228 SampleUnit: "bytes", 229 PeriodType: "", 230 PeriodUnit: "", 231 }, 232 "memory:alloc_in_new_tlab_objects:count::": { 233 ID: "memory:alloc_in_new_tlab_objects:count::", 234 Name: "memory", 235 SampleType: "alloc_in_new_tlab_objects", 236 SampleUnit: "count", 237 PeriodType: "", 238 PeriodUnit: "", 239 }, 240 "memory:alloc_objects:count:space:bytes": { 241 ID: "memory:alloc_objects:count:space:bytes", 242 Name: "memory", 243 SampleType: "alloc_objects", 244 SampleUnit: "count", 245 PeriodType: "space", 246 PeriodUnit: "bytes", 247 }, 248 "memory:alloc_space:bytes:space:bytes": { 249 ID: "memory:alloc_space:bytes:space:bytes", 250 Name: "memory", 251 SampleType: "alloc_space", 252 SampleUnit: "bytes", 253 PeriodType: "space", 254 PeriodUnit: "bytes", 255 }, 256 "memory:inuse_objects:count:space:bytes": { 257 ID: "memory:inuse_objects:count:space:bytes", 258 Name: "memory", 259 SampleType: "inuse_objects", 260 SampleUnit: "count", 261 PeriodType: "space", 262 PeriodUnit: "bytes", 263 }, 264 "memory:inuse_space:bytes:space:bytes": { 265 ID: "memory:inuse_space:bytes:space:bytes", 266 Name: "memory", 267 SampleType: "inuse_space", 268 SampleUnit: "bytes", 269 PeriodType: "space", 270 PeriodUnit: "bytes", 271 }, 272 "mutex:contentions:count:contentions:count": { 273 ID: "mutex:contentions:count:contentions:count", 274 Name: "mutex", 275 SampleType: "contentions", 276 SampleUnit: "count", 277 PeriodType: "contentions", 278 PeriodUnit: "count", 279 }, 280 "mutex:delay:nanoseconds:contentions:count": { 281 ID: "mutex:delay:nanoseconds:contentions:count", 282 Name: "mutex", 283 SampleType: "delay", 284 SampleUnit: "nanoseconds", 285 PeriodType: "contentions", 286 PeriodUnit: "count", 287 }, 288 "process_cpu:alloc_samples:count:cpu:nanoseconds": { 289 ID: "process_cpu:alloc_samples:count:cpu:nanoseconds", 290 Name: "process_cpu", 291 SampleType: "alloc_samples", 292 SampleUnit: "count", 293 PeriodType: "cpu", 294 PeriodUnit: "nanoseconds", 295 }, 296 "process_cpu:alloc_size:bytes:cpu:nanoseconds": { 297 ID: "process_cpu:alloc_size:bytes:cpu:nanoseconds", 298 Name: "process_cpu", 299 SampleType: "alloc_size", 300 SampleUnit: "bytes", 301 PeriodType: "cpu", 302 PeriodUnit: "nanoseconds", 303 }, 304 "process_cpu:cpu:nanoseconds:cpu:nanoseconds": { 305 ID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 306 Name: "process_cpu", 307 SampleType: "cpu", 308 SampleUnit: "nanoseconds", 309 PeriodType: "cpu", 310 PeriodUnit: "nanoseconds", 311 }, 312 "process_cpu:exception:count:cpu:nanoseconds": { 313 ID: "process_cpu:exception:count:cpu:nanoseconds", 314 Name: "process_cpu", 315 SampleType: "exception", 316 SampleUnit: "count", 317 PeriodType: "cpu", 318 PeriodUnit: "nanoseconds", 319 }, 320 "process_cpu:lock_count:count:cpu:nanoseconds": { 321 ID: "process_cpu:lock_count:count:cpu:nanoseconds", 322 Name: "process_cpu", 323 SampleType: "lock_count", 324 SampleUnit: "count", 325 PeriodType: "cpu", 326 PeriodUnit: "nanoseconds", 327 }, 328 "process_cpu:lock_time:nanoseconds:cpu:nanoseconds": { 329 ID: "process_cpu:lock_time:nanoseconds:cpu:nanoseconds", 330 Name: "process_cpu", 331 SampleType: "lock_time", 332 SampleUnit: "nanoseconds", 333 PeriodType: "cpu", 334 PeriodUnit: "nanoseconds", 335 }, 336 "process_cpu:samples:count::milliseconds": { 337 ID: "process_cpu:samples:count::milliseconds", 338 Name: "process_cpu", 339 SampleType: "samples", 340 SampleUnit: "count", 341 PeriodType: "", 342 PeriodUnit: "milliseconds", 343 }, 344 "process_cpu:samples:count:cpu:nanoseconds": { 345 ID: "process_cpu:samples:count:cpu:nanoseconds", 346 Name: "process_cpu", 347 SampleType: "samples", 348 SampleUnit: "count", 349 PeriodType: "cpu", 350 PeriodUnit: "nanoseconds", 351 }, 352 } 353 354 for id, want := range validProfileTypes { 355 t.Run(id, func(t *testing.T) { 356 got, err := ParseProfileTypeSelector(id) 357 require.NoError(t, err) 358 require.Equal(t, want, got) 359 }) 360 } 361 }