github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/statements/statements.spec.tsx (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 import moment from "moment"; 14 import { RouteComponentProps } from "react-router-dom"; 15 import * as H from "history"; 16 17 import "src/protobufInit"; 18 import * as protos from "src/js/protos"; 19 import { CollectedStatementStatistics } from "src/util/appStats"; 20 import { appAttr, statementAttr } from "src/util/constants"; 21 import { selectStatements, selectApps, selectTotalFingerprints, selectLastReset } from "./statementsPage"; 22 import { selectStatement } from "./statementDetails"; 23 import ISensitiveInfo = protos.cockroach.sql.ISensitiveInfo; 24 25 const INTERNAL_STATEMENT_PREFIX = "$ internal"; 26 27 describe("selectStatements", () => { 28 it("returns null if the statements data is invalid", () => { 29 const state = makeInvalidState(); 30 const props = makeEmptyRouteProps(); 31 32 const result = selectStatements(state, props); 33 34 assert.isNull(result); 35 }); 36 37 it("returns the statements currently loaded", () => { 38 const stmtA = makeFingerprint(1); 39 const stmtB = makeFingerprint(2, "foobar"); 40 const stmtC = makeFingerprint(3, "another"); 41 const state = makeStateWithStatements([stmtA, stmtB, stmtC]); 42 const props = makeEmptyRouteProps(); 43 44 const result = selectStatements(state, props); 45 46 assert.equal(result.length, 3); 47 48 const expectedFingerprints = [stmtA, stmtB, stmtC].map(stmt => stmt.key.key_data.query); 49 expectedFingerprints.sort(); 50 const actualFingerprints = result.map((stmt: any) => stmt.label); 51 actualFingerprints.sort(); 52 assert.deepEqual(actualFingerprints, expectedFingerprints); 53 }); 54 55 it("returns the statements without Internal for default ALL filter", () => { 56 const stmtA = makeFingerprint(1); 57 const stmtB = makeFingerprint(2, INTERNAL_STATEMENT_PREFIX); 58 const stmtC = makeFingerprint(3, INTERNAL_STATEMENT_PREFIX); 59 const stmtD = makeFingerprint(3, "another"); 60 const state = makeStateWithStatements([stmtA, stmtB, stmtC, stmtD]); 61 const props = makeEmptyRouteProps(); 62 63 const result = selectStatements(state, props); 64 65 assert.equal(result.length, 2); 66 }); 67 68 it("coalesces statements from different apps", () => { 69 const stmtA = makeFingerprint(1); 70 const stmtB = makeFingerprint(1, "foobar"); 71 const stmtC = makeFingerprint(1, "another"); 72 const sumCount = stmtA.stats.count.add(stmtB.stats.count.add(stmtC.stats.count)).toNumber(); 73 const state = makeStateWithStatements([stmtA, stmtB, stmtC]); 74 const props = makeEmptyRouteProps(); 75 76 const result = selectStatements(state, props); 77 78 assert.equal(result.length, 1); 79 assert.equal(result[0].label, stmtA.key.key_data.query); 80 assert.equal(result[0].stats.count.toNumber(), sumCount); 81 }); 82 83 it("coalesces statements with differing node ids", () => { 84 const state = makeStateWithStatements([ 85 makeFingerprint(1, "", 1), 86 makeFingerprint(1, "", 2), 87 makeFingerprint(1, "", 3), 88 ]); 89 const props = makeEmptyRouteProps(); 90 91 const result = selectStatements(state, props); 92 93 assert.equal(result.length, 1); 94 }); 95 96 it("coalesces statements with differing distSQL and failed values", () => { 97 const state = makeStateWithStatements([ 98 makeFingerprint(1, "", 1, false, false), 99 makeFingerprint(1, "", 1, false, true), 100 makeFingerprint(1, "", 1, true, false), 101 makeFingerprint(1, "", 1, true, true), 102 ]); 103 const props = makeEmptyRouteProps(); 104 105 const result = selectStatements(state, props); 106 107 assert.equal(result.length, 1); 108 }); 109 110 it("filters out statements when app param is set", () => { 111 const state = makeStateWithStatements([ 112 makeFingerprint(1, "foo"), 113 makeFingerprint(2, "bar"), 114 makeFingerprint(3, "baz"), 115 ]); 116 const props = makeRoutePropsWithApp("foo"); 117 118 const result = selectStatements(state, props); 119 120 assert.equal(result.length, 1); 121 }); 122 123 it("filters out statements with app set when app param is \"(unset)\"", () => { 124 const state = makeStateWithStatements([ 125 makeFingerprint(1, ""), 126 makeFingerprint(2, "bar"), 127 makeFingerprint(3, "baz"), 128 ]); 129 const props = makeRoutePropsWithApp("(unset)"); 130 131 const result = selectStatements(state, props); 132 133 assert.equal(result.length, 1); 134 }); 135 136 it("filters out statements with app set when app param is \"(internal)\"", () => { 137 const state = makeStateWithStatements([ 138 makeFingerprint(1, "$ internal_stmnt_app"), 139 makeFingerprint(2, "bar"), 140 makeFingerprint(3, "baz"), 141 ]); 142 const props = makeRoutePropsWithApp("(internal)"); 143 const result = selectStatements(state, props); 144 assert.equal(result.length, 1); 145 }); 146 }); 147 148 describe("selectApps", () => { 149 it("returns an empty array if the statements data is invalid", () => { 150 const state = makeInvalidState(); 151 152 const result = selectApps(state); 153 154 assert.deepEqual(result, []); 155 }); 156 157 it("returns all the apps that appear in the statements", () => { 158 const state = makeStateWithStatements([ 159 makeFingerprint(1), 160 makeFingerprint(1, "foobar"), 161 makeFingerprint(2, "foobar"), 162 makeFingerprint(3, "cockroach sql"), 163 ]); 164 165 const result = selectApps(state); 166 167 assert.deepEqual(result, ["(unset)", "foobar", "cockroach sql"]); 168 }); 169 }); 170 171 describe("selectTotalFingerprints", () => { 172 it("returns zero if the statements data is invalid", () => { 173 const state = makeInvalidState(); 174 175 const result = selectTotalFingerprints(state); 176 177 assert.equal(result, 0); 178 }); 179 180 it("returns the number of statement fingerprints", () => { 181 const state = makeStateWithStatements([ 182 makeFingerprint(1), 183 makeFingerprint(2), 184 makeFingerprint(3), 185 ]); 186 187 const result = selectTotalFingerprints(state); 188 189 assert.equal(result, 3); 190 }); 191 192 it("coalesces statements from different apps", () => { 193 const state = makeStateWithStatements([ 194 makeFingerprint(1), 195 makeFingerprint(1, "foobar"), 196 makeFingerprint(1, "another"), 197 ]); 198 199 const result = selectTotalFingerprints(state); 200 201 assert.equal(result, 1); 202 }); 203 204 it("coalesces statements with differing node ids", () => { 205 const state = makeStateWithStatements([ 206 makeFingerprint(1, "", 1), 207 makeFingerprint(1, "", 2), 208 makeFingerprint(1, "", 3), 209 ]); 210 211 const result = selectTotalFingerprints(state); 212 213 assert.equal(result, 1); 214 }); 215 216 it("coalesces statements with differing distSQL and failed keys", () => { 217 const state = makeStateWithStatements([ 218 makeFingerprint(1, "", 1, false, false), 219 makeFingerprint(1, "", 1, false, true), 220 makeFingerprint(1, "", 1, true, false), 221 makeFingerprint(1, "", 1, true, true), 222 ]); 223 224 const result = selectTotalFingerprints(state); 225 226 assert.equal(result, 1); 227 }); 228 }); 229 230 describe("selectLastReset", () => { 231 it("returns \"unknown\" if the statements data is invalid", () => { 232 const state = makeInvalidState(); 233 234 const result = selectLastReset(state); 235 236 assert.equal(result, "unknown"); 237 }); 238 239 it("returns the formatted timestamp if valid", () => { 240 const timestamp = 92951700; 241 const state = makeStateWithLastReset(timestamp); 242 243 const result = selectLastReset(state); 244 245 assert.equal(moment.utc(result).unix(), timestamp); 246 }); 247 }); 248 249 describe("selectStatement", () => { 250 it("returns null if the statements data is invalid", () => { 251 const state = makeInvalidState(); 252 const props = makeEmptyRouteProps(); 253 254 const result = selectStatement(state, props); 255 256 assert.isNull(result); 257 }); 258 259 it("returns the statement currently loaded", () => { 260 const stmtA = makeFingerprint(1); 261 const stmtB = makeFingerprint(2, "foobar"); 262 const stmtC = makeFingerprint(3, "another"); 263 const state = makeStateWithStatements([stmtA, stmtB, stmtC]); 264 const props = makeRoutePropsWithStatement(stmtA.key.key_data.query); 265 266 const result = selectStatement(state, props); 267 268 assert.equal(result.statement, stmtA.key.key_data.query); 269 assert.equal(result.stats.count.toNumber(), stmtA.stats.count.toNumber()); 270 assert.deepEqual(result.app, [stmtA.key.key_data.app]); 271 assert.deepEqual(result.distSQL, { numerator: 0, denominator: 1 }); 272 assert.deepEqual(result.opt, { numerator: 0, denominator: 1 }); 273 assert.deepEqual(result.failed, { numerator: 0, denominator: 1 }); 274 assert.deepEqual(result.node_id, [stmtA.key.node_id]); 275 }); 276 277 it("coalesces statements from different apps", () => { 278 const stmtA = makeFingerprint(1); 279 const stmtB = makeFingerprint(1, "foobar"); 280 const stmtC = makeFingerprint(1, "another"); 281 const sumCount = stmtA.stats.count.add(stmtB.stats.count.add(stmtC.stats.count)).toNumber(); 282 const state = makeStateWithStatements([stmtA, stmtB, stmtC]); 283 const props = makeRoutePropsWithStatement(stmtA.key.key_data.query); 284 285 const result = selectStatement(state, props); 286 287 assert.equal(result.statement, stmtA.key.key_data.query); 288 assert.equal(result.stats.count.toNumber(), sumCount); 289 assert.deepEqual(result.app, [stmtA.key.key_data.app, stmtB.key.key_data.app, stmtC.key.key_data.app]); 290 assert.deepEqual(result.distSQL, { numerator: 0, denominator: 3 }); 291 assert.deepEqual(result.opt, { numerator: 0, denominator: 3 }); 292 assert.deepEqual(result.failed, { numerator: 0, denominator: 3 }); 293 assert.deepEqual(result.node_id, [stmtA.key.node_id]); 294 }); 295 296 it("coalesces statements with differing node ids", () => { 297 const stmtA = makeFingerprint(1, "", 1); 298 const stmtB = makeFingerprint(1, "", 2); 299 const stmtC = makeFingerprint(1, "", 3); 300 const sumCount = stmtA.stats.count 301 .add(stmtB.stats.count) 302 .add(stmtC.stats.count) 303 .toNumber(); 304 const state = makeStateWithStatements([stmtA, stmtB, stmtC]); 305 const props = makeRoutePropsWithStatement(stmtA.key.key_data.query); 306 307 const result = selectStatement(state, props); 308 309 assert.equal(result.statement, stmtA.key.key_data.query); 310 assert.equal(result.stats.count.toNumber(), sumCount); 311 assert.deepEqual(result.app, [stmtA.key.key_data.app]); 312 assert.deepEqual(result.distSQL, { numerator: 0, denominator: 3 }); 313 assert.deepEqual(result.opt, { numerator: 0, denominator: 3 }); 314 assert.deepEqual(result.failed, { numerator: 0, denominator: 3 }); 315 assert.deepEqual(result.node_id, [1, 2, 3]); 316 }); 317 318 it("coalesces statements with differing distSQL, opt and failed values", () => { 319 const stmtA = makeFingerprint(1, "", 1, false, false, false); 320 const stmtB = makeFingerprint(1, "", 1, false, false, true); 321 const stmtC = makeFingerprint(1, "", 1, false, true, false); 322 const stmtD = makeFingerprint(1, "", 1, false, true, true); 323 const stmtE = makeFingerprint(1, "", 1, true, false, false); 324 const stmtF = makeFingerprint(1, "", 1, true, false, true); 325 const stmtG = makeFingerprint(1, "", 1, true, true, false); 326 const stmtH = makeFingerprint(1, "", 1, true, true, true); 327 const sumCount = stmtA.stats.count 328 .add(stmtB.stats.count) 329 .add(stmtC.stats.count) 330 .add(stmtD.stats.count) 331 .add(stmtE.stats.count) 332 .add(stmtF.stats.count) 333 .add(stmtG.stats.count) 334 .add(stmtH.stats.count) 335 .toNumber(); 336 const state = makeStateWithStatements([stmtA, stmtB, stmtC, stmtD, stmtE, stmtF, stmtG, stmtH]); 337 const props = makeRoutePropsWithStatement(stmtA.key.key_data.query); 338 339 const result = selectStatement(state, props); 340 341 assert.equal(result.statement, stmtA.key.key_data.query); 342 assert.equal(result.stats.count.toNumber(), sumCount); 343 assert.deepEqual(result.app, [stmtA.key.key_data.app]); 344 assert.deepEqual(result.distSQL, { numerator: 4, denominator: 8 }); 345 assert.deepEqual(result.opt, { numerator: 4, denominator: 8 }); 346 assert.deepEqual(result.failed, { numerator: 4, denominator: 8 }); 347 assert.deepEqual(result.node_id, [stmtA.key.node_id]); 348 }); 349 350 it("filters out statements when app param is set", () => { 351 const stmtA = makeFingerprint(1, "foo"); 352 const state = makeStateWithStatements([ 353 stmtA, 354 makeFingerprint(2, "bar"), 355 makeFingerprint(3, "baz"), 356 ]); 357 const props = makeRoutePropsWithStatementAndApp(stmtA.key.key_data.query, "foo"); 358 359 const result = selectStatement(state, props); 360 361 assert.equal(result.statement, stmtA.key.key_data.query); 362 assert.equal(result.stats.count.toNumber(), stmtA.stats.count.toNumber()); 363 assert.deepEqual(result.app, [stmtA.key.key_data.app]); 364 assert.deepEqual(result.distSQL, { numerator: 0, denominator: 1 }); 365 assert.deepEqual(result.opt, { numerator: 0, denominator: 1 }); 366 assert.deepEqual(result.failed, { numerator: 0, denominator: 1 }); 367 assert.deepEqual(result.node_id, [stmtA.key.node_id]); 368 }); 369 370 it("filters out statements with app set when app param is \"(unset)\"", () => { 371 const stmtA = makeFingerprint(1, ""); 372 const state = makeStateWithStatements([ 373 stmtA, 374 makeFingerprint(2, "bar"), 375 makeFingerprint(3, "baz"), 376 ]); 377 const props = makeRoutePropsWithStatementAndApp(stmtA.key.key_data.query, "(unset)"); 378 379 const result = selectStatement(state, props); 380 381 assert.equal(result.statement, stmtA.key.key_data.query); 382 assert.equal(result.stats.count.toNumber(), stmtA.stats.count.toNumber()); 383 assert.deepEqual(result.app, [stmtA.key.key_data.app]); 384 assert.deepEqual(result.distSQL, { numerator: 0, denominator: 1 }); 385 assert.deepEqual(result.opt, { numerator: 0, denominator: 1 }); 386 assert.deepEqual(result.failed, { numerator: 0, denominator: 1 }); 387 assert.deepEqual(result.node_id, [stmtA.key.node_id]); 388 }); 389 390 it("filters out statements with app set when app param is \"(internal)\"", () => { 391 const stmtA = makeFingerprint(1, "$ internal_stmnt_app"); 392 const state = makeStateWithStatements([ 393 stmtA, 394 makeFingerprint(2, "bar"), 395 makeFingerprint(3, "baz"), 396 ]); 397 const props = makeRoutePropsWithStatementAndApp(stmtA.key.key_data.query, "(internal)"); 398 399 const result = selectStatement(state, props); 400 401 assert.equal(result.statement, stmtA.key.key_data.query); 402 assert.equal(result.stats.count.toNumber(), stmtA.stats.count.toNumber()); 403 assert.deepEqual(result.app, [stmtA.key.key_data.app]); 404 assert.deepEqual(result.distSQL, { numerator: 0, denominator: 1 }); 405 assert.deepEqual(result.opt, { numerator: 0, denominator: 1 }); 406 assert.deepEqual(result.failed, { numerator: 0, denominator: 1 }); 407 assert.deepEqual(result.node_id, [stmtA.key.node_id]); 408 }); 409 }); 410 411 function makeFingerprint(id: number, app: string = "", nodeId: number = 1, distSQL: boolean = false, failed: boolean = false, opt: boolean = false) { 412 return { 413 key: { 414 key_data: { 415 query: "SELECT * FROM table_" + id + " WHERE true", 416 app, 417 distSQL, 418 opt, 419 failed, 420 }, 421 node_id: nodeId, 422 }, 423 stats: makeStats(), 424 }; 425 } 426 427 let makeStatsIndex = 1; 428 function makeStats() { 429 return { 430 count: Long.fromNumber(makeStatsIndex++), 431 first_attempt_count: Long.fromNumber(1), 432 max_retries: Long.fromNumber(0), 433 num_rows: makeStat(), 434 parse_lat: makeStat(), 435 plan_lat: makeStat(), 436 run_lat: makeStat(), 437 overhead_lat: makeStat(), 438 service_lat: makeStat(), 439 sensitive_info: makeEmptySensitiveInfo(), 440 }; 441 } 442 443 function makeStat() { 444 return { 445 mean: 10, 446 squared_diffs: 1, 447 }; 448 } 449 450 function makeEmptySensitiveInfo(): ISensitiveInfo { 451 return { 452 last_err: null, 453 most_recent_plan_description: null, 454 }; 455 } 456 457 function makeInvalidState() { 458 return { 459 cachedData: { 460 statements: { 461 inFlight: true, 462 valid: false, 463 }, 464 statementDiagnosticsReports: { 465 inFlight: true, 466 valid: false, 467 }, 468 }, 469 }; 470 } 471 472 function makeStateWithStatementsAndLastReset(statements: CollectedStatementStatistics[], lastReset: number) { 473 return { 474 cachedData: { 475 statements: { 476 data: protos.cockroach.server.serverpb.StatementsResponse.fromObject({ 477 statements, 478 last_reset: { 479 seconds: lastReset, 480 nanos: 0, 481 }, 482 internal_app_name_prefix: INTERNAL_STATEMENT_PREFIX, 483 }), 484 inFlight: false, 485 valid: true, 486 }, 487 statementDiagnosticsReports: { 488 inFlight: true, 489 valid: false, 490 }, 491 }, 492 }; 493 } 494 495 function makeStateWithStatements(statements: CollectedStatementStatistics[]) { 496 return makeStateWithStatementsAndLastReset(statements, 0); 497 } 498 499 function makeStateWithLastReset(lastReset: number) { 500 return makeStateWithStatementsAndLastReset([], lastReset); 501 } 502 503 function makeRoutePropsWithParams(params: { [key: string]: string }) { 504 const history = H.createHashHistory(); 505 return { 506 location: history.location, 507 history, 508 match: { 509 url: "", 510 path: history.location.pathname, 511 isExact: false, 512 params, 513 }, 514 }; 515 } 516 517 function makeEmptyRouteProps(): RouteComponentProps<any> { 518 const history = H.createHashHistory(); 519 return { 520 location: history.location, 521 history, 522 match: { 523 url: "", 524 path: history.location.pathname, 525 isExact: false, 526 params: {}, 527 }, 528 }; 529 } 530 531 function makeRoutePropsWithApp(app: string) { 532 return makeRoutePropsWithParams({ 533 [appAttr]: app, 534 }); 535 } 536 537 function makeRoutePropsWithStatement(stmt: string) { 538 return makeRoutePropsWithParams({ 539 [statementAttr]: stmt, 540 }); 541 } 542 543 function makeRoutePropsWithStatementAndApp(stmt: string, app: string) { 544 return makeRoutePropsWithParams({ 545 [appAttr]: app, 546 [statementAttr]: stmt, 547 }); 548 }