Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions coderd/database/dump.sql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- Leave the enum as is, dropping enum values is not easily supported.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Currently have 2 graph stages in a workspace build
ALTER TYPE provisioner_job_timing_stage ADD VALUE IF NOT EXISTS 'graph_second';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dislike this name. I have another PR where I add a stage_seq number.

We only have the graph stage duplicated, and it should be the only one. So probably best to just hard code it 😢

(I also tried to remove the second graph, but that is really challenging with quotas)

13 changes: 8 additions & 5 deletions coderd/database/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions codersdk/workspacebuilds.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,11 @@ type TimingStage string

const (
// Based on ProvisionerJobTimingStage
TimingStageInit TimingStage = "init"
TimingStagePlan TimingStage = "plan"
TimingStageGraph TimingStage = "graph"
TimingStageApply TimingStage = "apply"
TimingStageInit TimingStage = "init"
TimingStagePlan TimingStage = "plan"
TimingStageGraph TimingStage = "graph"
TimingStageGraphSecond TimingStage = "graph_second"
TimingStageApply TimingStage = "apply"
// Based on WorkspaceAgentScriptTimingStage
TimingStageStart TimingStage = "start"
TimingStageStop TimingStage = "stop"
Expand Down
2 changes: 2 additions & 0 deletions provisioner/terraform/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,9 @@ func (e *executor) apply(
}

// `terraform show` & `terraform graph`
endGraph := e.timings.startStage(database.ProvisionerJobTimingStageGraphSecond)
state, err := e.stateResources(ctx, killCtx)
endGraph(err)
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion provisioner/terraform/timings.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) {
return
}

s.stage = t.stage
// Only set the stage if it hasn't already been set on the span.
// Explicitly set stage takes precedence.
if s.stage == "" {
s.stage = t.stage
}
ts = dbtime.Time(ts.UTC())

switch s.kind {
Expand Down
2 changes: 2 additions & 0 deletions site/src/api/typesGenerated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

112 changes: 62 additions & 50 deletions site/src/modules/workspaces/WorkspaceTiming/StagesChart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { TimingStage } from "api/typesGenerated";
import { T } from "lodash/fp";
import { CircleAlertIcon, InfoIcon } from "lucide-react";
import type { FC } from "react";
import { Bar, ClickableBar } from "./Chart/Bar";
Expand Down Expand Up @@ -33,6 +34,10 @@ export type Stage = {
* The name is used to identify the stage.
*/
name: TimingStage;
/**
* The graph stage actually runs twice, the second with another name.
*/
alternativeNames?: TimingStage[];
/**
* The value to display in the stage label. This can differ from the stage
* name to provide more context or clarity.
Expand All @@ -48,7 +53,7 @@ export type Stage = {
tooltip: Omit<TooltipProps, "children">;
};

type StageTiming = {
export type StageTiming = {
stage: Stage;
/**
* Represents the number of resources included in this stage that can be
Expand All @@ -61,7 +66,7 @@ type StageTiming = {
* duration of the stage and to position the stage within the chart. This can
* be undefined if a stage has no timing data.
*/
range: TimeRange | undefined;
ranges: TimeRange[] | undefined;
/**
* Display an error icon within the bar to indicate when a stage has failed.
* This is used in the agent scripts stage.
Expand All @@ -79,7 +84,10 @@ export const StagesChart: FC<StagesChartProps> = ({
onSelectStage,
}) => {
const totalRange = mergeTimeRanges(
timings.map((t) => t.range).filter((t) => t !== undefined),
timings
.map((t) => t.ranges)
.filter((t) => t !== undefined)
.flat(),
);
const totalTime = calcDuration(totalRange);
const [ticks, scale] = makeTicks(totalTime);
Expand Down Expand Up @@ -125,11 +133,12 @@ export const StagesChart: FC<StagesChartProps> = ({
const stageTimings = timings.filter(
(t) => t.stage.section === section,
);

return (
<XAxisSection key={section}>
{stageTimings.map((t) => {
// If the stage has no timing data, we just want to render an empty row
if (t.range === undefined) {
if (t.ranges === undefined) {
return (
<XAxisRow
key={t.stage.name}
Expand All @@ -138,53 +147,55 @@ export const StagesChart: FC<StagesChartProps> = ({
);
}

const value = calcDuration(t.range);
const offset = calcOffset(t.range, totalRange);
const validDuration = value > 0 && !Number.isNaN(value);
return t.ranges?.map((range, index) => {
const value = calcDuration(range);
const offset = calcOffset(range, totalRange);
const validDuration = value > 0 && !Number.isNaN(value);

return (
<XAxisRow
key={t.stage.name}
yAxisLabelId={encodeURIComponent(t.stage.name)}
>
{/** We only want to expand stages with more than one resource */}
{t.visibleResources > 1 ? (
<ClickableBar
aria-label={`View ${t.stage.label} details`}
scale={scale}
value={value}
offset={offset}
onClick={() => {
onSelectStage(t.stage);
}}
>
{t.error && (
<CircleAlertIcon
className="size-icon-sm"
css={{
color: "#F87171",
marginRight: 4,
}}
/>
)}
<Blocks count={t.visibleResources} />
</ClickableBar>
) : (
<Bar scale={scale} value={value} offset={offset} />
)}
{validDuration ? (
<span>{formatTime(value)}</span>
) : (
<span
css={(theme) => ({
color: theme.palette.error.main,
})}
>
Invalid
</span>
)}
</XAxisRow>
);
return (
<XAxisRow
key={t.stage.name + index}
yAxisLabelId={encodeURIComponent(t.stage.name)}
>
{/** We only want to expand stages with more than one resource */}
{t.visibleResources > 1 ? (
<ClickableBar
aria-label={`View ${t.stage.label} details`}
scale={scale}
value={value}
offset={offset}
onClick={() => {
onSelectStage(t.stage);
}}
>
{t.error && (
<CircleAlertIcon
className="size-icon-sm"
css={{
color: "#F87171",
marginRight: 4,
}}
/>
)}
<Blocks count={t.visibleResources} />
</ClickableBar>
) : (
<Bar scale={scale} value={value} offset={offset} />
)}
{validDuration ? (
<span>{formatTime(value)}</span>
) : (
<span
css={(theme) => ({
color: theme.palette.error.main,
})}
>
Invalid
</span>
)}
</XAxisRow>
);
});
})}
</XAxisSection>
);
Expand Down Expand Up @@ -247,6 +258,7 @@ export const provisioningStages: Stage[] = [
},
{
name: "graph",
alternativeNames: ["graph_second"],
label: "graph",
section: "provisioning",
tooltip: {
Expand Down
24 changes: 18 additions & 6 deletions site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
provisioningStages,
type Stage,
StagesChart,
type StageTiming,
} from "./StagesChart";

type TimingView =
Expand Down Expand Up @@ -130,14 +131,25 @@ export const WorkspaceTimings: FC<WorkspaceTimingsProps> = ({
<div css={styles.collapseBody}>
{view.name === "default" && (
<StagesChart
timings={stages.map((s) => {
timings={stages.map((s): StageTiming => {
const stageTimings = timings.filter(
(t) => t.stage === s.name,
// graph has 2 stages, `graph` and `graph_second`
(t) =>
t.stage === s.name ||
s.alternativeNames?.includes(t.stage),
);
const stageRange =
stageTimings.length === 0


const keyedRanges = stageTimings.reduce<Record<string, TimeRange[]>>(
(acc, t) => {
acc[t.stage] = acc[t.stage] || [];
acc[t.stage].push(toTimeRange(t));
return acc;
}, {});

const stageRanges = stageTimings.length === 0
? undefined
: mergeTimeRanges(stageTimings.map(toTimeRange));
: Object.entries(keyedRanges).map(([_, ranges]) => mergeTimeRanges(ranges));

// Prevent users from inspecting internal coder resources in
// provisioner timings because they were not useful to the
Expand All @@ -158,7 +170,7 @@ export const WorkspaceTimings: FC<WorkspaceTimingsProps> = ({

return {
stage: s,
range: stageRange,
ranges: stageRanges,
visibleResources: visibleResources.length,
error: stageTimings.some(
(t) => "status" in t && t.status === "exit_failure",
Expand Down
9 changes: 9 additions & 0 deletions site/src/modules/workspaces/WorkspaceTiming/storybookData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,15 @@ export const WorkspaceTimingsResponse: WorkspaceBuildTimings = {
action: "create",
resource: "coder_metadata.container_info[0]",
},
{
job_id: "86fd4143-d95f-4602-b464-1149ede62269",
started_at: "2024-10-14T11:30:53.693767Z",
ended_at: "2024-10-14T11:30:58.693767Z",
stage: "graph_second",
source: "terraform",
action: "terraform",
resource: "coder_graph_second_stage",
},
],
agent_script_timings: [
{
Expand Down
Loading