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  }