Skip to content

Commit 1baf38e

Browse files
authored
Improve performance of experiment column collection (#4353)
1 parent abfc8d5 commit 1baf38e

File tree

7 files changed

+125
-67
lines changed

7 files changed

+125
-67
lines changed

extension/src/experiments/columns/collect/deps.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export const collectDeps = (acc: ColumnAccumulator, data: ExpData) => {
1818
return
1919
}
2020
for (const [file, { hash }] of Object.entries(deps)) {
21+
const path = buildDepPath(file)
22+
if (acc.collected.has(path)) {
23+
return
24+
}
25+
acc.collected.add(path)
26+
2127
const pathArray = getPathArray(file)
2228
const label = pathArray.pop() as string
2329

@@ -27,7 +33,6 @@ export const collectDeps = (acc: ColumnAccumulator, data: ExpData) => {
2733
'/',
2834
label
2935
)
30-
const path = buildDepPath(file)
3136

3237
mergeAncestors(
3338
acc,

extension/src/experiments/columns/collect/index.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ const mockedGetConfigValue = jest.mocked(getConfigValue)
2222
mockedGetConfigValue.mockImplementation(() => 5)
2323

2424
describe('collectColumns', () => {
25-
it('should return a value equal to the columns fixture when given the output fixture', () => {
26-
const columns = collectColumns(outputFixture)
25+
it('should return a value equal to the columns fixture when given the output fixture', async () => {
26+
const columns = await collectColumns(outputFixture)
2727
expect(columns).toStrictEqual(columnsFixture)
2828
})
2929

30-
it('should output both params and metrics when both are present', () => {
31-
const columns = collectColumns(
30+
it('should output both params and metrics when both are present', async () => {
31+
const columns = await collectColumns(
3232
generateTestExpShowOutput({
3333
metrics: {
3434
1: {
@@ -50,8 +50,8 @@ describe('collectColumns', () => {
5050
expect(metrics).toBeDefined()
5151
})
5252

53-
it('should omit params when none exist in the source data', () => {
54-
const columns = collectColumns(
53+
it('should omit params when none exist in the source data', async () => {
54+
const columns = await collectColumns(
5555
generateTestExpShowOutput({
5656
metrics: {
5757
1: {
@@ -66,13 +66,13 @@ describe('collectColumns', () => {
6666
expect(metrics).toBeDefined()
6767
})
6868

69-
it('should return an empty array if no params and metrics are provided', () => {
70-
const columns = collectColumns(generateTestExpShowOutput({}))
69+
it('should return an empty array if no params and metrics are provided', async () => {
70+
const columns = await collectColumns(generateTestExpShowOutput({}))
7171
expect(columns).toStrictEqual([])
7272
})
7373

74-
it('should aggregate multiple different field names', () => {
75-
const columns = collectColumns(
74+
it('should aggregate multiple different field names', async () => {
75+
const columns = await collectColumns(
7676
generateTestExpShowOutput(
7777
{
7878
params: {
@@ -127,8 +127,8 @@ describe('collectColumns', () => {
127127
])
128128
})
129129

130-
it('should create concatenated columns for nesting deeper than 5', () => {
131-
const columns = collectColumns(
130+
it('should create concatenated columns for nesting deeper than 5', async () => {
131+
const columns = await collectColumns(
132132
generateTestExpShowOutput({
133133
params: {
134134
'params.yaml': {

extension/src/experiments/columns/collect/index.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,50 @@ const collectFromExperiment = (
2828

2929
const { data } = experiment
3030
if (data) {
31-
collectMetricsAndParams(acc, data)
32-
collectDeps(acc, data)
31+
return Promise.all([
32+
collectMetricsAndParams(acc, data),
33+
collectDeps(acc, data)
34+
])
3335
}
3436
}
3537

36-
const collectFromExperiments = (
38+
const collectFromExperiments = async (
3739
acc: ColumnAccumulator,
3840
experiments?: ExpRange[] | null
3941
) => {
4042
if (!experiments) {
4143
return
4244
}
45+
46+
const promises = []
4347
for (const { revs } of experiments) {
44-
collectFromExperiment(acc, revs[0])
48+
promises.push(collectFromExperiment(acc, revs[0]))
4549
}
50+
51+
await Promise.all(promises)
4652
}
4753

48-
export const collectColumns = (output: ExpShowOutput): Column[] => {
49-
const acc: ColumnAccumulator = {}
54+
export const collectColumns = async (
55+
output: ExpShowOutput
56+
): Promise<Column[]> => {
57+
const acc: ColumnAccumulator = { collected: new Set(), columns: {} }
5058

51-
acc.timestamp = timestampColumn
59+
acc.columns.timestamp = timestampColumn
5260

61+
const promises = []
5362
for (const expState of output) {
5463
if (experimentHasError(expState)) {
5564
continue
5665
}
5766

58-
collectFromExperiment(acc, expState)
59-
collectFromExperiments(acc, expState.experiments)
67+
promises.push(
68+
collectFromExperiment(acc, expState),
69+
collectFromExperiments(acc, expState.experiments)
70+
)
6071
}
72+
await Promise.all(promises)
6173

62-
const columns = Object.values(acc)
74+
const columns = Object.values(acc.columns)
6375
const hasNoData = isEqual(columns, [timestampColumn])
6476

6577
return hasNoData ? [] : columns

extension/src/experiments/columns/collect/metricsAndParams.ts

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,35 @@ const collectMetricOrParam = (
2929
label: string,
3030
value: Value
3131
) => {
32+
const path = buildMetricOrParamPath(type, ...pathArray, label)
33+
if (acc.collected.has(path)) {
34+
return
35+
}
36+
acc.collected.add(path)
37+
3238
const { limitedDepthAncestors, limitedDepthLabel } = limitAncestorDepth(
3339
pathArray,
3440
METRIC_PARAM_SEPARATOR,
3541
FILE_SEPARATOR,
3642
label
3743
)
38-
const path = buildMetricOrParamPath(type, ...limitedDepthAncestors, label)
44+
const limitedPath = buildMetricOrParamPath(
45+
type,
46+
...limitedDepthAncestors,
47+
limitedDepthLabel
48+
)
49+
3950
mergeAncestors(
4051
acc,
4152
type,
42-
path,
53+
limitedPath,
4354
limitedDepthAncestors,
4455
(...pathArray: string[]) => buildMetricOrParamPath(type, ...pathArray)
4556
)
4657

4758
collectColumn(
4859
acc,
49-
buildMetricOrParamPath(
50-
type,
51-
...(limitedDepthAncestors || pathArray),
52-
label
53-
),
60+
limitedPath,
5461
buildMetricOrParamPath(type, ...limitedDepthAncestors),
5562
[type, ...pathArray, label],
5663
limitedDepthLabel,
@@ -89,17 +96,28 @@ const walkMetricsOrParamsFile = (
8996
}
9097
}
9198

99+
const collectMetricsOrParams = (
100+
acc: ColumnAccumulator,
101+
type: ColumnType,
102+
metricsOrParams: MetricsOrParams | null
103+
): void => {
104+
if (!metricsOrParams) {
105+
return
106+
}
107+
walkMetricsOrParamsFile(acc, type, metricsOrParams)
108+
}
109+
92110
export const collectMetricsAndParams = (
93111
acc: ColumnAccumulator,
94112
data: ExpData
95113
) => {
96114
const { metrics, params } = data
97-
if (metrics) {
98-
walkMetricsOrParamsFile(acc, ColumnType.METRICS, metrics)
99-
}
100-
if (params) {
101-
walkMetricsOrParamsFile(acc, ColumnType.PARAMS, params)
102-
}
115+
return Promise.all([
116+
// eslint-disable-next-line sonarjs/no-use-of-empty-return-value
117+
collectMetricsOrParams(acc, ColumnType.METRICS, metrics),
118+
// eslint-disable-next-line sonarjs/no-use-of-empty-return-value
119+
collectMetricsOrParams(acc, ColumnType.PARAMS, params)
120+
])
103121
}
104122

105123
const collectChange = (

extension/src/experiments/columns/collect/util.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { Column, ColumnType } from '../../webview/contract'
22
import { Value } from '../../../cli/dvc/contract'
33
import { ConfigKey, getConfigValue } from '../../../vscode/config'
44

5-
export type ColumnAccumulator = Record<string, Column>
5+
export type ColumnAccumulator = {
6+
columns: Record<string, Column>
7+
collected: Set<string>
8+
}
69

710
const joinPathArray = (
811
pathSegments: string[],
@@ -76,10 +79,10 @@ const mergeParentColumnByPath = (
7679
parentPath: string,
7780
label: string
7881
) => {
79-
if (acc[path]) {
80-
acc[path].hasChildren = true
82+
if (acc.columns[path]) {
83+
acc.columns[path].hasChildren = true
8184
} else {
82-
acc[path] = {
85+
acc.columns[path] = {
8386
hasChildren: true,
8487
label,
8588
parentPath,
@@ -106,17 +109,15 @@ export const mergeAncestors = (
106109
limitedDepthAncestors: string[],
107110
join: (...pathArray: string[]) => string
108111
) => {
109-
if (!acc[path]) {
110-
for (let i = 1; i <= limitedDepthAncestors.length; i++) {
111-
const pathArray = limitedDepthAncestors.slice(0, i)
112-
mergeParentColumnByPath(
113-
acc,
114-
type,
115-
join(...pathArray),
116-
join(...pathArray.slice(0, -1)),
117-
pathArray[pathArray.length - 1]
118-
)
119-
}
112+
for (let i = 1; i <= limitedDepthAncestors.length; i++) {
113+
const pathArray = limitedDepthAncestors.slice(0, i)
114+
mergeParentColumnByPath(
115+
acc,
116+
type,
117+
join(...pathArray),
118+
join(...pathArray.slice(0, -1)),
119+
pathArray[pathArray.length - 1]
120+
)
120121
}
121122
}
122123

@@ -146,8 +147,11 @@ export const collectColumn = (
146147
label: string,
147148
value: Value
148149
) => {
149-
if (acc[path]) {
150-
return
151-
}
152-
acc[path] = buildValueColumn(path, parentPath, pathArray, label, value)
150+
acc.columns[path] = buildValueColumn(
151+
path,
152+
parentPath,
153+
pathArray,
154+
label,
155+
value
156+
)
153157
}

extension/src/experiments/columns/model.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ describe('ColumnsModel', () => {
133133
)
134134
await model.transformAndSet(deeplyNestedOutputFixture)
135135
expect(mockedGetConfigValue).toHaveBeenCalled()
136+
136137
expect(model.getSelected()).toStrictEqual(deeplyNestedColumnsWithHeightOf1)
137138
})
138139

extension/src/test/fixtures/expShow/deeplyNested/maxHeight.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -352,20 +352,21 @@ export const deeplyNestedColumnsWithHeightOf2: Column[] = [
352352
export const deeplyNestedColumnsWithHeightOf1: Column[] = [
353353
timestampColumn,
354354
{
355+
firstValueType: 'string',
355356
hasChildren: false,
356357
label: 'params.yaml:nested1.doubled',
357358
parentPath: 'params',
358-
path: 'params:doubled',
359+
path: 'params:params.yaml:nested1.doubled',
359360
pathArray: ['params', 'params.yaml', 'nested1', 'doubled'],
360-
type: ColumnType.PARAMS,
361-
firstValueType: 'string'
361+
type: ColumnType.PARAMS
362362
},
363363
{
364+
firstValueType: 'string',
364365
hasChildren: false,
365366
label:
366367
'params.yaml:nested1.nested2.nested3.nested4.nested5.nested6.nested7',
367368
parentPath: 'params',
368-
path: 'params:nested7',
369+
path: 'params:params.yaml:nested1.nested2.nested3.nested4.nested5.nested6.nested7',
369370
pathArray: [
370371
'params',
371372
'params.yaml',
@@ -377,14 +378,14 @@ export const deeplyNestedColumnsWithHeightOf1: Column[] = [
377378
'nested6',
378379
'nested7'
379380
],
380-
type: ColumnType.PARAMS,
381-
firstValueType: 'string'
381+
type: ColumnType.PARAMS
382382
},
383383
{
384+
firstValueType: 'string',
384385
hasChildren: false,
385386
label: 'params.yaml:nested1.nested2.nested3.nested4.nested5b.nested6',
386387
parentPath: 'params',
387-
path: 'params:nested6',
388+
path: 'params:params.yaml:nested1.nested2.nested3.nested4.nested5b.nested6',
388389
pathArray: [
389390
'params',
390391
'params.yaml',
@@ -395,16 +396,33 @@ export const deeplyNestedColumnsWithHeightOf1: Column[] = [
395396
'nested5b',
396397
'nested6'
397398
],
398-
type: ColumnType.PARAMS,
399-
firstValueType: 'string'
399+
type: ColumnType.PARAMS
400+
},
401+
{
402+
firstValueType: 'string',
403+
hasChildren: false,
404+
label: 'params.yaml:nested1.nested2.nested3.nested4.nested5b.doubled',
405+
parentPath: 'params',
406+
path: 'params:params.yaml:nested1.nested2.nested3.nested4.nested5b.doubled',
407+
pathArray: [
408+
'params',
409+
'params.yaml',
410+
'nested1',
411+
'nested2',
412+
'nested3',
413+
'nested4',
414+
'nested5b',
415+
'doubled'
416+
],
417+
type: ColumnType.PARAMS
400418
},
401419
{
420+
firstValueType: 'number',
402421
hasChildren: false,
403422
label: 'params.yaml:outlier',
404423
parentPath: 'params',
405-
path: 'params:outlier',
424+
path: 'params:params.yaml:outlier',
406425
pathArray: ['params', 'params.yaml', 'outlier'],
407-
type: ColumnType.PARAMS,
408-
firstValueType: 'number'
426+
type: ColumnType.PARAMS
409427
}
410428
]

0 commit comments

Comments
 (0)