Skip to content

Commit

Permalink
pkg/ui: Add span details component to tracez_v2.
Browse files Browse the repository at this point in the history
This PR adds the "span details" and "raw trace" screens to the new tracez_v2
page. Previously, only the "snapshot details (i.e. list spans) screen existed
on that page. The detailed span information page is where users will go to dig
in to the specifics of a span. In particular, we'll display accumulated data
on child spans of that page, helping navigate to problem segments more easily.

Note that this PR brings the v2 page up to feature parity with the v1 page.
Though the redesign is not yet complete, we'll be able to turn down the v1
page after this lands.

This PR also modifies the route structure a little to encapsulate better.

Release note: None
  • Loading branch information
benbardin committed Jan 3, 2023
1 parent 5ebb4ca commit c793ceb
Show file tree
Hide file tree
Showing 12 changed files with 991 additions and 209 deletions.
43 changes: 38 additions & 5 deletions pkg/ui/workspaces/cluster-ui/src/api/tracezApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

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

import Long from "long";
export type ListTracingSnapshotsRequest =
cockroach.server.serverpb.ListTracingSnapshotsRequest;
export type ListTracingSnapshotsResponse =
Expand All @@ -29,9 +29,15 @@ export type GetTracingSnapshotResponse =
export type Span = cockroach.server.serverpb.ITracingSpan;
export type Snapshot = cockroach.server.serverpb.ITracingSnapshot;

export type GetTraceRequest = cockroach.server.serverpb.GetTraceRequest;
export const GetTraceRequest = cockroach.server.serverpb.GetTraceRequest;
export type GetTraceResponse = cockroach.server.serverpb.GetTraceResponse;

export const SetTraceRecordingTypeRequest =
cockroach.server.serverpb.SetTraceRecordingTypeRequest;
export type SetTraceRecordingTypeResponse =
cockroach.server.serverpb.SetTraceRecordingTypeResponse;
export type RecordingMode = cockroach.util.tracing.tracingpb.RecordingMode;

const API_PREFIX = "_admin/v1";

export function listTracingSnapshots(
Expand All @@ -58,6 +64,8 @@ export function takeTracingSnapshot(
);
}

// This is getting plugged into our redux libraries, which want calls with a
// single argument. So wrap the two arguments in a request object.
export function getTracingSnapshot(req: {
nodeID: string;
snapshotID: number;
Expand All @@ -70,14 +78,39 @@ export function getTracingSnapshot(req: {
);
}

export function getTraceForSnapshot(req: {
// This is getting plugged into our redux libraries, which want calls with a
// single argument. So wrap the two arguments in a request object.
export function getRawTrace(req: {
nodeID: string;
req: GetTraceRequest;
snapshotID: number;
traceID: Long;
}): Promise<GetTraceResponse> {
const rpcReq = new GetTraceRequest({
snapshot_id: Long.fromNumber(req.snapshotID),
trace_id: req.traceID,
});
return fetchData(
cockroach.server.serverpb.GetTraceResponse,
`${API_PREFIX}/traces?remote_node_id=${req.nodeID}`,
cockroach.server.serverpb.GetTraceRequest,
req.req as any,
rpcReq as any,
);
}

export function setTraceRecordingType(
nodeID: string,
traceID: Long,
recordingMode: RecordingMode,
): Promise<SetTraceRecordingTypeResponse> {
const req = new SetTraceRecordingTypeRequest({
trace_id: traceID,
recording_mode: recordingMode,
});
return fetchData(
cockroach.server.serverpb.SetTraceRecordingTypeResponse,
// TODO(davidh): Consider making this endpoint just POST to `/traces/{trace_ID}`
`${API_PREFIX}/settracerecordingtype?remote_node_id=${nodeID}`,
cockroach.server.serverpb.SetTraceRecordingTypeRequest,
req as any,
);
}
63 changes: 62 additions & 1 deletion pkg/ui/workspaces/cluster-ui/src/tracez/snapshot.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
display: flex;
flex-flow: column;
height: 100%;
font-family: $font-family--base;
}

.no-results {
Expand Down Expand Up @@ -66,6 +67,7 @@

.table-title-time {
width: 20ch;
margin-right: 4px;
}

.tag-group {
Expand Down Expand Up @@ -104,7 +106,66 @@
padding-top: 1px;
}

.icon-gray {
fill: lightgray;
height: 10px;
width: 10px;
padding-top: 1px;
}

.section {
flex: 0 0 auto;
padding: 12px 24px 140px 0px;
padding: 12px 24px 12px 0px;
}

.span-section {
background-color: $colors--neutral-0;
padding: 12px;
margin-right: 12px;
margin-bottom: 12px;
}

.bottom-padding {
padding-bottom: 140px;
}

.span-snapshot-key {
font-family: $font-family--bold;
}

.span-snapshot-key-value {
padding-bottom: 4px;
}

.span-snapshot-column {
min-width: fit-content;
}

.span-snapshot-columns {
display: flex;
flex-direction: row;
gap: 24px;
}

.span-header-columns {
display: flex;
flex-direction: row;
padding-bottom: 12px;
padding-right: 12px;
align-items: center;
}

.span-details-title {
color: $colors--neutral-7;
font-family: $font-family--semi-bold;
font-style: normal;
font-stretch: normal;
font-size: 20px;
padding-bottom: 0px;
margin-bottom: 0px;
width: 100%;
}

.span-details-raw-trace-button {
flex-shrink: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2022 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import { GetTraceResponse } from "src/api";
import Long from "long";
import { Loading } from "src/loading";
import React, { useEffect } from "react";
import classNames from "classnames/bind";
import styles from "../snapshot.module.scss";
const cx = classNames.bind(styles);

export const RawTraceComponent: React.FC<{
nodeID: string;
snapshotID: number;
traceID: Long;
rawTrace: GetTraceResponse;
rawTraceLoading: boolean;
rawTraceError?: Error;
refreshRawTrace: (req: {
nodeID: string;
snapshotID: number;
traceID: Long;
}) => void;
}> = props => {
const {
nodeID,
snapshotID,
traceID,
rawTrace,
rawTraceLoading,
rawTraceError,
refreshRawTrace,
} = props;

useEffect(() => {
if (!(nodeID && snapshotID && traceID)) {
return;
}
refreshRawTrace({
nodeID,
snapshotID,
traceID,
});
}, [nodeID, snapshotID, traceID, refreshRawTrace]);

return (
<Loading
loading={rawTraceLoading}
page={"raw trace"}
error={rawTraceError}
render={() => {
return (
<>
<section
data-testid="raw-trace-component"
className={cx("span-section")}
>
<pre>{rawTrace?.serialized_recording}</pre>
</section>
<div className={cx("bottom-padding")} />
</>
);
}}
/>
);
};
161 changes: 161 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/tracez/snapshot/snapshotComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright 2022 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import { Helmet } from "react-helmet";
import { commonStyles } from "src/common";
import { PageConfig, PageConfigItem } from "src/pageConfig";
import { Button, Icon } from "@cockroachlabs/ui-components";
import { Dropdown } from "src/dropdown";
import { Loading } from "src/loading";
import { SpanTable } from "./spanTable";
import React, { useMemo } from "react";
import classNames from "classnames/bind";
import styles from "../snapshot.module.scss";
import { TimestampToMoment } from "src/util";
import { SortSetting } from "src/sortedtable";
import {
GetTracingSnapshotResponse,
ListTracingSnapshotsResponse,
} from "src/api";
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import Long from "long";
const cx = classNames.bind(styles);

export const SnapshotComponent: React.FC<{
sort: SortSetting;
changeSortSetting: (value: SortSetting) => void;
nodes?: cockroach.server.status.statuspb.INodeStatus[];
nodeID: string;
onNodeSelected: (_: string) => void;
snapshots: ListTracingSnapshotsResponse;
snapshotID: number;
snapshot: GetTracingSnapshotResponse;
onSnapshotSelected: (_: number) => void;
isLoading: boolean;
error: Error;
spanDetailsURL: (_: Long) => string;
takeAndLoadSnapshot: () => void;
}> = props => {
const {
sort,
changeSortSetting,
nodes,
nodeID,
onNodeSelected,
snapshots,
snapshotID,
snapshot,
onSnapshotSelected,
isLoading,
error,
spanDetailsURL,
takeAndLoadSnapshot,
} = props;

const snapshotsAsJson = JSON.stringify(snapshots);

const [snapshotItems, snapshotName] = useMemo(() => {
if (!snapshots) {
return [[], ""];
}
let selectedName = "";
const items = snapshots.snapshots.map(snapshotInfo => {
const id = snapshotInfo.snapshot_id.toNumber();
const time = TimestampToMoment(snapshotInfo.captured_at).format(
"MMM D, YYYY [at] HH:mm:ss",
);
const out = {
name: "Snapshot " + id + ": " + time,
value: id,
};
if (id === snapshotID) {
selectedName = out.name;
}
return out;
});
return [items, selectedName];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [snapshotsAsJson, snapshotID]);

const [nodeItems, nodeName] = useMemo(() => {
if (!nodes) {
return [[], ""];
}
let selectedName = "";
const items = nodes.map(node => {
const id = node.desc.node_id.toString();
const out = {
name: "Node " + id,
value: id,
};
if (id === nodeID) {
selectedName = out.name;
}
return out;
});
return [items, selectedName];
}, [nodes, nodeID]);

return (
<div className={cx("snapshots-page")}>
<Helmet title="Snapshots" />
<h3
data-testid="snapshot-component-title"
className={commonStyles("base-heading")}
>
Snapshots
</h3>
<div>
<PageConfig>
<PageConfigItem>
<Button onClick={takeAndLoadSnapshot} intent="secondary">
<Icon iconName="Download" /> Take snapshot
</Button>
</PageConfigItem>
<PageConfigItem>
<Dropdown items={nodeItems} onChange={onNodeSelected}>
{nodeName}
</Dropdown>
</PageConfigItem>
{snapshotItems.length > 0 && (
<PageConfigItem>
<Dropdown<number>
items={snapshotItems}
onChange={onSnapshotSelected}
>
{snapshotName}
</Dropdown>
</PageConfigItem>
)}
</PageConfig>
</div>
<section className={cx("section")}>
{snapshotID ? (
<Loading
loading={isLoading}
page={"snapshots"}
error={error}
render={() => (
<SpanTable
snapshot={snapshot?.snapshot}
setSort={changeSortSetting}
sort={sort}
spanDetailsURL={spanDetailsURL}
/>
)}
/>
) : (
"No snapshots found on this node."
)}
</section>
<div className={cx("bottom-padding")} />
</div>
);
};
Loading

0 comments on commit c793ceb

Please sign in to comment.