@@ -2,12 +2,92 @@ var webdriver = require('protractor/node_modules/selenium-webdriver');
22
33module . exports = {
44 perfLogs : perfLogs ,
5- sumTimelineStats : sumTimelineStats ,
5+ sumTimelineRecords : sumTimelineRecords ,
66 runSimpleBenchmark : runSimpleBenchmark ,
77 verifyNoErrors : verifyNoErrors ,
88 printObjectAsMarkdown : printObjectAsMarkdown
99} ;
1010
11+ // TODO: rename into runSimpleBenchmark
12+ function runSimpleBenchmark ( config ) {
13+ // TODO: move this into the tests!
14+ browser . get ( config . url ) ;
15+
16+ var buttons = config . buttons . map ( function ( selector ) {
17+ return $ ( selector ) ;
18+ } ) ;
19+ var globalParams = browser . params ;
20+
21+ // empty perflogs queue and gc
22+ gc ( ) ;
23+ perfLogs ( ) ;
24+ var sampleQueue = [ ] ;
25+ var bestSampleStats = null ;
26+
27+ loop ( globalParams . maxRepeatCount ) . then ( function ( stats ) {
28+ printObjectAsMarkdown ( config . name , stats ) ;
29+ } ) ;
30+
31+ function loop ( count ) {
32+ if ( ! count ) {
33+ return bestSampleStats ;
34+ }
35+ return webdriver . promise . all ( buttons . map ( function ( button ) {
36+ // Note: even though we remove the gc time from the script time,
37+ // we still get a high standard devication if we don't gc after every click...
38+ return button . click ( ) . then ( gc ) ;
39+ } ) ) . then ( function ( ) {
40+ return perfLogs ( ) ;
41+ } ) . then ( function ( logs ) {
42+ var stats = calculateStatsBasedOnLogs ( logs ) ;
43+ if ( stats ) {
44+ if ( stats . script . error < globalParams . exitOnErrorLowerThan ) {
45+ return stats ;
46+ }
47+ if ( ! bestSampleStats || stats . script . error < bestSampleStats . script . error ) {
48+ bestSampleStats = stats ;
49+ }
50+ }
51+ return loop ( count - 1 ) ;
52+ } ) ;
53+ }
54+
55+ function calculateStatsBasedOnLogs ( logs ) {
56+ sampleQueue . push ( sumTimelineRecords ( logs [ 'Timeline.eventRecorded' ] ) ) ;
57+ if ( sampleQueue . length >= globalParams . sampleSize ) {
58+ sampleQueue . splice ( 0 , sampleQueue . length - globalParams . sampleSize ) ;
59+ // TODO: gc numbers don't have much meaning right now,
60+ // as a benchmark run destroys everything.
61+ // We need to measure the heap size after gc as well!
62+ return calculateObjectSampleStats ( sampleQueue , [ 'script' , 'render' , 'gcTime' , 'gcAmount' ] ) ;
63+ }
64+ return null ;
65+ }
66+ }
67+
68+ function gc ( ) {
69+ // TODO(tbosch): this only works on chrome, and we actually should
70+ // extend chromedriver to use the Debugger.CollectGarbage call of the
71+ // remote debugger protocol.
72+ // See http://src.chromium.org/viewvc/blink/trunk/Source/devtools/protocol.json
73+ // For iOS Safari we need an extension to appium that uses
74+ // the webkit remote debug protocol. See
75+ // https://github.com/WebKit/webkit/blob/master/Source/WebInspectorUI/Versions/Inspector-iOS-8.0.json
76+ return browser . executeScript ( 'window.gc()' ) ;
77+ }
78+
79+ function verifyNoErrors ( ) {
80+ browser . manage ( ) . logs ( ) . get ( 'browser' ) . then ( function ( browserLog ) {
81+ var filteredLog = browserLog . filter ( function ( logEntry ) {
82+ return logEntry . level . value > webdriver . logging . Level . WARNING . value ;
83+ } ) ;
84+ expect ( filteredLog . length ) . toEqual ( 0 ) ;
85+ if ( filteredLog . length ) {
86+ console . log ( 'browser console errors: ' + require ( 'util' ) . inspect ( filteredLog ) ) ;
87+ }
88+ } ) ;
89+ }
90+
1191function perfLogs ( ) {
1292 return plainLogs ( 'performance' ) . then ( function ( entries ) {
1393 var entriesByMethod = { } ;
@@ -34,115 +114,56 @@ function plainLogs(type) {
34114} ;
35115
36116
37- function sumTimelineStats ( messages ) {
117+ function sumTimelineRecords ( messages ) {
38118 var recordStats = {
39119 script : 0 ,
40- gc : {
41- time : 0 ,
42- amount : 0
43- } ,
120+ gcTime : 0 ,
121+ gcAmount : 0 ,
44122 render : 0
45123 } ;
46124 messages . forEach ( function ( message ) {
47- sumTimelineRecordStats ( message . record , recordStats ) ;
125+ processRecord ( message . record , recordStats ) ;
48126 } ) ;
49127 return recordStats ;
50- }
51-
52- function sumTimelineRecordStats ( record , result ) {
53- var summedChildrenDuration = 0 ;
54- if ( record . children ) {
55- record . children . forEach ( function ( child ) {
56- summedChildrenDuration += sumTimelineRecordStats ( child , result ) ;
57- } ) ;
58- }
59- // in case a script forced a gc or a reflow
60- // we need to substract the gc time / reflow time
61- // from the script time!
62- var recordDuration = ( record . endTime ? record . endTime - record . startTime : 0 )
63- - summedChildrenDuration ;
64-
65- var recordSummed = true ;
66- if ( record . type === 'FunctionCall' ) {
67- result . script += recordDuration ;
68- } else if ( record . type === 'GCEvent' ) {
69- result . gc . time += recordDuration ;
70- result . gc . amount += record . data . usedHeapSizeDelta ;
71- } else if ( record . type === 'RecalculateStyles' ||
72- record . type === 'Layout' ||
73- record . type === 'UpdateLayerTree' ||
74- record . type === 'Paint' ||
75- record . type === 'Rasterize' ||
76- record . type === 'CompositeLayers' ) {
77- result . render += recordDuration ;
78- } else {
79- recordSummed = false ;
80- }
81- if ( recordSummed ) {
82- return recordDuration ;
83- } else {
84- return summedChildrenDuration ;
85- }
86- }
87-
88- function runSimpleBenchmark ( config ) {
89- var url = config . url ;
90- var buttonSelectors = config . buttons ;
91- // TODO: Don't use a fixed number of warmup / measure iterations,
92- // but make this dependent on the variance of the test results!
93- var warmupCount = browser . params . warmupCount ;
94- var measureCount = browser . params . measureCount ;
95- var name = config . name ;
96-
97- browser . get ( url ) ;
98- // TODO(tbosch): replace this with a proper protractor/ng2.0 integration
99- // and remove this function as well as all method calls.
100- browser . sleep ( browser . params . sleepInterval )
101-
102- var btns = buttonSelectors . map ( function ( selector ) {
103- return $ ( selector ) ;
104- } ) ;
105128
106- multiClick ( btns , warmupCount ) ;
107- gc ( ) ;
108- // empty perflogs queue
109- perfLogs ( ) ;
110-
111- multiClick ( btns , measureCount ) ;
112- gc ( ) ;
113- return perfLogs ( ) . then ( function ( logs ) {
114- var stats = sumTimelineStats ( logs [ 'Timeline.eventRecorded' ] ) ;
115- printObjectAsMarkdown ( name , stats ) ;
116- return stats ;
117- } ) ;
118- }
119-
120- function gc ( ) {
121- // TODO(tbosch): this only works on chrome.
122- // For iOS Safari we need an extension to appium...
123- browser . executeScript ( 'window.gc()' ) ;
124- }
125-
126- function multiClick ( buttons , count ) {
127- var actions = browser . actions ( ) ;
128- for ( var i = 0 ; i < count ; i ++ ) {
129- buttons . forEach ( function ( button ) {
130- actions . click ( button ) ;
131- } ) ;
132- }
133- actions . perform ( ) ;
134- }
129+ function processRecord ( record , recordStats ) {
130+ var summedChildrenDuration = 0 ;
131+ if ( record . children ) {
132+ record . children . forEach ( function ( child ) {
133+ summedChildrenDuration += processRecord ( child , recordStats ) ;
134+ } ) ;
135+ }
135136
136- function verifyNoErrors ( ) {
137- browser . manage ( ) . logs ( ) . get ( 'browser' ) . then ( function ( browserLog ) {
138- var filteredLog = browserLog . filter ( function ( logEntry ) {
139- return logEntry . level . value > webdriver . logging . Level . WARNING . value ;
140- } ) ;
141- expect ( filteredLog . length ) . toEqual ( 0 ) ;
142- if ( filteredLog . length ) {
143- console . log ( 'browser console errors: ' + require ( 'util' ) . inspect ( filteredLog ) ) ;
137+ var recordDuration ;
138+ var recordUsed = false ;
139+ if ( recordStats ) {
140+ // we need to substract the time of child records
141+ // that have been added to the stats from this record.
142+ // E.g. for a script record that triggered a gc or reflow while executing.
143+ recordDuration = ( record . endTime ? record . endTime - record . startTime : 0 )
144+ - summedChildrenDuration ;
145+ if ( record . type === 'FunctionCall' ) {
146+ if ( ! record . data || record . data . scriptName !== 'InjectedScript' ) {
147+ // ignore scripts that were injected by Webdriver (e.g. calculation of element positions, ...)
148+ recordStats . script += recordDuration ;
149+ recordUsed = true ;
150+ }
151+ } else if ( record . type === 'GCEvent' ) {
152+ recordStats . gcTime += recordDuration ;
153+ recordStats . gcAmount += record . data . usedHeapSizeDelta ;
154+ recordUsed = true ;
155+ } else if ( record . type === 'RecalculateStyles' ||
156+ record . type === 'Layout' ||
157+ record . type === 'UpdateLayerTree' ||
158+ record . type === 'Paint' ||
159+ record . type === 'Rasterize' ||
160+ record . type === 'CompositeLayers' ) {
161+ recordStats . render += recordDuration ;
162+ recordUsed = true ;
163+ }
144164 }
145- } ) ;
165+ return recordUsed ? recordDuration : summedChildrenDuration ;
166+ }
146167}
147168
148169function printObjectAsMarkdown ( name , obj ) {
@@ -176,4 +197,40 @@ function printObjectAsMarkdown(name, obj) {
176197 }
177198 }
178199 }
179- }
200+ }
201+
202+ function calculateObjectSampleStats ( objectSamples , properties ) {
203+ var result = { } ;
204+ properties . forEach ( function ( prop ) {
205+ var samples = objectSamples . map ( function ( objectSample ) {
206+ return objectSample [ prop ] ;
207+ } ) ;
208+ var mean = calculateMean ( samples ) ;
209+ var error = calculateCoefficientOfVariation ( samples , mean ) ;
210+ result [ prop ] = {
211+ mean : mean ,
212+ error : error
213+ } ;
214+ } ) ;
215+ return result ;
216+ }
217+
218+ function calculateCoefficientOfVariation ( sample , mean ) {
219+ return calculateStandardDeviation ( sample , mean ) / mean * 100 ;
220+ }
221+
222+ function calculateMean ( sample ) {
223+ var total = 0 ;
224+ sample . forEach ( function ( x ) { total += x ; } ) ;
225+ return total / sample . length ;
226+ }
227+
228+ function calculateStandardDeviation ( sample , mean ) {
229+ var deviation = 0 ;
230+ sample . forEach ( function ( x ) {
231+ deviation += Math . pow ( x - mean , 2 ) ;
232+ } ) ;
233+ deviation = deviation / ( sample . length - 1 ) ;
234+ deviation = Math . sqrt ( deviation ) ;
235+ return deviation ;
236+ } ;
0 commit comments