github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/util/appStats.spec.ts (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 import { assert } from "chai"; 12 import Long from "long"; 13 14 import * as protos from "src/js/protos"; 15 import { addNumericStats, NumericStat, flattenStatementStats, StatementStatistics, combineStatementStats } from "./appStats"; 16 import IExplainTreePlanNode = protos.cockroach.sql.IExplainTreePlanNode; 17 import ISensitiveInfo = protos.cockroach.sql.ISensitiveInfo; 18 19 // record is implemented here so we can write the below test as a direct 20 // analog of the one in pkg/roachpb/app_stats_test.go. It's here rather 21 // than in the main source file because we don't actually need it for the 22 // application to use. 23 function record(l: NumericStat, count: number, val: number) { 24 const delta = val - l.mean; 25 l.mean += delta / count; 26 l.squared_diffs += delta * (val - l.mean); 27 } 28 29 function emptyStats() { 30 return { 31 mean: 0, 32 squared_diffs: 0, 33 }; 34 } 35 36 describe("addNumericStats", () => { 37 it("adds two numeric stats together", () => { 38 const aData = [1.1, 3.3, 2.2]; 39 const bData = [2.0, 3.0, 5.5, 1.2]; 40 41 let countA = 0; 42 let countB = 0; 43 let countAB = 0; 44 45 let sumA = 0; 46 let sumB = 0; 47 let sumAB = 0; 48 49 const a = emptyStats(); 50 const b = emptyStats(); 51 const ab = emptyStats(); 52 53 aData.forEach(v => { 54 countA++; 55 sumA += v; 56 record(a, countA, v); 57 }); 58 59 bData.forEach(v => { 60 countB++; 61 sumB += v; 62 record(b, countB, v); 63 }); 64 65 bData.concat(aData).forEach(v => { 66 countAB++; 67 sumAB += v; 68 record(ab, countAB, v); 69 }); 70 71 assert.approximately(a.mean, 2.2, 0.0000001); 72 assert.approximately(a.mean, sumA / countA, 0.0000001); 73 assert.approximately(b.mean, sumB / countB, 0.0000001); 74 assert.approximately(ab.mean, sumAB / countAB, 0.0000001); 75 76 const combined = addNumericStats(a, b, countA, countB); 77 78 assert.approximately(combined.mean, ab.mean, 0.0000001); 79 assert.approximately(combined.squared_diffs, ab.squared_diffs, 0.0000001); 80 81 const reversed = addNumericStats(b, a, countB, countA); 82 83 assert.deepEqual(reversed, combined); 84 }); 85 }); 86 87 describe("flattenStatementStats", () => { 88 it("flattens CollectedStatementStatistics to ExecutionStatistics", () => { 89 const stats = [ 90 { 91 key: { 92 key_data: { 93 query: "SELECT * FROM foobar", 94 app: "foobar", 95 distSQL: true, 96 opt: true, 97 failed: false, 98 }, 99 node_id: 1, 100 }, 101 stats: {}, 102 }, 103 { 104 key: { 105 key_data: { 106 query: "UPDATE foobar SET name = 'baz' WHERE id = 42", 107 app: "bazzer", 108 distSQL: false, 109 opt: false, 110 failed: true, 111 }, 112 node_id: 2, 113 }, 114 stats: {}, 115 }, 116 ]; 117 118 const flattened = flattenStatementStats(stats); 119 120 assert.equal(flattened.length, stats.length); 121 122 for (let i = 0; i < flattened.length; i++) { 123 assert.equal(flattened[i].statement, stats[i].key.key_data.query); 124 assert.equal(flattened[i].app, stats[i].key.key_data.app); 125 assert.equal(flattened[i].distSQL, stats[i].key.key_data.distSQL); 126 assert.equal(flattened[i].opt, stats[i].key.key_data.opt); 127 assert.equal(flattened[i].failed, stats[i].key.key_data.failed); 128 assert.equal(flattened[i].node_id, stats[i].key.node_id); 129 130 assert.equal(flattened[i].stats, stats[i].stats); 131 } 132 }); 133 }); 134 135 function randomInt(max: number): number { 136 return Math.floor(Math.random() * max); 137 } 138 139 function randomFloat(scale: number): number { 140 return Math.random() * scale; 141 } 142 143 function randomStat(scale: number = 1): NumericStat { 144 return { 145 mean: randomFloat(scale), 146 squared_diffs: randomFloat(scale * 0.3), 147 }; 148 } 149 150 function randomStats(sensitiveInfo?: ISensitiveInfo): StatementStatistics { 151 const count = randomInt(1000); 152 // tslint:disable:variable-name 153 const first_attempt_count = randomInt(count); 154 const max_retries = randomInt(count - first_attempt_count); 155 // tslint:enable:variable-name 156 157 return { 158 count: Long.fromNumber(count), 159 first_attempt_count: Long.fromNumber(first_attempt_count), 160 max_retries: Long.fromNumber(max_retries), 161 num_rows: randomStat(100), 162 parse_lat: randomStat(), 163 plan_lat: randomStat(), 164 run_lat: randomStat(), 165 service_lat: randomStat(), 166 overhead_lat: randomStat(), 167 sensitive_info: sensitiveInfo || makeSensitiveInfo(null, null), 168 }; 169 } 170 171 function randomString(length: number = 10): string { 172 const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 173 let text = ""; 174 for (let i = 0; i < length; i++) { 175 text += possible.charAt(Math.floor(Math.random() * possible.length)); 176 } 177 return text; 178 } 179 180 function randomPlanDescription(): IExplainTreePlanNode { 181 return { 182 name: randomString(), 183 attrs: [ 184 { 185 key: randomString(), 186 value: randomString(), 187 }, 188 ], 189 }; 190 } 191 192 function makeSensitiveInfo(lastErr: string, planDescription: IExplainTreePlanNode): ISensitiveInfo { 193 return { 194 last_err: lastErr, 195 most_recent_plan_description: planDescription, 196 }; 197 } 198 199 describe("combineStatementStats", () => { 200 it("combines statement statistics", () => { 201 const a = randomStats(); 202 const b = randomStats(); 203 const c = randomStats(); 204 205 const ab = combineStatementStats([a, b]); 206 const ac = combineStatementStats([a, c]); 207 const bc = combineStatementStats([b, c]); 208 209 // tslint:disable:variable-name 210 const ab_c = combineStatementStats([ab, c]); 211 const ac_b = combineStatementStats([ac, b]); 212 const bc_a = combineStatementStats([bc, a]); 213 // tslint:enable:variable-name 214 215 assert.equal(ab_c.count.toString(), ac_b.count.toString()); 216 assert.equal(ab_c.count.toString(), bc_a.count.toString()); 217 218 assert.equal(ab_c.first_attempt_count.toString(), ac_b.first_attempt_count.toString()); 219 assert.equal(ab_c.first_attempt_count.toString(), bc_a.first_attempt_count.toString()); 220 221 assert.equal(ab_c.max_retries.toString(), ac_b.max_retries.toString()); 222 assert.equal(ab_c.max_retries.toString(), bc_a.max_retries.toString()); 223 224 assert.approximately(ab_c.num_rows.mean, ac_b.num_rows.mean, 0.0000001); 225 assert.approximately(ab_c.num_rows.mean, bc_a.num_rows.mean, 0.0000001); 226 assert.approximately(ab_c.num_rows.squared_diffs, ac_b.num_rows.squared_diffs, 0.0000001); 227 assert.approximately(ab_c.num_rows.squared_diffs, bc_a.num_rows.squared_diffs, 0.0000001); 228 229 assert.approximately(ab_c.parse_lat.mean, ac_b.parse_lat.mean, 0.0000001); 230 assert.approximately(ab_c.parse_lat.mean, bc_a.parse_lat.mean, 0.0000001); 231 assert.approximately(ab_c.parse_lat.squared_diffs, ac_b.parse_lat.squared_diffs, 0.0000001); 232 assert.approximately(ab_c.parse_lat.squared_diffs, bc_a.parse_lat.squared_diffs, 0.0000001); 233 234 assert.approximately(ab_c.plan_lat.mean, ac_b.plan_lat.mean, 0.0000001); 235 assert.approximately(ab_c.plan_lat.mean, bc_a.plan_lat.mean, 0.0000001); 236 assert.approximately(ab_c.plan_lat.squared_diffs, ac_b.plan_lat.squared_diffs, 0.0000001); 237 assert.approximately(ab_c.plan_lat.squared_diffs, bc_a.plan_lat.squared_diffs, 0.0000001); 238 239 assert.approximately(ab_c.run_lat.mean, ac_b.run_lat.mean, 0.0000001); 240 assert.approximately(ab_c.run_lat.mean, bc_a.run_lat.mean, 0.0000001); 241 assert.approximately(ab_c.run_lat.squared_diffs, ac_b.run_lat.squared_diffs, 0.0000001); 242 assert.approximately(ab_c.run_lat.squared_diffs, bc_a.run_lat.squared_diffs, 0.0000001); 243 244 assert.approximately(ab_c.service_lat.mean, ac_b.service_lat.mean, 0.0000001); 245 assert.approximately(ab_c.service_lat.mean, bc_a.service_lat.mean, 0.0000001); 246 assert.approximately(ab_c.service_lat.squared_diffs, ac_b.service_lat.squared_diffs, 0.0000001); 247 assert.approximately(ab_c.service_lat.squared_diffs, bc_a.service_lat.squared_diffs, 0.0000001); 248 249 assert.approximately(ab_c.overhead_lat.mean, ac_b.overhead_lat.mean, 0.0000001); 250 assert.approximately(ab_c.overhead_lat.mean, bc_a.overhead_lat.mean, 0.0000001); 251 assert.approximately(ab_c.overhead_lat.squared_diffs, ac_b.overhead_lat.squared_diffs, 0.0000001); 252 assert.approximately(ab_c.overhead_lat.squared_diffs, bc_a.overhead_lat.squared_diffs, 0.0000001); 253 }); 254 255 describe("when sensitiveInfo has data", () => { 256 it("uses first non-empty property from each statementStat", () => { 257 const error1 = randomString(); 258 const error2 = randomString(); 259 const plan1 = randomPlanDescription(); 260 const plan2 = randomPlanDescription(); 261 262 const empty = makeSensitiveInfo(null, null); 263 const a = makeSensitiveInfo(error1, null); 264 const b = makeSensitiveInfo(null, plan1); 265 const c = makeSensitiveInfo(error2, plan2); 266 267 assertSensitiveInfoInCombineStatementStats([empty], empty); 268 assertSensitiveInfoInCombineStatementStats([a], a); 269 assertSensitiveInfoInCombineStatementStats([b], b); 270 assertSensitiveInfoInCombineStatementStats([c], c); 271 272 assertSensitiveInfoInCombineStatementStats([empty, a], a); 273 assertSensitiveInfoInCombineStatementStats([empty, b], b); 274 assertSensitiveInfoInCombineStatementStats([empty, c], c); 275 assertSensitiveInfoInCombineStatementStats([a, empty], a); 276 assertSensitiveInfoInCombineStatementStats([b, empty], b); 277 assertSensitiveInfoInCombineStatementStats([c, empty], c); 278 279 assertSensitiveInfoInCombineStatementStats([a, b, c], { 280 last_err: a.last_err, 281 most_recent_plan_description: b.most_recent_plan_description, 282 }); 283 assertSensitiveInfoInCombineStatementStats([a, c, b], { 284 last_err: a.last_err, 285 most_recent_plan_description: c.most_recent_plan_description, 286 }); 287 assertSensitiveInfoInCombineStatementStats([b, c, a], { 288 last_err: c.last_err, 289 most_recent_plan_description: b.most_recent_plan_description, 290 }); 291 assertSensitiveInfoInCombineStatementStats([c, a, b], c); 292 293 function assertSensitiveInfoInCombineStatementStats( 294 input: ISensitiveInfo[], 295 expected: ISensitiveInfo, 296 ) { 297 const stats = input.map((sensitiveInfo) => randomStats(sensitiveInfo)); 298 const result = combineStatementStats(stats); 299 assert.deepEqual(result.sensitive_info, expected); 300 } 301 }); 302 }); 303 });