OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 Copyright 2017 The Chromium Authors. All rights reserved. | 3 Copyright 2017 The Chromium Authors. All rights reserved. |
4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
5 found in the LICENSE file. | 5 found in the LICENSE file. |
6 --> | 6 --> |
7 | 7 |
8 <link rel="import" href="/tracing/core/test_utils.html"> | 8 <link rel="import" href="/tracing/core/test_utils.html"> |
9 <link rel="import" href="/tracing/extras/chrome/cpu_time.html"> | 9 <link rel="import" href="/tracing/extras/chrome/cpu_time.html"> |
| 10 <link rel="import" href="/tracing/extras/chrome/cpu_time_test_utils.html"> |
10 | 11 |
11 <script> | 12 <script> |
12 'use strict'; | 13 'use strict'; |
13 | 14 |
14 tr.b.unittest.testSuite(function() { | 15 tr.b.unittest.testSuite(function() { |
| 16 const getStageToInitiatorToSegmentBounds = |
| 17 tr.e.chrome.cpuTime.getStageToInitiatorToSegmentBounds; |
| 18 |
| 19 const INITIATOR_TYPE = tr.model.um.INITIATOR_TYPE; |
| 20 |
| 21 const CHROME_PROCESS_NAMES = |
| 22 tr.e.chrome.chrome_processes.CHROME_PROCESS_NAMES; |
| 23 |
| 24 const constructMultiDimensionalView = |
| 25 tr.e.chrome.cpuTime.constructMultiDimensionalView; |
| 26 |
| 27 const buildModelFromSpec = tr.e.chrome.cpuTimeTestUtils.buildModelFromSpec; |
| 28 |
15 test('getCpuTimeForThread', () => { | 29 test('getCpuTimeForThread', () => { |
16 const model = tr.c.TestUtils.newModel(function(model) { | 30 const model = tr.c.TestUtils.newModel(function(model) { |
17 const thread = model.getOrCreateProcess(1).getOrCreateThread(1); | 31 const thread = model.getOrCreateProcess(1).getOrCreateThread(1); |
18 const sliceSpecs = [ | 32 const sliceSpecs = [ |
19 {wallTimeBounds: [100, 200], cpuStart: 120, cpuDuration: 50}, | 33 {wallTimeBounds: [100, 200], cpuStart: 120, cpuDuration: 50}, |
20 {wallTimeBounds: [300, 600], cpuStart: 350, cpuDuration: 150} | 34 {wallTimeBounds: [300, 600], cpuStart: 350, cpuDuration: 150} |
21 ]; | 35 ]; |
22 for (const sliceSpec of sliceSpecs) { | 36 for (const sliceSpec of sliceSpecs) { |
23 thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ | 37 thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({ |
24 type: tr.model.ThreadSlice, | 38 type: tr.model.ThreadSlice, |
25 isTopLevel: true, | 39 isTopLevel: true, |
26 start: sliceSpec.wallTimeBounds[0], | 40 start: sliceSpec.wallTimeBounds[0], |
27 duration: sliceSpec.wallTimeBounds[1] - sliceSpec.wallTimeBounds[0], | 41 duration: sliceSpec.wallTimeBounds[1] - sliceSpec.wallTimeBounds[0], |
28 cpuStart: sliceSpec.cpuStart, | 42 cpuStart: sliceSpec.cpuStart, |
29 cpuDuration: sliceSpec.cpuDuration, | 43 cpuDuration: sliceSpec.cpuDuration, |
30 })); | 44 })); |
31 } | 45 } |
32 }); | 46 }); |
33 | 47 |
34 const thread = model.getOrCreateProcess(1).getOrCreateThread(1); | 48 const thread = model.getOrCreateProcess(1).getOrCreateThread(1); |
35 const bounds = new tr.b.math.Range.fromExplicitRange(150, 400); | 49 const bounds = new tr.b.math.Range.fromExplicitRange(150, 400); |
36 // 1/2 of first slice + 1/3 of second slice | 50 // 1/2 of first slice + 1/3 of second slice |
37 const expectedCpuTime = 25 + 50; | 51 const expectedCpuTime = 25 + 50; |
38 | 52 |
39 // Should be essentially equal, but choosing a very small epsilon 1e-7 | 53 // Should be essentially equal, but choosing a very small epsilon 1e-7 |
40 // to allow for floating point errors. | 54 // to allow for floating point errors. |
41 assert.closeTo(tr.e.chrome.cpuTime.getCpuTimeForThread(thread, bounds), | 55 assert.closeTo(tr.e.chrome.cpuTime.getCpuTimeForThread(thread, bounds), |
42 expectedCpuTime, 1e-7); | 56 expectedCpuTime, 1e-7); |
43 }); | 57 }); |
| 58 |
| 59 test('getStageToInitiatorToSegmentBounds', () => { |
| 60 const model = tr.c.TestUtils.newModel(function(model) { |
| 61 const thread = model.getOrCreateProcess(1).getOrCreateThread(1); |
| 62 // This is needed for a model to have segments. |
| 63 thread.name = 'CrBrowserMain'; |
| 64 |
| 65 model.userModel.expectations.push(new tr.model.um.AnimationExpectation( |
| 66 model, INITIATOR_TYPE.CSS, |
| 67 100, // start time. |
| 68 300 // duration. |
| 69 )); |
| 70 model.userModel.expectations.push(new tr.model.um.AnimationExpectation( |
| 71 model, INITIATOR_TYPE.VIDEO, 300, 100)); |
| 72 model.userModel.expectations.push(new tr.model.um.ResponseExpectation( |
| 73 model, INITIATOR_TYPE.SCROLL, 400, 200)); |
| 74 }); |
| 75 |
| 76 const segments = model.userModel.segments; |
| 77 |
| 78 const map = getStageToInitiatorToSegmentBounds( |
| 79 model.userModel.segments, model.bounds); |
| 80 |
| 81 // Ignoring Idle Expectations, we have the following segments: |
| 82 // [100, 300]: CSS Animation |
| 83 // [300, 400]: CSS Animation, Video Animation |
| 84 // [400, 600]: Scroll Response |
| 85 const allSegments = [...map.get('all_stages').get('all_initiators')]; |
| 86 assert.sameDeepMembers( |
| 87 allSegments.map(s => [s.min, s.max]), |
| 88 [[100, 300], [300, 400], [400, 600]] |
| 89 ); |
| 90 |
| 91 const videoAnimationSegments = |
| 92 [...map.get('Animation').get(INITIATOR_TYPE.VIDEO)]; |
| 93 assert.sameDeepMembers( |
| 94 videoAnimationSegments.map(s => [s.min, s.max]), |
| 95 [[300, 400]]); |
| 96 |
| 97 const cssAnimationSegments = |
| 98 [...map.get('Animation').get(INITIATOR_TYPE.CSS)]; |
| 99 assert.sameDeepMembers( |
| 100 cssAnimationSegments.map(s => [s.min, s.max]), |
| 101 [[100, 300], [300, 400]]); |
| 102 |
| 103 const allAnimationSegments = |
| 104 [...map.get('Animation').get('all_initiators')]; |
| 105 assert.sameDeepMembers( |
| 106 allAnimationSegments.map(s => [s.min, s.max]), |
| 107 [[100, 300], [300, 400]]); |
| 108 |
| 109 const scrollResponseSegments = |
| 110 [...map.get('Response').get(INITIATOR_TYPE.SCROLL)]; |
| 111 assert.sameDeepMembers( |
| 112 scrollResponseSegments.map(s => [s.min, s.max]), |
| 113 [[400, 600]]); |
| 114 |
| 115 const allResponseSegments = |
| 116 [...map.get('Response').get('all_initiators')]; |
| 117 assert.sameDeepMembers( |
| 118 allResponseSegments.map(s => [s.min, s.max]), |
| 119 [[400, 600]]); |
| 120 }); |
| 121 |
| 122 test('getStageToInitiatorToSegmentBounds-rangeOfInterest', () => { |
| 123 const model = tr.c.TestUtils.newModel(function(model) { |
| 124 const thread = model.getOrCreateProcess(1).getOrCreateThread(1); |
| 125 // This is needed for a model to have segments. |
| 126 thread.name = 'CrBrowserMain'; |
| 127 |
| 128 model.userModel.expectations.push(new tr.model.um.AnimationExpectation( |
| 129 model, INITIATOR_TYPE.CSS, |
| 130 100, // start time. |
| 131 300 // duration. |
| 132 )); |
| 133 model.userModel.expectations.push(new tr.model.um.AnimationExpectation( |
| 134 model, INITIATOR_TYPE.VIDEO, 300, 100)); |
| 135 model.userModel.expectations.push(new tr.model.um.ResponseExpectation( |
| 136 model, INITIATOR_TYPE.SCROLL, 400, 200)); |
| 137 }); |
| 138 |
| 139 const segments = model.userModel.segments; |
| 140 |
| 141 const map = getStageToInitiatorToSegmentBounds(model.userModel.segments, |
| 142 tr.b.math.Range.fromExplicitRange(150, 350)); |
| 143 |
| 144 // Ignoring Idle Expectations, we have the following segments in range: |
| 145 // [150, 300]: CSS Animation |
| 146 // [300, 350]: CSS Animation, Video Animation |
| 147 const allSegments = [...map.get('all_stages').get('all_initiators')]; |
| 148 assert.sameDeepMembers( |
| 149 allSegments.map(s => [s.min, s.max]), |
| 150 [[150, 300], [300, 350]] |
| 151 ); |
| 152 |
| 153 const videoAnimationSegments = |
| 154 [...map.get('Animation').get(INITIATOR_TYPE.VIDEO)]; |
| 155 assert.sameDeepMembers( |
| 156 videoAnimationSegments.map(s => [s.min, s.max]), |
| 157 [[300, 350]]); |
| 158 |
| 159 const cssAnimationSegments = |
| 160 [...map.get('Animation').get(INITIATOR_TYPE.CSS)]; |
| 161 assert.sameDeepMembers( |
| 162 cssAnimationSegments.map(s => [s.min, s.max]), |
| 163 [[150, 300], [300, 350]]); |
| 164 |
| 165 const allAnimationSegments = |
| 166 [...map.get('Animation').get('all_initiators')]; |
| 167 assert.sameDeepMembers( |
| 168 allAnimationSegments.map(s => [s.min, s.max]), |
| 169 [[150, 300], [300, 350]]); |
| 170 |
| 171 // There should be no Response segments |
| 172 assert.isFalse(map.has('Response')); |
| 173 }); |
| 174 |
| 175 /** |
| 176 * Given the root node of a top down multidimensional tree view, and returns |
| 177 * the node at |path|. |
| 178 */ |
| 179 function getNodeValues_(root, path) { |
| 180 let node = root; |
| 181 for (let i = 0; i <= 2; i++) { |
| 182 for (const component of path[i]) { |
| 183 node = node.children[i].get(component); |
| 184 } |
| 185 } |
| 186 return node.values; |
| 187 } |
| 188 |
| 189 const getCpuUsage_ = nodeValues => nodeValues[0].total; |
| 190 const getCpuTotal_ = nodeValues => nodeValues[1].total; |
| 191 |
| 192 /** |
| 193 * Returns a simple model spec with one process (browser process) and one |
| 194 * thread (CrBrowserMain). |
| 195 * |
| 196 * It does not contain any slices or expectations - those should be added |
| 197 * manually. |
| 198 * |
| 199 * This is a function instead of just a variable because the test functions |
| 200 * are meant to modify this modelSpec and insert suitable expectations. |
| 201 */ |
| 202 function getSimpleModelSpec_() { |
| 203 return { |
| 204 processes: [ |
| 205 { |
| 206 name: 'Browser', |
| 207 pid: 12345, |
| 208 threads: [ |
| 209 { |
| 210 name: 'CrBrowserMain', |
| 211 tid: 1, |
| 212 slices: [] |
| 213 }, |
| 214 ], |
| 215 }, |
| 216 ], |
| 217 expectations: [], |
| 218 }; |
| 219 } |
| 220 |
| 221 test('constructMultiDimensionalView_' + |
| 222 'slicesDoNotStraddleExpecationBoundaries', () => { |
| 223 const simpleModelSpec = getSimpleModelSpec_(); |
| 224 simpleModelSpec.processes[0].threads[0].slices.push( |
| 225 {range: [150, 200], cpu: 30}, |
| 226 {range: [205, 255], cpu: 20} |
| 227 ); |
| 228 simpleModelSpec.expectations.push( |
| 229 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, range: [100, 300]} |
| 230 ); |
| 231 |
| 232 const path = [ |
| 233 [CHROME_PROCESS_NAMES.BROWSER], |
| 234 ['CrBrowserMain'], ['Animation', 'CSS']]; |
| 235 |
| 236 const model = buildModelFromSpec(simpleModelSpec); |
| 237 const root = constructMultiDimensionalView(model, model.bounds); |
| 238 const values = getNodeValues_(root, path); |
| 239 |
| 240 assert.closeTo(getCpuUsage_(values), (30 + 20) / 200, 1e-7); |
| 241 assert.closeTo(getCpuTotal_(values), (30 + 20), 1e-7); |
| 242 }); |
| 243 |
| 244 test('constructMultiDimensionalView_' + |
| 245 'slicesStraddleExpectationBoundaries', () => { |
| 246 const simpleModelSpec = getSimpleModelSpec_(); |
| 247 simpleModelSpec.processes[0].threads[0].slices.push( |
| 248 {range: [150, 200], cpu: 30}, |
| 249 {range: [205, 255], cpu: 20} |
| 250 ); |
| 251 simpleModelSpec.expectations.push( |
| 252 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 253 range: [75, 175]} |
| 254 ); |
| 255 const path = [ |
| 256 [CHROME_PROCESS_NAMES.BROWSER], |
| 257 ['CrBrowserMain'], ['Animation', 'Video'] |
| 258 ]; |
| 259 |
| 260 const model = buildModelFromSpec(simpleModelSpec); |
| 261 const root = constructMultiDimensionalView(model, model.bounds); |
| 262 const values = getNodeValues_(root, path); |
| 263 |
| 264 assert.closeTo(getCpuUsage_(values), 15 / 100, 1e-7); |
| 265 assert.closeTo(getCpuTotal_(values), 15, 1e-7); |
| 266 }); |
| 267 |
| 268 test('constructMultiDimensionalView_' + |
| 269 'singleThread-disjointExpectationsOfSameInitiator', () => { |
| 270 const simpleModelSpec = getSimpleModelSpec_(); |
| 271 simpleModelSpec.processes[0].threads[0].slices.push( |
| 272 {range: [150, 200], cpu: 30}, |
| 273 {range: [205, 255], cpu: 20} |
| 274 ); |
| 275 simpleModelSpec.expectations.push( |
| 276 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 277 range: [100, 160]}, |
| 278 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 279 range: [205, 225]} |
| 280 ); |
| 281 const path = [ |
| 282 [CHROME_PROCESS_NAMES.BROWSER], |
| 283 ['CrBrowserMain'], ['Response', 'Scroll'] |
| 284 ]; |
| 285 |
| 286 const model = buildModelFromSpec(simpleModelSpec); |
| 287 const root = constructMultiDimensionalView(model, model.bounds); |
| 288 const values = getNodeValues_(root, path); |
| 289 |
| 290 assert.closeTo(getCpuUsage_(values), |
| 291 (0.2 * 30 + 0.4 * 20) / (60 + 20), 1e-7); |
| 292 assert.closeTo(getCpuTotal_(values), |
| 293 0.2 * 30 + 0.4 * 20, 1e-7); |
| 294 }); |
| 295 |
| 296 test('constructMultiDimensionalView_' + |
| 297 'singleThread-overlappingExpectationsOfSameInitiators', () => { |
| 298 const simpleModelSpec = getSimpleModelSpec_(); |
| 299 simpleModelSpec.processes[0].threads[0].slices.push( |
| 300 {range: [150, 200], cpu: 30}, |
| 301 {range: [205, 255], cpu: 20} |
| 302 ); |
| 303 simpleModelSpec.expectations.push( |
| 304 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 305 range: [100, 190]}, |
| 306 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 307 range: [160, 230]} |
| 308 ); |
| 309 const path = [ |
| 310 [CHROME_PROCESS_NAMES.BROWSER], |
| 311 ['CrBrowserMain'], ['Animation', 'CSS'] |
| 312 ]; |
| 313 |
| 314 const model = buildModelFromSpec(simpleModelSpec); |
| 315 const root = constructMultiDimensionalView(model, model.bounds); |
| 316 const values = getNodeValues_(root, path); |
| 317 |
| 318 assert.closeTo(getCpuUsage_(values), (30 + 0.5 * 20) / 130, 1e-7); |
| 319 assert.closeTo(getCpuTotal_(values), 30 + 0.5 * 20, 1e-7); |
| 320 }); |
| 321 |
| 322 test('constructMultiDimensionalView_' + |
| 323 'singleThread-overlappingExpectationsOfDifferentInitiators', () => { |
| 324 const simpleModelSpec = getSimpleModelSpec_(); |
| 325 simpleModelSpec.processes[0].threads[0].slices.push( |
| 326 {range: [150, 200], cpu: 30}, |
| 327 {range: [205, 255], cpu: 20} |
| 328 ); |
| 329 simpleModelSpec.expectations.push( |
| 330 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 331 range: [100, 190]}, |
| 332 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 333 range: [160, 230]} |
| 334 ); |
| 335 const path = [ |
| 336 [CHROME_PROCESS_NAMES.BROWSER], |
| 337 ['CrBrowserMain'], ['Animation', 'all_initiators'] |
| 338 ]; |
| 339 |
| 340 const model = buildModelFromSpec(simpleModelSpec); |
| 341 const root = constructMultiDimensionalView(model, model.bounds); |
| 342 const values = getNodeValues_(root, path); |
| 343 |
| 344 assert.closeTo(getCpuUsage_(values), |
| 345 (30 + 0.5 * 20) / 130, 1e-7); |
| 346 assert.closeTo(getCpuTotal_(values), |
| 347 30 + 0.5 * 20, 1e-7); |
| 348 }); |
| 349 |
| 350 test('constructMultiDimensionalView_' + |
| 351 'singleThread-allStages-customRangeOfInterest', () => { |
| 352 const simpleModelSpec = getSimpleModelSpec_(); |
| 353 simpleModelSpec.processes[0].threads[0].slices.push( |
| 354 {range: [150, 200], cpu: 30}, |
| 355 {range: [205, 255], cpu: 20} |
| 356 ); |
| 357 simpleModelSpec.expectations.push( |
| 358 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 359 range: [100, 190]}, |
| 360 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 361 range: [160, 230]} |
| 362 ); |
| 363 const path = [ |
| 364 [CHROME_PROCESS_NAMES.BROWSER], |
| 365 ['CrBrowserMain'], ['all_stages', 'all_initiators'] |
| 366 ]; |
| 367 |
| 368 const model = buildModelFromSpec(simpleModelSpec); |
| 369 const rangeOfInterest = new tr.b.math.Range.fromExplicitRange(100, 210); |
| 370 const root = constructMultiDimensionalView(model, rangeOfInterest); |
| 371 const values = getNodeValues_(root, path); |
| 372 |
| 373 assert.closeTo(getCpuUsage_(values), (30 + 0.1 * 20) / 110, 1e-7); |
| 374 assert.closeTo(getCpuTotal_(values), 30 + 0.1 * 20, 1e-7); |
| 375 }); |
| 376 |
| 377 /** |
| 378 * Returns a model spec where the browser process has two worker threads. |
| 379 * |
| 380 * This is a function instead of just a variable because the test functions |
| 381 * are meant to modify this modelSpec and insert suitable expectations. |
| 382 */ |
| 383 function getMultipleThreadsOfSameTypeModelSpec_() { |
| 384 return { |
| 385 processes: [ |
| 386 { |
| 387 name: 'Browser', |
| 388 pid: 12345, |
| 389 threads: [ |
| 390 { |
| 391 name: 'CrBrowserMain', |
| 392 tid: 1, |
| 393 slices: [], |
| 394 }, |
| 395 { |
| 396 name: 'Worker/1', |
| 397 tid: 42, |
| 398 slices: (() => { |
| 399 const slices = []; |
| 400 for (let i = 0; i < 1000; i += 20) { |
| 401 slices.push({range: [i, i + 10], cpu: 5}); |
| 402 } |
| 403 return slices; |
| 404 })(), |
| 405 }, |
| 406 { |
| 407 name: 'Worker/2', |
| 408 tid: 52, |
| 409 slices: (() => { |
| 410 const slices = []; |
| 411 for (let i = 0; i < 1000; i += 100) { |
| 412 slices.push({range: [i, i + 80], cpu: 40}); |
| 413 } |
| 414 return slices; |
| 415 })(), |
| 416 }, |
| 417 ], |
| 418 }, |
| 419 ], |
| 420 |
| 421 expectations: [], |
| 422 }; |
| 423 } |
| 424 |
| 425 test('constructMultiDimensionalView_multipleThreadsOfSameType_' + |
| 426 'singleExpectation', () => { |
| 427 const modelSpec = getMultipleThreadsOfSameTypeModelSpec_(); |
| 428 modelSpec.expectations.push( |
| 429 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 430 range: [0, 90]} |
| 431 ); |
| 432 |
| 433 const path = [ |
| 434 [CHROME_PROCESS_NAMES.BROWSER], |
| 435 ['Worker'], ['Animation', 'Video'] |
| 436 ]; |
| 437 |
| 438 const model = buildModelFromSpec(modelSpec); |
| 439 const root = constructMultiDimensionalView(model, model.bounds); |
| 440 const values = getNodeValues_(root, path); |
| 441 |
| 442 assert.closeTo(getCpuUsage_(values), (5 * 5 + 40) / 90, 1e-7); |
| 443 assert.closeTo(getCpuTotal_(values), 5 * 5 + 40, 1e-7); |
| 444 }); |
| 445 |
| 446 test('constructMultiDimensionalView_multipleThreadsOfSameType_' + |
| 447 'disjointExpectationSameInitiator', () => { |
| 448 const modelSpec = getMultipleThreadsOfSameTypeModelSpec_(); |
| 449 modelSpec.expectations.push( |
| 450 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 451 range: [500, 560]}, |
| 452 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 453 range: [690, 890]} |
| 454 ); |
| 455 |
| 456 const path = [ |
| 457 [CHROME_PROCESS_NAMES.BROWSER], |
| 458 ['Worker'], ['Animation', 'CSS'] |
| 459 ]; |
| 460 |
| 461 const model = buildModelFromSpec(modelSpec); |
| 462 const root = constructMultiDimensionalView(model, model.bounds); |
| 463 const values = getNodeValues_(root, path); |
| 464 |
| 465 assert.closeTo(getCpuUsage_(values), |
| 466 ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)) / (60 + 200), 1e-7); |
| 467 assert.closeTo(getCpuTotal_(values), |
| 468 (5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2), 1e-7); |
| 469 }); |
| 470 |
| 471 test('constructMultiDimensionalView_multipleThreadsOfSameType_' + |
| 472 'overlappingExpectationsOfSameInitiator', () => { |
| 473 const modelSpec = getMultipleThreadsOfSameTypeModelSpec_(); |
| 474 // [Scroll R] |
| 475 // [Scroll R] |
| 476 modelSpec.expectations.push( |
| 477 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 478 range: [210, 260]}, |
| 479 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 480 range: [250, 300]} |
| 481 ); |
| 482 |
| 483 const path = [ |
| 484 [CHROME_PROCESS_NAMES.BROWSER], |
| 485 ['Worker'], ['Response', 'Scroll'] |
| 486 ]; |
| 487 |
| 488 const model = buildModelFromSpec(modelSpec); |
| 489 const root = constructMultiDimensionalView(model, model.bounds); |
| 490 const values = getNodeValues_(root, path); |
| 491 |
| 492 assert.closeTo(getCpuUsage_(values), |
| 493 (5 * 4 + 40 * (70 / 80)) / 90, 1e-7); |
| 494 assert.closeTo(getCpuTotal_(values), |
| 495 5 * 4 + 40 * (70 / 80), 1e-7); |
| 496 }); |
| 497 |
| 498 test('constructMultiDimensionalView_multipleThreadsOfSameType_' + |
| 499 'disjointExpectationsAllInitiators', () => { |
| 500 const modelSpec = getMultipleThreadsOfSameTypeModelSpec_(); |
| 501 modelSpec.expectations.push( |
| 502 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 503 range: [0, 90]}, |
| 504 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 505 range: [500, 560]}, |
| 506 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 507 range: [690, 890]} |
| 508 ); |
| 509 |
| 510 const path = [ |
| 511 [CHROME_PROCESS_NAMES.BROWSER], |
| 512 ['Worker'], ['Animation', 'all_initiators'] |
| 513 ]; |
| 514 |
| 515 const model = buildModelFromSpec(modelSpec); |
| 516 const root = constructMultiDimensionalView(model, model.bounds); |
| 517 const values = getNodeValues_(root, path); |
| 518 |
| 519 assert.closeTo(getCpuUsage_(values), |
| 520 ((5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2))) / |
| 521 (90 + 60 + 200), 1e-7); |
| 522 assert.closeTo(getCpuTotal_(values), |
| 523 (5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)), |
| 524 1e-7); |
| 525 }); |
| 526 |
| 527 |
| 528 test('constructMultiDimensionalView_multipleThreadsOfSameType_' + |
| 529 'overlappingExpectationsAllInitiators', () => { |
| 530 const modelSpec = getMultipleThreadsOfSameTypeModelSpec_(); |
| 531 // [Click R] |
| 532 // [Scroll R] |
| 533 // [Scroll R] |
| 534 modelSpec.expectations.push( |
| 535 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 536 range: [200, 220]}, |
| 537 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 538 range: [210, 260]}, |
| 539 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 540 range: [250, 300]} |
| 541 ); |
| 542 |
| 543 const path = [ |
| 544 [CHROME_PROCESS_NAMES.BROWSER], |
| 545 ['Worker'], ['Response', 'all_initiators'] |
| 546 ]; |
| 547 |
| 548 const model = buildModelFromSpec(modelSpec); |
| 549 const root = constructMultiDimensionalView(model, model.bounds); |
| 550 const values = getNodeValues_(root, path); |
| 551 |
| 552 assert.closeTo(getCpuUsage_(values), |
| 553 (5 * 5 + 40) / 100, 1e-7); |
| 554 assert.closeTo(getCpuTotal_(values), |
| 555 5 * 5 + 40, 1e-7); |
| 556 }); |
| 557 |
| 558 |
| 559 test('constructMultiDimensionalView_multipleThreadsOfSameType_' + |
| 560 'allStagesAllInitiators', () => { |
| 561 const modelSpec = getMultipleThreadsOfSameTypeModelSpec_(); |
| 562 // [Video A] [Click R] [CSS A] [ CSS A ] |
| 563 // [Scroll R] |
| 564 // [Scroll R] |
| 565 modelSpec.expectations.push( |
| 566 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 567 range: [0, 90]}, |
| 568 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 569 range: [200, 220]}, |
| 570 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 571 range: [210, 260]}, |
| 572 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 573 range: [250, 300]}, |
| 574 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 575 range: [500, 560]}, |
| 576 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 577 range: [690, 890]} |
| 578 ); |
| 579 |
| 580 const path = [ |
| 581 [CHROME_PROCESS_NAMES.BROWSER], |
| 582 ['Worker'], ['all_stages', 'all_initiators'] |
| 583 ]; |
| 584 |
| 585 const model = buildModelFromSpec(modelSpec); |
| 586 const root = constructMultiDimensionalView(model, model.bounds); |
| 587 const values = getNodeValues_(root, path); |
| 588 |
| 589 assert.closeTo(getCpuUsage_(values), (250 + 400) / 990, 1e-7); |
| 590 assert.closeTo(getCpuTotal_(values), 250 + 400, 1e-7); |
| 591 }); |
| 592 |
| 593 /** |
| 594 * Returns a model spec where there are two renderer processes, each with a |
| 595 * renderer main thread. |
| 596 * |
| 597 * This is a function instead of just a variable because the test functions |
| 598 * are meant to modify this modelSpec and insert suitable expectations. |
| 599 */ |
| 600 function getMultipleProcessesOfSameTypeModelSpec_() { |
| 601 return { |
| 602 processes: [ |
| 603 { |
| 604 name: 'Browser', |
| 605 pid: 12345, |
| 606 threads: [ |
| 607 { |
| 608 name: 'CrBrowserMain', |
| 609 tid: 1, |
| 610 slices: [], |
| 611 }, |
| 612 ], |
| 613 }, |
| 614 { |
| 615 name: 'Renderer', |
| 616 pid: 20001, |
| 617 threads: [ |
| 618 { |
| 619 name: 'CrRendererMain', |
| 620 tid: 42, |
| 621 slices: (() => { |
| 622 const slices = []; |
| 623 for (let i = 0; i < 1000; i += 20) { |
| 624 slices.push({range: [i, i + 10], cpu: 5}); |
| 625 } |
| 626 return slices; |
| 627 })(), |
| 628 }, |
| 629 ], |
| 630 }, |
| 631 { |
| 632 name: 'Renderer', |
| 633 pid: 30001, |
| 634 threads: [ |
| 635 { |
| 636 name: 'CrRendererMain', |
| 637 tid: 52, |
| 638 slices: (() => { |
| 639 const slices = []; |
| 640 for (let i = 0; i < 1000; i += 100) { |
| 641 slices.push({range: [i, i + 80], cpu: 40}); |
| 642 } |
| 643 return slices; |
| 644 })(), |
| 645 }, |
| 646 ] |
| 647 }, |
| 648 ], |
| 649 |
| 650 expectations: [], |
| 651 }; |
| 652 } |
| 653 |
| 654 test('constructMultiDimensionalView_multipleProcessesOfSameType_' + |
| 655 'singleExpectation', () => { |
| 656 const modelSpec = getMultipleProcessesOfSameTypeModelSpec_(); |
| 657 modelSpec.expectations.push( |
| 658 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 659 range: [0, 90]} |
| 660 ); |
| 661 |
| 662 const path = [ |
| 663 [CHROME_PROCESS_NAMES.RENDERER], |
| 664 ['CrRendererMain'], ['Animation', 'Video'] |
| 665 ]; |
| 666 |
| 667 const model = buildModelFromSpec(modelSpec); |
| 668 const root = constructMultiDimensionalView(model, model.bounds); |
| 669 const values = getNodeValues_(root, path); |
| 670 |
| 671 assert.closeTo(getCpuUsage_(values), (5 * 5 + 40) / 90, 1e-7); |
| 672 assert.closeTo(getCpuTotal_(values), 5 * 5 + 40, 1e-7); |
| 673 }); |
| 674 |
| 675 test('constructMultiDimensionalView_multipleProcessesOfSameType_' + |
| 676 'disjointExpectationSameInitiator', () => { |
| 677 const modelSpec = getMultipleProcessesOfSameTypeModelSpec_(); |
| 678 modelSpec.expectations.push( |
| 679 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 680 range: [500, 560]}, |
| 681 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 682 range: [690, 890]} |
| 683 ); |
| 684 |
| 685 const path = [ |
| 686 [CHROME_PROCESS_NAMES.RENDERER], |
| 687 ['CrRendererMain'], ['Animation', 'CSS'] |
| 688 ]; |
| 689 |
| 690 const model = buildModelFromSpec(modelSpec); |
| 691 const root = constructMultiDimensionalView(model, model.bounds); |
| 692 const values = getNodeValues_(root, path); |
| 693 |
| 694 assert.closeTo(getCpuUsage_(values), |
| 695 ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)) / (60 + 200), 1e-7); |
| 696 assert.closeTo(getCpuTotal_(values), |
| 697 (5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2), 1e-7); |
| 698 }); |
| 699 |
| 700 test('constructMultiDimensionalView_multipleProcessesOfSameType_' + |
| 701 'overlappingExpectationsOfSameInitiator', () => { |
| 702 const modelSpec = getMultipleProcessesOfSameTypeModelSpec_(); |
| 703 // [Scroll R] |
| 704 // [Scroll R] |
| 705 modelSpec.expectations.push( |
| 706 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 707 range: [210, 260]}, |
| 708 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 709 range: [250, 300]} |
| 710 ); |
| 711 |
| 712 const path = [ |
| 713 [CHROME_PROCESS_NAMES.RENDERER], |
| 714 ['CrRendererMain'], ['Response', 'Scroll'] |
| 715 ]; |
| 716 |
| 717 const model = buildModelFromSpec(modelSpec); |
| 718 const root = constructMultiDimensionalView(model, model.bounds); |
| 719 const values = getNodeValues_(root, path); |
| 720 |
| 721 assert.closeTo(getCpuUsage_(values), |
| 722 (5 * 4 + 40 * (70 / 80)) / 90, 1e-7); |
| 723 assert.closeTo(getCpuTotal_(values), |
| 724 5 * 4 + 40 * (70 / 80), 1e-7); |
| 725 }); |
| 726 |
| 727 test('constructMultiDimensionalView_multipleProcessesOfSameType_' + |
| 728 'disjointExpectationsAllInitiators', () => { |
| 729 const modelSpec = getMultipleProcessesOfSameTypeModelSpec_(); |
| 730 modelSpec.expectations.push( |
| 731 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 732 range: [0, 90]}, |
| 733 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 734 range: [500, 560]}, |
| 735 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 736 range: [690, 890]} |
| 737 ); |
| 738 |
| 739 const path = [ |
| 740 [CHROME_PROCESS_NAMES.RENDERER], |
| 741 ['CrRendererMain'], ['Animation', 'all_initiators'] |
| 742 ]; |
| 743 |
| 744 const model = buildModelFromSpec(modelSpec); |
| 745 const root = constructMultiDimensionalView(model, model.bounds); |
| 746 const values = getNodeValues_(root, path); |
| 747 |
| 748 assert.closeTo(getCpuUsage_(values), |
| 749 ((5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2))) / |
| 750 (90 + 60 + 200), 1e-7); |
| 751 assert.closeTo(getCpuTotal_(values), |
| 752 (5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)), |
| 753 1e-7); |
| 754 }); |
| 755 |
| 756 |
| 757 test('constructMultiDimensionalView_multipleProcessesOfSameType_' + |
| 758 'overlappingExpectationsAllInitiators', () => { |
| 759 const modelSpec = getMultipleProcessesOfSameTypeModelSpec_(); |
| 760 // [Click R] |
| 761 // [Scroll R] |
| 762 // [Scroll R] |
| 763 modelSpec.expectations.push( |
| 764 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 765 range: [200, 220]}, |
| 766 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 767 range: [210, 260]}, |
| 768 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 769 range: [250, 300]} |
| 770 ); |
| 771 |
| 772 const path = [ |
| 773 [CHROME_PROCESS_NAMES.RENDERER], |
| 774 ['CrRendererMain'], ['Response', 'all_initiators'] |
| 775 ]; |
| 776 |
| 777 const model = buildModelFromSpec(modelSpec); |
| 778 const root = constructMultiDimensionalView(model, model.bounds); |
| 779 const values = getNodeValues_(root, path); |
| 780 |
| 781 assert.closeTo(getCpuUsage_(values), (5 * 5 + 40) / 100, 1e-7); |
| 782 assert.closeTo(getCpuTotal_(values), 5 * 5 + 40, 1e-7); |
| 783 }); |
| 784 |
| 785 |
| 786 test('constructMultiDimensionalView_multipleProcessesOfSameType_' + |
| 787 'allStagesAllInitiators', () => { |
| 788 const modelSpec = getMultipleProcessesOfSameTypeModelSpec_(); |
| 789 // [Video A] [Click R] [CSS A] [ CSS A ] |
| 790 // [Scroll R] |
| 791 // [Scroll R] |
| 792 modelSpec.expectations.push( |
| 793 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 794 range: [0, 90]}, |
| 795 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 796 range: [200, 220]}, |
| 797 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 798 range: [210, 260]}, |
| 799 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 800 range: [250, 300]}, |
| 801 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 802 range: [500, 560]}, |
| 803 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 804 range: [690, 890]} |
| 805 ); |
| 806 |
| 807 const path = [ |
| 808 [CHROME_PROCESS_NAMES.RENDERER], |
| 809 ['CrRendererMain'], ['all_stages', 'all_initiators'] |
| 810 ]; |
| 811 |
| 812 const model = buildModelFromSpec(modelSpec); |
| 813 const root = constructMultiDimensionalView(model, model.bounds); |
| 814 const values = getNodeValues_(root, path); |
| 815 |
| 816 assert.closeTo(getCpuUsage_(values), (250 + 400) / 990, 1e-7); |
| 817 assert.closeTo(getCpuTotal_(values), 250 + 400, 1e-7); |
| 818 }); |
| 819 |
| 820 /** |
| 821 * Returns a model spec where the browser process has a main thread and an IO |
| 822 * thread. |
| 823 * |
| 824 * This is a function instead of just a variable because the test functions |
| 825 * are meant to modify this modelSpec and insert suitable expectations. |
| 826 */ |
| 827 function getAllThreadsOfSameProcessModelSpec_() { |
| 828 return { |
| 829 processes: [ |
| 830 { |
| 831 name: 'Browser', |
| 832 pid: 12345, |
| 833 threads: [ |
| 834 { |
| 835 name: 'CrBrowserMain', |
| 836 tid: 1, |
| 837 slices: (() => { |
| 838 const slices = []; |
| 839 for (let i = 0; i < 1000; i += 20) { |
| 840 slices.push({range: [i, i + 10], cpu: 5}); |
| 841 } |
| 842 return slices; |
| 843 })(), |
| 844 }, |
| 845 { |
| 846 name: 'Chrome_IOThread', |
| 847 tid: 5, |
| 848 slices: (() => { |
| 849 const slices = []; |
| 850 for (let i = 0; i < 1000; i += 100) { |
| 851 slices.push({range: [i, i + 80], cpu: 40}); |
| 852 } |
| 853 return slices; |
| 854 })(), |
| 855 } |
| 856 ], |
| 857 }, |
| 858 ], |
| 859 |
| 860 expectations: [], |
| 861 }; |
| 862 } |
| 863 |
| 864 |
| 865 test('constructMultiDimensionalView_AllThreadsOfSameProcess_' + |
| 866 'singleExpectation', () => { |
| 867 const modelSpec = getAllThreadsOfSameProcessModelSpec_(); |
| 868 modelSpec.expectations.push( |
| 869 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 870 range: [0, 90]} |
| 871 ); |
| 872 |
| 873 const pathForAllThreads = [ |
| 874 [CHROME_PROCESS_NAMES.BROWSER], [], ['Animation', 'Video']]; |
| 875 |
| 876 const pathForThread1 = [ |
| 877 [CHROME_PROCESS_NAMES.BROWSER], |
| 878 ['CrBrowserMain'], ['Animation', 'Video']]; |
| 879 |
| 880 const pathForThread2 = [ |
| 881 [CHROME_PROCESS_NAMES.BROWSER], |
| 882 ['Chrome_IOThread'], ['Animation', 'Video']]; |
| 883 |
| 884 const model = buildModelFromSpec(modelSpec); |
| 885 const root = constructMultiDimensionalView(model, model.bounds); |
| 886 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 887 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 888 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 889 |
| 890 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 891 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 892 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 893 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 894 }); |
| 895 |
| 896 test('constructMultiDimensionalView_AllThreadsOfSameProcess_' + |
| 897 'disjointExpectationSameInitiator', () => { |
| 898 const modelSpec = getAllThreadsOfSameProcessModelSpec_(); |
| 899 modelSpec.expectations.push( |
| 900 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 901 range: [500, 560]}, |
| 902 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 903 range: [690, 890]} |
| 904 ); |
| 905 |
| 906 const pathForAllThreads = [ |
| 907 [CHROME_PROCESS_NAMES.BROWSER], [], ['Animation', 'CSS']]; |
| 908 |
| 909 const pathForThread1 = [ |
| 910 [CHROME_PROCESS_NAMES.BROWSER], |
| 911 ['CrBrowserMain'], ['Animation', 'CSS']]; |
| 912 |
| 913 const pathForThread2 = [ |
| 914 [CHROME_PROCESS_NAMES.BROWSER], |
| 915 ['Chrome_IOThread'], ['Animation', 'CSS']]; |
| 916 |
| 917 const model = buildModelFromSpec(modelSpec); |
| 918 const root = constructMultiDimensionalView(model, model.bounds); |
| 919 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 920 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 921 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 922 |
| 923 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 924 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 925 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 926 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 927 }); |
| 928 |
| 929 test('constructMultiDimensionalView_AllThreadsOfSameProcess_' + |
| 930 'overlappingExpectationsOfSameInitiator', () => { |
| 931 const modelSpec = getAllThreadsOfSameProcessModelSpec_(); |
| 932 // [Scroll R] |
| 933 // [Scroll R] |
| 934 modelSpec.expectations.push( |
| 935 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 936 range: [210, 260]}, |
| 937 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 938 range: [250, 300]} |
| 939 ); |
| 940 |
| 941 const pathForAllThreads = [ |
| 942 [CHROME_PROCESS_NAMES.BROWSER], [], ['Response', 'Scroll']]; |
| 943 |
| 944 const pathForThread1 = [ |
| 945 [CHROME_PROCESS_NAMES.BROWSER], |
| 946 ['CrBrowserMain'], ['Response', 'Scroll']]; |
| 947 |
| 948 const pathForThread2 = [ |
| 949 [CHROME_PROCESS_NAMES.BROWSER], |
| 950 ['Chrome_IOThread'], ['Response', 'Scroll']]; |
| 951 |
| 952 const model = buildModelFromSpec(modelSpec); |
| 953 const root = constructMultiDimensionalView(model, model.bounds); |
| 954 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 955 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 956 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 957 |
| 958 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 959 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 960 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 961 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 962 }); |
| 963 |
| 964 test('constructMultiDimensionalView_AllThreadsOfSameProcess_' + |
| 965 'disjointExpectationsAllInitiators', () => { |
| 966 const modelSpec = getAllThreadsOfSameProcessModelSpec_(); |
| 967 modelSpec.expectations.push( |
| 968 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 969 range: [0, 90]}, |
| 970 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 971 range: [500, 560]}, |
| 972 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 973 range: [690, 890]} |
| 974 ); |
| 975 |
| 976 const pathForAllThreads = [ |
| 977 [CHROME_PROCESS_NAMES.BROWSER], [], ['Animation', 'all_initiators']]; |
| 978 |
| 979 const pathForThread1 = [ |
| 980 [CHROME_PROCESS_NAMES.BROWSER], |
| 981 ['CrBrowserMain'], ['Animation', 'all_initiators']]; |
| 982 |
| 983 const pathForThread2 = [ |
| 984 [CHROME_PROCESS_NAMES.BROWSER], |
| 985 ['Chrome_IOThread'], ['Animation', 'all_initiators']]; |
| 986 |
| 987 const model = buildModelFromSpec(modelSpec); |
| 988 const root = constructMultiDimensionalView(model, model.bounds); |
| 989 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 990 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 991 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 992 |
| 993 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 994 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 995 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 996 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 997 }); |
| 998 |
| 999 test('constructMultiDimensionalView_AllThreadsOfSameProcess_' + |
| 1000 'overlappingExpectationsAllInitiators', () => { |
| 1001 const modelSpec = getAllThreadsOfSameProcessModelSpec_(); |
| 1002 // [Click R] |
| 1003 // [Scroll R] |
| 1004 // [Scroll R] |
| 1005 modelSpec.expectations.push( |
| 1006 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 1007 range: [200, 220]}, |
| 1008 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1009 range: [210, 260]}, |
| 1010 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1011 range: [250, 300]} |
| 1012 ); |
| 1013 |
| 1014 const pathForAllThreads = [ |
| 1015 [CHROME_PROCESS_NAMES.BROWSER], [], ['Response', 'all_initiators']]; |
| 1016 |
| 1017 const pathForThread1 = [ |
| 1018 [CHROME_PROCESS_NAMES.BROWSER], |
| 1019 ['CrBrowserMain'], ['Response', 'all_initiators']]; |
| 1020 |
| 1021 const pathForThread2 = [ |
| 1022 [CHROME_PROCESS_NAMES.BROWSER], |
| 1023 ['Chrome_IOThread'], ['Response', 'all_initiators']]; |
| 1024 |
| 1025 const model = buildModelFromSpec(modelSpec); |
| 1026 const root = constructMultiDimensionalView(model, model.bounds); |
| 1027 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1028 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1029 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1030 |
| 1031 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1032 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1033 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1034 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1035 }); |
| 1036 |
| 1037 test('constructMultiDimensionalView_AllThreadsOfSameProcess_' + |
| 1038 'allStagesAllInitiators', () => { |
| 1039 const modelSpec = getAllThreadsOfSameProcessModelSpec_(); |
| 1040 // [Video A] [Click R] [CSS A] [ CSS A ] |
| 1041 // [Scroll R] |
| 1042 // [Scroll R] |
| 1043 modelSpec.expectations.push( |
| 1044 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 1045 range: [0, 90]}, |
| 1046 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 1047 range: [200, 220]}, |
| 1048 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1049 range: [210, 260]}, |
| 1050 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1051 range: [250, 300]}, |
| 1052 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1053 range: [500, 560]}, |
| 1054 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1055 range: [690, 890]} |
| 1056 ); |
| 1057 |
| 1058 const pathForAllThreads = [ |
| 1059 [CHROME_PROCESS_NAMES.BROWSER], [], ['all_stages', 'all_initiators']]; |
| 1060 |
| 1061 const pathForThread1 = [ |
| 1062 [CHROME_PROCESS_NAMES.BROWSER], |
| 1063 ['CrBrowserMain'], ['all_stages', 'all_initiators']]; |
| 1064 |
| 1065 const pathForThread2 = [ |
| 1066 [CHROME_PROCESS_NAMES.BROWSER], |
| 1067 ['Chrome_IOThread'], ['all_stages', 'all_initiators']]; |
| 1068 |
| 1069 const model = buildModelFromSpec(modelSpec); |
| 1070 const root = constructMultiDimensionalView(model, model.bounds); |
| 1071 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1072 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1073 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1074 |
| 1075 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1076 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1077 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1078 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1079 }); |
| 1080 |
| 1081 /** |
| 1082 * Returns a model spec where a renderer process and a GPU process both have a |
| 1083 * Chrome_ChildIOThread. |
| 1084 * |
| 1085 * This is a function instead of just a variable because the test functions |
| 1086 * are meant to modify this modelSpec and insert suitable expectations. |
| 1087 * |
| 1088 * The modelSpec includes a basic browser process because it not a valid |
| 1089 * chrome model otherwise. |
| 1090 */ |
| 1091 function getAllProcessesOfSameThreadModelSpec_() { |
| 1092 return { |
| 1093 processes: [ |
| 1094 { |
| 1095 name: 'Browser', |
| 1096 pid: 12345, |
| 1097 threads: [ |
| 1098 { |
| 1099 name: 'CrBrowserMain', |
| 1100 tid: 1, |
| 1101 slices: [], |
| 1102 }, |
| 1103 ], |
| 1104 }, |
| 1105 { |
| 1106 name: 'Renderer', |
| 1107 pid: 20001, |
| 1108 threads: [ |
| 1109 { |
| 1110 name: 'Chrome_ChildIOThread', |
| 1111 tid: 42, |
| 1112 slices: (() => { |
| 1113 const slices = []; |
| 1114 for (let i = 0; i < 1000; i += 20) { |
| 1115 slices.push({range: [i, i + 10], cpu: 5}); |
| 1116 } |
| 1117 return slices; |
| 1118 })(), |
| 1119 }, |
| 1120 ], |
| 1121 }, |
| 1122 { |
| 1123 name: 'GPU Process', |
| 1124 pid: 30001, |
| 1125 threads: [ |
| 1126 { |
| 1127 name: 'Chrome_ChildIOThread', |
| 1128 tid: 52, |
| 1129 slices: (() => { |
| 1130 const slices = []; |
| 1131 for (let i = 0; i < 1000; i += 100) { |
| 1132 slices.push({range: [i, i + 80], cpu: 40}); |
| 1133 } |
| 1134 return slices; |
| 1135 })(), |
| 1136 }, |
| 1137 ] |
| 1138 }, |
| 1139 ], |
| 1140 expectations: [], |
| 1141 }; |
| 1142 } |
| 1143 |
| 1144 test('constructMultiDimensionalView_AllProcessesOfSameThread_' + |
| 1145 'singleExpectation', () => { |
| 1146 const modelSpec = getAllProcessesOfSameThreadModelSpec_(); |
| 1147 modelSpec.expectations.push( |
| 1148 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 1149 range: [0, 90]} |
| 1150 ); |
| 1151 |
| 1152 const pathForAllThreads = [ |
| 1153 [], ['Chrome_ChildIOThread'], ['Animation', 'Video']]; |
| 1154 |
| 1155 const pathForThread1 = [ |
| 1156 [CHROME_PROCESS_NAMES.RENDERER], |
| 1157 ['Chrome_ChildIOThread'], ['Animation', 'Video']]; |
| 1158 |
| 1159 const pathForThread2 = [ |
| 1160 [CHROME_PROCESS_NAMES.GPU], |
| 1161 ['Chrome_ChildIOThread'], ['Animation', 'Video']]; |
| 1162 |
| 1163 const model = buildModelFromSpec(modelSpec); |
| 1164 const root = constructMultiDimensionalView(model, model.bounds); |
| 1165 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1166 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1167 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1168 |
| 1169 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1170 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1171 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1172 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1173 }); |
| 1174 |
| 1175 test('constructMultiDimensionalView_AllProcessesOfSameThread_' + |
| 1176 'disjointExpectationSameInitiator', () => { |
| 1177 const modelSpec = getAllProcessesOfSameThreadModelSpec_(); |
| 1178 modelSpec.expectations.push( |
| 1179 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1180 range: [500, 560]}, |
| 1181 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1182 range: [690, 890]} |
| 1183 ); |
| 1184 |
| 1185 const pathForAllThreads = [ |
| 1186 [], ['Chrome_ChildIOThread'], ['Animation', 'CSS']]; |
| 1187 |
| 1188 const pathForThread1 = [ |
| 1189 [CHROME_PROCESS_NAMES.RENDERER], |
| 1190 ['Chrome_ChildIOThread'], ['Animation', 'CSS']]; |
| 1191 |
| 1192 const pathForThread2 = [ |
| 1193 [CHROME_PROCESS_NAMES.GPU], |
| 1194 ['Chrome_ChildIOThread'], ['Animation', 'CSS']]; |
| 1195 |
| 1196 const model = buildModelFromSpec(modelSpec); |
| 1197 const root = constructMultiDimensionalView(model, model.bounds); |
| 1198 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1199 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1200 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1201 |
| 1202 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1203 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1204 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1205 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1206 }); |
| 1207 |
| 1208 test('constructMultiDimensionalView_AllProcessesOfSameThread_' + |
| 1209 'overlappingExpectationsOfSameInitiator', () => { |
| 1210 const modelSpec = getAllProcessesOfSameThreadModelSpec_(); |
| 1211 // [Scroll R] |
| 1212 // [Scroll R] |
| 1213 modelSpec.expectations.push( |
| 1214 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1215 range: [210, 260]}, |
| 1216 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1217 range: [250, 300]} |
| 1218 ); |
| 1219 |
| 1220 const pathForAllThreads = [ |
| 1221 [], ['Chrome_ChildIOThread'], ['Response', 'Scroll']]; |
| 1222 |
| 1223 const pathForThread1 = [ |
| 1224 [CHROME_PROCESS_NAMES.RENDERER], |
| 1225 ['Chrome_ChildIOThread'], ['Response', 'Scroll']]; |
| 1226 |
| 1227 const pathForThread2 = [ |
| 1228 [CHROME_PROCESS_NAMES.GPU], |
| 1229 ['Chrome_ChildIOThread'], ['Response', 'Scroll']]; |
| 1230 |
| 1231 const model = buildModelFromSpec(modelSpec); |
| 1232 const root = constructMultiDimensionalView(model, model.bounds); |
| 1233 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1234 |
| 1235 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1236 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1237 |
| 1238 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1239 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1240 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1241 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1242 }); |
| 1243 |
| 1244 test('constructMultiDimensionalView_AllProcessesOfSameThread_' + |
| 1245 'disjointExpectationsAllInitiators', () => { |
| 1246 const modelSpec = getAllProcessesOfSameThreadModelSpec_(); |
| 1247 modelSpec.expectations.push( |
| 1248 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 1249 range: [0, 90]}, |
| 1250 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1251 range: [500, 560]}, |
| 1252 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1253 range: [690, 890]} |
| 1254 ); |
| 1255 |
| 1256 const pathForAllThreads = [ |
| 1257 [], ['Chrome_ChildIOThread'], ['Animation', 'all_initiators']]; |
| 1258 |
| 1259 const pathForThread1 = [ |
| 1260 [CHROME_PROCESS_NAMES.RENDERER], |
| 1261 ['Chrome_ChildIOThread'], ['Animation', 'all_initiators']]; |
| 1262 |
| 1263 const pathForThread2 = [ |
| 1264 [CHROME_PROCESS_NAMES.GPU], |
| 1265 ['Chrome_ChildIOThread'], ['Animation', 'all_initiators']]; |
| 1266 |
| 1267 const model = buildModelFromSpec(modelSpec); |
| 1268 const root = constructMultiDimensionalView(model, model.bounds); |
| 1269 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1270 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1271 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1272 |
| 1273 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1274 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1275 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1276 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1277 }); |
| 1278 |
| 1279 test('constructMultiDimensionalView_AllProcessesOfSameThread_' + |
| 1280 'overlappingExpectationsAllInitiators', () => { |
| 1281 const modelSpec = getAllProcessesOfSameThreadModelSpec_(); |
| 1282 // [Click R] |
| 1283 // [Scroll R] |
| 1284 // [Scroll R] |
| 1285 modelSpec.expectations.push( |
| 1286 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 1287 range: [200, 220]}, |
| 1288 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1289 range: [210, 260]}, |
| 1290 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1291 range: [250, 300]} |
| 1292 ); |
| 1293 |
| 1294 const pathForAllThreads = [ |
| 1295 [], ['Chrome_ChildIOThread'], ['Response', 'all_initiators']]; |
| 1296 |
| 1297 const pathForThread1 = [ |
| 1298 [CHROME_PROCESS_NAMES.RENDERER], |
| 1299 ['Chrome_ChildIOThread'], ['Response', 'all_initiators']]; |
| 1300 |
| 1301 const pathForThread2 = [ |
| 1302 [CHROME_PROCESS_NAMES.GPU], |
| 1303 ['Chrome_ChildIOThread'], ['Response', 'all_initiators']]; |
| 1304 |
| 1305 const model = buildModelFromSpec(modelSpec); |
| 1306 const root = constructMultiDimensionalView(model, model.bounds); |
| 1307 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1308 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1309 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1310 |
| 1311 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1312 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1313 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1314 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1315 }); |
| 1316 |
| 1317 test('constructMultiDimensionalView_AllProcessesOfSameThread_' + |
| 1318 'allStagesAllInitiators', () => { |
| 1319 const modelSpec = getAllProcessesOfSameThreadModelSpec_(); |
| 1320 // [Video A] [Click R] [CSS A] [ CSS A ] |
| 1321 // [Scroll R] |
| 1322 // [Scroll R] |
| 1323 modelSpec.expectations.push( |
| 1324 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 1325 range: [0, 90]}, |
| 1326 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 1327 range: [200, 220]}, |
| 1328 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1329 range: [210, 260]}, |
| 1330 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1331 range: [250, 300]}, |
| 1332 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1333 range: [500, 560]}, |
| 1334 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1335 range: [690, 890]} |
| 1336 ); |
| 1337 |
| 1338 const pathForAllThreads = [ |
| 1339 [], ['Chrome_ChildIOThread'], ['all_stages', 'all_initiators']]; |
| 1340 |
| 1341 const pathForThread1 = [ |
| 1342 [CHROME_PROCESS_NAMES.RENDERER], |
| 1343 ['Chrome_ChildIOThread'], ['all_stages', 'all_initiators']]; |
| 1344 |
| 1345 const pathForThread2 = [ |
| 1346 [CHROME_PROCESS_NAMES.GPU], |
| 1347 ['Chrome_ChildIOThread'], ['all_stages', 'all_initiators']]; |
| 1348 |
| 1349 const model = buildModelFromSpec(modelSpec); |
| 1350 const root = constructMultiDimensionalView(model, model.bounds); |
| 1351 const valueForAllThreads = getNodeValues_(root, pathForAllThreads); |
| 1352 const valueForThread1 = getNodeValues_(root, pathForThread1); |
| 1353 const valueForThread2 = getNodeValues_(root, pathForThread2); |
| 1354 |
| 1355 assert.closeTo(getCpuUsage_(valueForAllThreads), |
| 1356 getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7); |
| 1357 assert.closeTo(getCpuTotal_(valueForAllThreads), |
| 1358 getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7); |
| 1359 }); |
| 1360 |
| 1361 test('constructMultiDimensionalView_completeAggregation', () => { |
| 1362 const modelSpec = { |
| 1363 processes: [ |
| 1364 { |
| 1365 name: 'Browser', |
| 1366 pid: 12345, |
| 1367 threads: [ |
| 1368 { |
| 1369 name: 'CrBrowserMain', |
| 1370 tid: 1, |
| 1371 slices: (() => { |
| 1372 const slices = []; |
| 1373 for (let i = 0; i < 1000; i += 50) { |
| 1374 slices.push({range: [i, i + 50], cpu: 30}); |
| 1375 } |
| 1376 return slices; |
| 1377 })(), |
| 1378 }, |
| 1379 ], |
| 1380 }, |
| 1381 { |
| 1382 name: 'Renderer', |
| 1383 pid: 20001, |
| 1384 threads: [ |
| 1385 { |
| 1386 name: 'Chrome_ChildIOThread', |
| 1387 tid: 42, |
| 1388 slices: (() => { |
| 1389 const slices = []; |
| 1390 for (let i = 0; i < 1000; i += 20) { |
| 1391 slices.push({range: [i, i + 10], cpu: 5}); |
| 1392 } |
| 1393 return slices; |
| 1394 })(), |
| 1395 }, |
| 1396 ], |
| 1397 }, |
| 1398 { |
| 1399 name: 'GPU Process', |
| 1400 pid: 30001, |
| 1401 threads: [ |
| 1402 { |
| 1403 name: 'Chrome_ChildIOThread', |
| 1404 tid: 52, |
| 1405 slices: (() => { |
| 1406 const slices = []; |
| 1407 for (let i = 0; i < 1000; i += 100) { |
| 1408 slices.push({range: [i, i + 80], cpu: 40}); |
| 1409 } |
| 1410 return slices; |
| 1411 })(), |
| 1412 }, |
| 1413 ] |
| 1414 }, |
| 1415 ], |
| 1416 |
| 1417 // [Video A] [Click R] [CSS A] [ CSS A ] |
| 1418 // [Scroll R] |
| 1419 // [Scroll R] |
| 1420 expectations: [ |
| 1421 {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO, |
| 1422 range: [0, 90]}, |
| 1423 {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK, |
| 1424 range: [200, 220]}, |
| 1425 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1426 range: [210, 260]}, |
| 1427 {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL, |
| 1428 range: [250, 300]}, |
| 1429 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1430 range: [500, 560]}, |
| 1431 {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS, |
| 1432 range: [690, 890]} |
| 1433 ], |
| 1434 }; |
| 1435 |
| 1436 const model = buildModelFromSpec(modelSpec); |
| 1437 const path = [[], [], ['all_stages'], ['all_initiators']]; |
| 1438 const root = constructMultiDimensionalView(model, model.bounds); |
| 1439 const values = getNodeValues_(root, path); |
| 1440 |
| 1441 assert.closeTo(getCpuUsage_(values), |
| 1442 (30 * 20 + 5 * 50 + 40 * 10) / 1000, 1e-7); |
| 1443 assert.closeTo(getCpuTotal_(values), |
| 1444 (30 * 20 + 5 * 50 + 40 * 10), 1e-7); |
| 1445 }); |
44 }); | 1446 }); |
| 1447 |
45 </script> | 1448 </script> |
OLD | NEW |