Skip to content

Commit

Permalink
ui/cluster-ui: add 'Interval Start Time' column to stmts/txns tables
Browse files Browse the repository at this point in the history
This commit adds the `Interval Start Time (UTC)` column to stmt and txn
tables. Statements and transactions are now both grouped by their `aggregated_ts`
field in addition to the stmt / fingerprint id.

To support viewing of statements grouped by aggregation interval start time,
a new query parameter has been added to statement details pages.
If `aggregated_ts` is set, it will display the statement details for statements
aggregated at that interval, using data from combined statements API response.
If unset, we will fetch and show statement data from in-memory stats.

Release note (ui change): A new column, 'Interval Start Time (UTC)', has
been added to both statement and transaction tables. The column represents
the start time in UTC of the stats aggregation interval for a statement.
By default, the aggregation interval is 1 hour.

A new query parameter has been added to statement details pages.
If the search param `aggregated_ts` is set, it will display the statement details
for statements aggregated at that interval. If unset, we will display the statement
details for the statement collected from the last hour.
  • Loading branch information
xinhaoz committed Sep 16, 2021
1 parent 0fda751 commit 512cfe3
Show file tree
Hide file tree
Showing 38 changed files with 572 additions and 325 deletions.
39 changes: 0 additions & 39 deletions pkg/ui/workspaces/cluster-ui/src/api/fetchData.spec.ts

This file was deleted.

13 changes: 0 additions & 13 deletions pkg/ui/workspaces/cluster-ui/src/api/fetchData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { RequestError } from "../util";
import { getBasePath } from "./basePath";
import { stringify } from "querystring";

interface ProtoBuilder<
P extends ConstructorType,
Expand All @@ -30,18 +29,6 @@ export function toArrayBuffer(encodedRequest: Uint8Array): ArrayBuffer {
);
}

// propsToQueryString is a helper function that converts a set of object
// properties to a query string
// - keys with null or undefined values will be skipped
// - non-string values will be toString'd
export function propsToQueryString(props: { [k: string]: any }) {
const params = new URLSearchParams();
Object.entries(props).forEach(
([k, v]: [string, any]) => v != null && params.set(k, v.toString()),
);
return params.toString();
}

/**
* @param RespBuilder expects protobuf stub class to build decode response;
* @param path relative URL path for requested resource;
Expand Down
5 changes: 3 additions & 2 deletions pkg/ui/workspaces/cluster-ui/src/api/statementsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
// licenses/APL.txt.

import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { fetchData, propsToQueryString } from "src/api";
import { fetchData } from "src/api";
import { propsToQueryString } from "src/util";

const STATEMENTS_PATH = "/_status/statements";

Expand All @@ -28,7 +29,7 @@ export const getCombinedStatements = (
const queryStr = propsToQueryString({
start: req.start.toInt(),
end: req.end.toInt(),
combined: true,
combined: req.combined,
});
return fetchData(
cockroach.server.serverpb.StatementsResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { sessionAttr } from "src/util/constants";
import { Helmet } from "react-helmet";
import { Loading } from "../loading";
import _ from "lodash";
import { Link, RouteComponentProps, withRouter } from "react-router-dom";
import { Link, RouteComponentProps } from "react-router-dom";

import { SessionInfo } from "./sessionsTable";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ const statementStats: any = {
exec_stats: execStats,
};

const aggregatedTs = Date.parse("Sep 15 2021 01:00:00 GMT") * 1e-3;

export const getStatementDetailsPropsFixture = (): StatementDetailsProps => ({
history,
location: {
Expand Down Expand Up @@ -144,27 +146,31 @@ export const getStatementDetailsPropsFixture = (): StatementDetailsProps => ({
byNode: [
{
label: "4",
aggregatedTs,
implicitTxn: true,
database: "defaultdb",
fullScan: true,
stats: statementStats,
},
{
label: "3",
aggregatedTs,
implicitTxn: true,
database: "defaultdb",
fullScan: true,
stats: statementStats,
},
{
label: "2",
aggregatedTs,
implicitTxn: true,
database: "defaultdb",
fullScan: true,
stats: statementStats,
},
{
label: "1",
aggregatedTs,
implicitTxn: true,
database: "defaultdb",
fullScan: true,
Expand Down Expand Up @@ -208,6 +214,7 @@ export const getStatementDetailsPropsFixture = (): StatementDetailsProps => ({
"4": "gcp-europe-west1",
},
refreshStatements: noop,
invalidateStatements: noop,
refreshStatementDiagnosticsRequests: noop,
refreshNodes: noop,
refreshNodesLiveness: noop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { createSelector } from "@reduxjs/toolkit";
import { RouteComponentProps, match as Match } from "react-router-dom";
import { Location } from "history";
import _ from "lodash";
import { AppState } from "../store";
import {
Expand All @@ -24,12 +25,15 @@ import {
databaseAttr,
StatementStatistics,
statementKey,
aggregatedTsAttr,
queryByName,
} from "../util";
import { AggregateStatistics } from "../statementsTable";
import { Fraction } from "./statementDetails";

interface StatementDetailsData {
nodeId: number;
aggregatedTs: number;
implicitTxn: boolean;
fullScan: boolean;
database: string;
Expand All @@ -46,6 +50,7 @@ function coalesceNodeStats(
if (!(key in statsKey)) {
statsKey[key] = {
nodeId: stmt.node_id,
aggregatedTs: stmt.aggregated_ts,
implicitTxn: stmt.implicit_txn,
fullScan: stmt.full_scan,
database: stmt.database,
Expand All @@ -59,6 +64,7 @@ function coalesceNodeStats(
const stmt = statsKey[key];
return {
label: stmt.nodeId.toString(),
aggregatedTs: stmt.aggregatedTs,
implicitTxn: stmt.implicitTxn,
fullScan: stmt.fullScan,
database: stmt.database,
Expand Down Expand Up @@ -87,15 +93,19 @@ function fractionMatching(

function filterByRouterParamsPredicate(
match: Match<any>,
location: Location,
internalAppNamePrefix: string,
): (stat: ExecutionStatistics) => boolean {
const statement = getMatchParamByName(match, statementAttr);
const implicitTxn = getMatchParamByName(match, implicitTxnAttr) === "true";
const database = getMatchParamByName(match, databaseAttr);
const aggregatedTs = queryByName(location, aggregatedTsAttr);
let app = getMatchParamByName(match, appAttr);

const filterByKeys = (stmt: ExecutionStatistics) =>
stmt.statement === statement &&
aggregatedTs == null &&
stmt.aggregated_ts.toString() === aggregatedTs &&
stmt.implicit_txn === implicitTxn &&
(stmt.database === database || database === null);

Expand Down Expand Up @@ -129,7 +139,11 @@ export const selectStatement = createSelector(
const flattened = flattenStatementStats(statements);
const results = _.filter(
flattened,
filterByRouterParamsPredicate(props.match, internalAppNamePrefix),
filterByRouterParamsPredicate(
props.match,
props.location,
internalAppNamePrefix,
),
);
const statement = getMatchParamByName(props.match, statementAttr);
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
formatNumberForDisplay,
calculateTotalWorkload,
unique,
queryByName,
} from "src/util";
import { Loading } from "src/loading";
import { Button } from "src/button";
Expand Down Expand Up @@ -62,6 +63,7 @@ import { NodeSummaryStats } from "../nodes";
import { UIConfigState } from "../store/uiConfig";
import moment, { Moment } from "moment";
import { StatementsRequest } from "src/api/statementsApi";
import { aggregatedTsAttr } from "../util/constants";

const { TabPane } = Tabs;

Expand Down Expand Up @@ -128,6 +130,7 @@ export type NodesSummary = {

export interface StatementDetailsDispatchProps {
refreshStatements: (req?: StatementsRequest) => void;
invalidateStatements: () => void;
refreshStatementDiagnosticsRequests: () => void;
refreshNodes: () => void;
refreshNodesLiveness: () => void;
Expand Down Expand Up @@ -164,7 +167,15 @@ const summaryCardStylesCx = classNames.bind(summaryCardStyles);
function statementsRequestFromProps(
props: StatementDetailsProps,
): cockroach.server.serverpb.StatementsRequest | null {
if (props.isTenant || props.dateRange == null) return null;
// If there was no aggregated_ts requested in the search parameters, we show
// the latest statement from in-memory stats. This is mostly relevant for displaying
// statement details from active queries from the session details page.
const aggregatedTs = queryByName(props.location, aggregatedTsAttr);
if (aggregatedTs == null)
return new cockroach.server.serverpb.StatementsRequest({
combined: false,
});

return new cockroach.server.serverpb.StatementsRequest({
combined: true,
start: Long.fromNumber(props.dateRange[0].unix()),
Expand Down Expand Up @@ -351,6 +362,10 @@ export class StatementDetails extends React.Component<
};

componentDidMount() {
const req = statementsRequestFromProps(this.props);
if (!req.combined) {
this.props.invalidateStatements();
}
this.refreshStatements();
if (!this.props.isTenant) {
this.props.refreshStatementDiagnosticsRequests();
Expand All @@ -368,6 +383,16 @@ export class StatementDetails extends React.Component<
}
}

componentWillUnmount() {
// If we had to fetch in-memory statement statistics for this statement,
// we should invalidate the statements response in the cache, since the other
// pages will expect combined statements.
const req = statementsRequestFromProps(this.props);
if (!req.combined) {
this.props.invalidateStatements();
}
}

onTabChange = (tabId: string) => {
const { history } = this.props;
const searchParams = new URLSearchParams(history.location.search);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const mapDispatchToProps = (
dispatch: Dispatch,
): StatementDetailsDispatchProps => ({
refreshStatements: () => dispatch(statementsActions.refresh()),
invalidateStatements: () => dispatch(statementsActions.invalidated()),
refreshStatementDiagnosticsRequests: () =>
dispatch(statementDiagnosticsActions.refresh()),
refreshNodes: () => dispatch(nodesActions.refresh()),
Expand Down
Loading

0 comments on commit 512cfe3

Please sign in to comment.