telemetry-test-helpers.js (8579B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 /* global is ok registerCleanupFunction Services */ 5 6 "use strict"; 7 8 // We try to avoid polluting the global scope as far as possible by defining 9 // constants in the methods that use them because this script is not sandboxed 10 // meaning that it is loaded via Services.scriptloader.loadSubScript() 11 12 class TelemetryHelpers { 13 constructor() { 14 this.oldCanRecord = Services.telemetry.canRecordExtended; 15 this.generateTelemetryTests = this.generateTelemetryTests.bind(this); 16 registerCleanupFunction(this.stopTelemetry.bind(this)); 17 } 18 19 /** 20 * Allow collection of extended telemetry data. 21 */ 22 startTelemetry() { 23 Services.telemetry.canRecordExtended = true; 24 } 25 26 /** 27 * Clear all telemetry types. 28 */ 29 stopTelemetry() { 30 // Clear histograms, scalars and Telemetry Events. 31 this.clearHistograms(Services.telemetry.getSnapshotForHistograms); 32 this.clearHistograms(Services.telemetry.getSnapshotForKeyedHistograms); 33 Services.telemetry.clearScalars(); 34 Services.telemetry.clearEvents(); 35 36 Services.telemetry.canRecordExtended = this.oldCanRecord; 37 } 38 39 /** 40 * Clears Telemetry Histograms. 41 * 42 * @param {Function} snapshotFunc 43 * The function used to take the snapshot. This can be one of the 44 * following: 45 * - Services.telemetry.getSnapshotForHistograms 46 * - Services.telemetry.getSnapshotForKeyedHistograms 47 */ 48 clearHistograms(snapshotFunc) { 49 snapshotFunc("main", true); 50 } 51 52 /** 53 * Check the value of a given telemetry histogram. 54 * 55 * @param {string} histId 56 * Histogram id 57 * @param {string} key 58 * Keyed histogram key 59 * @param {Array | number} expected 60 * Expected value 61 * @param {string} checkType 62 * "array" (default) - Check that an array matches the histogram data. 63 * "hasentries" - For non-enumerated linear and exponential 64 * histograms. This checks for at least one entry. 65 * "scalar" - Telemetry type is a scalar. 66 * "keyedscalar" - Telemetry type is a keyed scalar. 67 */ 68 checkTelemetry(histId, key, expected, checkType) { 69 let actual; 70 let msg; 71 72 if (checkType === "array" || checkType === "hasentries") { 73 if (key) { 74 const keyedHistogram = Services.telemetry 75 .getKeyedHistogramById(histId) 76 .snapshot(); 77 const result = keyedHistogram[key]; 78 79 if (result) { 80 actual = result.values; 81 } else { 82 ok(false, `${histId}[${key}] exists`); 83 return; 84 } 85 } else { 86 actual = Services.telemetry.getHistogramById(histId).snapshot().values; 87 } 88 } 89 90 switch (checkType) { 91 case "array": 92 msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`; 93 is(JSON.stringify(actual), JSON.stringify(expected), msg); 94 break; 95 case "hasentries": { 96 const hasEntry = Object.values(actual).some(num => num > 0); 97 if (key) { 98 ok(hasEntry, `${histId}["${key}"] has at least one entry.`); 99 } else { 100 ok(hasEntry, `${histId} has at least one entry.`); 101 } 102 break; 103 } 104 case "scalar": { 105 const scalars = Services.telemetry.getSnapshotForScalars( 106 "main", 107 false 108 ).parent; 109 110 is(scalars[histId], expected, `${histId} correct`); 111 break; 112 } 113 case "keyedscalar": { 114 const keyedScalars = Services.telemetry.getSnapshotForKeyedScalars( 115 "main", 116 false 117 ).parent; 118 const value = keyedScalars[histId][key]; 119 120 msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`; 121 is(value, expected, msg); 122 break; 123 } 124 } 125 } 126 127 /** 128 * Generate telemetry tests. You should call generateTelemetryTests("DEVTOOLS_") 129 * from your result checking code in telemetry tests. It logs checkTelemetry 130 * calls for all changed telemetry values. 131 * 132 * @param {string} prefix 133 * Optionally limits results to histogram ids starting with prefix. 134 */ 135 generateTelemetryTests(prefix = "") { 136 // Get all histograms and scalars 137 const histograms = Services.telemetry.getSnapshotForHistograms( 138 "main", 139 true 140 ).parent; 141 const keyedHistograms = Services.telemetry.getSnapshotForKeyedHistograms( 142 "main", 143 true 144 ).parent; 145 const scalars = Services.telemetry.getSnapshotForScalars( 146 "main", 147 false 148 ).parent; 149 const keyedScalars = Services.telemetry.getSnapshotForKeyedScalars( 150 "main", 151 false 152 ).parent; 153 const allHistograms = Object.assign( 154 {}, 155 histograms, 156 keyedHistograms, 157 scalars, 158 keyedScalars 159 ); 160 // Get all keys 161 const histIds = Object.keys(allHistograms).filter(histId => 162 histId.startsWith(prefix) 163 ); 164 165 dump("=".repeat(80) + "\n"); 166 for (const histId of histIds) { 167 const snapshot = allHistograms[histId]; 168 169 if (histId === histId.toLowerCase()) { 170 if (typeof snapshot === "object") { 171 // Keyed Scalar 172 const keys = Object.keys(snapshot); 173 174 for (const key of keys) { 175 const value = snapshot[key]; 176 177 dump( 178 `checkTelemetry("${histId}", "${key}", ${value}, "keyedscalar");\n` 179 ); 180 } 181 } else { 182 // Scalar 183 dump(`checkTelemetry("${histId}", "", ${snapshot}, "scalar");\n`); 184 } 185 } else if ( 186 typeof snapshot.histogram_type !== "undefined" && 187 typeof snapshot.values !== "undefined" 188 ) { 189 // Histogram 190 const actual = snapshot.values; 191 192 this.displayDataFromHistogramSnapshot(snapshot, "", histId, actual); 193 } else { 194 // Keyed Histogram 195 const keys = Object.keys(snapshot); 196 197 for (const key of keys) { 198 const value = snapshot[key]; 199 const actual = value.counts; 200 201 this.displayDataFromHistogramSnapshot(value, key, histId, actual); 202 } 203 } 204 } 205 dump("=".repeat(80) + "\n"); 206 } 207 208 /** 209 * Generates the inner contents of a test's checkTelemetry() method. 210 * 211 * @param {HistogramSnapshot} snapshot 212 * A snapshot of a telemetry chart obtained via getSnapshotForHistograms or 213 * similar. 214 * @param {string} key 215 * Only used for keyed histograms. This is the key we are interested in 216 * checking. 217 * @param {string} histId 218 * The histogram ID. 219 * @param {Array | string | boolean} actual 220 * The value of the histogram data. 221 */ 222 displayDataFromHistogramSnapshot(snapshot, key, histId, actual) { 223 key = key ? `"${key}"` : `""`; 224 225 switch (snapshot.histogram_type) { 226 case Services.telemetry.HISTOGRAM_EXPONENTIAL: 227 case Services.telemetry.HISTOGRAM_LINEAR: { 228 let total = 0; 229 for (const val of Object.values(actual)) { 230 total += val; 231 } 232 233 if (histId.endsWith("_ENUMERATED")) { 234 if (total > 0) { 235 actual = actual.toSource(); 236 dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`); 237 } 238 return; 239 } 240 241 dump(`checkTelemetry("${histId}", ${key}, null, "hasentries");\n`); 242 break; 243 } 244 case Services.telemetry.HISTOGRAM_BOOLEAN: 245 actual = actual.toSource(); 246 247 if (actual !== "({})") { 248 dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`); 249 } 250 break; 251 case Services.telemetry.HISTOGRAM_FLAG: 252 actual = actual.toSource(); 253 254 if (actual !== "({0:1, 1:0})") { 255 dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`); 256 } 257 break; 258 case Services.telemetry.HISTOGRAM_COUNT: 259 actual = actual.toSource(); 260 261 dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`); 262 break; 263 } 264 } 265 } 266 267 // "exports"... because this is a helper and not imported via require we need to 268 // expose the three main methods that should be used by tests. The reason this 269 // is not imported via require is because it needs access to test methods 270 // (is, ok etc). 271 272 /* eslint-disable no-unused-vars */ 273 const telemetryHelpers = new TelemetryHelpers(); 274 const generateTelemetryTests = telemetryHelpers.generateTelemetryTests; 275 const checkTelemetry = telemetryHelpers.checkTelemetry; 276 const startTelemetry = telemetryHelpers.startTelemetry; 277 /* eslint-enable no-unused-vars */