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  });