function.ts 11.2 KB
Newer Older
chicm-ms's avatar
chicm-ms committed
1
import * as JSON5 from 'json5';
Lijiao's avatar
Lijiao committed
2
import axios from 'axios';
Lijiaoa's avatar
Lijiaoa committed
3
import { IContextualMenuProps } from '@fluentui/react';
Lijiao's avatar
Lijiao committed
4
import { MANAGER_IP } from './const';
5
import { EXPERIMENT } from './datamodel';
Lijiaoa's avatar
Lijiaoa committed
6
import { MetricDataRecord, FinalType, TableObj, Tensorboard } from './interface';
Lijiao's avatar
Lijiao committed
7

8
9
10
function getPrefix(): string | undefined {
    const pathName = window.location.pathname;
    let newPathName = pathName;
11
12
13
14
15
16
17
    const pathArr: string[] = ['/oview', '/detail', '/experiment'];
    pathArr.forEach(item => {
        if (pathName.endsWith(item)) {
            newPathName = pathName.replace(item, '');
        }
    });
    return newPathName === '' || newPathName === '/' ? undefined : newPathName;
18
19
}

20
async function requestAxios(url: string): Promise<any> {
Lijiaoa's avatar
Lijiaoa committed
21
22
23
24
25
26
27
28
29
30
31
32
    const response = await axios.get(url);
    if (response.status === 200) {
        if (response.data.error !== undefined) {
            throw new Error(`API ${url} ${response.data.error}`);
        } else {
            return response.data as any;
        }
    } else {
        throw new Error(`API ${url} ${response.status} error`);
    }
}

Lijiao's avatar
Lijiao committed
33
const convertTime = (num: number): string => {
34
35
36
    if (num <= 0) {
        return '0';
    }
Lijiao's avatar
Lijiao committed
37
38
39
40
    if (num % 3600 === 0) {
        return num / 3600 + 'h';
    } else {
        const hour = Math.floor(num / 3600);
41
        const min = Math.floor((num / 60) % 60);
Lijiao's avatar
Lijiao committed
42
43
44
45
46
        return hour > 0 ? `${hour}h ${min}min` : `${min}min`;
    }
};

// trial's duration, accurate to seconds for example 10min 30s
47
48
49
50
51
52
const convertDuration = (seconds: number): string => {
    let str = '';

    const d = Math.floor(seconds / (24 * 3600));
    if (d > 0) {
        str += `${d}d `;
53
    }
54
55
56
57
58
    seconds -= d * 24 * 3600;

    const h = Math.floor(seconds / 3600);
    if (h > 0) {
        str += `${h}h `;
59
    }
60
61
62
63
64
    seconds -= h * 3600;

    const m = Math.floor(seconds / 60);
    if (m > 0) {
        str += `${m}m `;
65
    }
66
67
68
69
    seconds -= m * 60;

    if (seconds > 0) {
        str += `${Math.floor(seconds)}s`;
Lijiao's avatar
Lijiao committed
70
    }
71
    return str ? str : '0s';
Lijiao's avatar
Lijiao committed
72
73
};

74
75
76
77
78
79
80
81
82
83
84
85
// according the unit(d,h,m) to convert duration
function convertTimeAsUnit(unit: string, value: number): number {
    let divisor = 1;
    if (unit === 'h') {
        divisor = 3600;
    } else if (unit === 'm') {
        divisor = 60;
    } else {
        divisor = 24 * 3600;
    }
    return value / divisor;
}
86
function parseMetrics(metricData: string): any {
Lijiaoa's avatar
Lijiaoa committed
87
    if (metricData.includes('NaN') || metricData.includes('Infinity')) {
88
89
90
91
92
93
        return JSON5.parse(JSON5.parse(metricData));
    } else {
        return JSON.parse(JSON.parse(metricData));
    }
}

94
95
const isArrayType = (list: any): boolean | undefined => {
    return Array.isArray(list);
96
};
97

Lijiao's avatar
Lijiao committed
98
// get final result value
99
// draw Accuracy point graph
Lijiao's avatar
Lijiao committed
100
const getFinalResult = (final?: MetricDataRecord[]): number => {
Lijiao's avatar
Lijiao committed
101
102
103
    let acc;
    let showDefault = 0;
    if (final) {
104
        acc = parseMetrics(final[final.length - 1].data);
105
        if (typeof acc === 'object' && !isArrayType(acc)) {
Lijiao's avatar
Lijiao committed
106
107
108
            if (acc.default) {
                showDefault = acc.default;
            }
109
        } else if (typeof acc === 'number') {
Lijiao's avatar
Lijiao committed
110
            showDefault = acc;
111
112
        } else {
            showDefault = NaN;
Lijiao's avatar
Lijiao committed
113
114
115
116
117
118
        }
        return showDefault;
    } else {
        return 0;
    }
};
119

Lijiaoa's avatar
Lijiaoa committed
120
121
122
123
function isNaNorInfinity(val: number): boolean {
    return Object.is(val, NaN) || Object.is(val, Infinity);
}

124
// get final result value // acc obj
125
const getFinal = (final?: MetricDataRecord[]): FinalType | undefined => {
126
127
    let showDefault: FinalType;
    if (final) {
128
        showDefault = parseMetrics(final[final.length - 1].data);
129
        if (typeof showDefault === 'number') {
130
            if (!isNaNorInfinity(showDefault)) {
Lijiaoa's avatar
Lijiaoa committed
131
                return { default: showDefault };
132
            }
133
134
135
136
137
        } else if (isArrayType(showDefault)) {
            // not support final type
            return undefined;
        } else if (typeof showDefault === 'object' && showDefault.hasOwnProperty('default')) {
            return showDefault;
138
139
140
141
142
143
        }
    } else {
        return undefined;
    }
};

Lijiao's avatar
Lijiao committed
144
// detail page table intermediate button
Lijiao's avatar
Lijiao committed
145
const intermediateGraphOption = (intermediateArr: number[], id: string): any => {
Lijiao's avatar
Lijiao committed
146
147
148
149
150
151
152
153
154
155
156
    const sequence: number[] = [];
    const lengthInter = intermediateArr.length;
    for (let i = 1; i <= lengthInter; i++) {
        sequence.push(i);
    }
    return {
        title: {
            text: id,
            left: 'center',
            textStyle: {
                fontSize: 16,
157
                color: '#333'
Lijiao's avatar
Lijiao committed
158
159
160
161
162
163
            }
        },
        tooltip: {
            trigger: 'item'
        },
        xAxis: {
164
            // name: '#Intermediate result',
Lijiao's avatar
Lijiao committed
165
166
167
            data: sequence
        },
        yAxis: {
Lijiao's avatar
Lijiao committed
168
            name: 'Default metric',
Lijiao's avatar
Lijiao committed
169
            type: 'value',
Lijiaoa's avatar
Lijiaoa committed
170
171
            data: intermediateArr,
            scale: true
Lijiao's avatar
Lijiao committed
172
        },
173
174
175
176
177
178
179
        series: [
            {
                symbolSize: 6,
                type: 'scatter',
                data: intermediateArr
            }
        ]
Lijiao's avatar
Lijiao committed
180
181
182
183
    };
};

// kill job
Lijiao's avatar
Lijiao committed
184
const killJob = (key: number, id: string, status: string, updateList?: Function): void => {
Lijiao's avatar
Lijiao committed
185
186
187
188
189
190
191
192
    axios(`${MANAGER_IP}/trial-jobs/${id}`, {
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/json;charset=utf-8'
        }
    })
        .then(res => {
            if (res.status === 200) {
193
194
                // TODO: use Message.txt to tooltip
                alert('Cancel the job successfully');
Lijiao's avatar
Lijiao committed
195
                // render the table
196
                if (updateList) {
197
                    updateList(); // FIXME
198
                }
Lijiao's avatar
Lijiao committed
199
            } else {
200
                alert('fail to cancel the job');
Lijiao's avatar
Lijiao committed
201
202
203
204
205
            }
        })
        .catch(error => {
            if (error.response.status === 500) {
                if (error.response.data.error) {
Lijiaoa's avatar
Lijiaoa committed
206
                    alert(error.response.data.error);
Lijiao's avatar
Lijiao committed
207
                } else {
Lijiaoa's avatar
Lijiaoa committed
208
                    alert('500 error, fail to cancel the job');
Lijiao's avatar
Lijiao committed
209
210
211
212
213
                }
            }
        });
};

214
const filterByStatus = (item: TableObj): boolean => {
215
216
217
    return item.status === 'SUCCEEDED';
};

218
// a waittiong trial may havn't start time
219
const filterDuration = (item: TableObj): boolean => {
220
221
222
    return item.status !== 'WAITING';
};

Lijiao's avatar
Lijiao committed
223
const downFile = (content: string, fileName: string): void => {
v-liguo's avatar
v-liguo committed
224
225
226
227
228
229
230
231
232
233
234
    const aTag = document.createElement('a');
    const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false;
    const file = new Blob([content], { type: 'application/json' });
    aTag.download = fileName;
    aTag.href = URL.createObjectURL(file);
    aTag.click();
    if (!isEdge) {
        URL.revokeObjectURL(aTag.href);
    }
    if (navigator.userAgent.indexOf('Firefox') > -1) {
        const downTag = document.createElement('a');
235
        downTag.addEventListener('click', function() {
v-liguo's avatar
v-liguo committed
236
237
238
            downTag.download = fileName;
            downTag.href = URL.createObjectURL(file);
        });
chicm-ms's avatar
chicm-ms committed
239
        const eventMouse = document.createEvent('MouseEvents');
v-liguo's avatar
v-liguo committed
240
241
242
243
244
        eventMouse.initEvent('click', false, false);
        downTag.dispatchEvent(eventMouse);
    }
};

245
246
247
248
249
// function formatTimestamp(timestamp?: number, placeholder?: string = 'N/A'): string {
function formatTimestamp(timestamp?: number, placeholder?: string): string {
    if (placeholder === undefined) {
        placeholder = 'N/A';
    }
250
251
252
    return timestamp ? new Date(timestamp).toLocaleString('en-US') : placeholder;
}

253
254
255
256
257
258
259
260
function expformatTimestamp(timestamp: number | string): string {
    if (typeof timestamp === 'number') {
        return new Date(timestamp).toLocaleString('en-US');
    } else {
        return 'N/A';
    }
}

261
function metricAccuracy(metric: MetricDataRecord): number {
chicm-ms's avatar
chicm-ms committed
262
    const data = parseMetrics(metric.data);
263
264
265
266
267
268
    // return typeof data === 'number' ? data : NaN;
    if (typeof data === 'number') {
        return data;
    } else {
        return data.default;
    }
269
270
271
272
}

function formatAccuracy(accuracy: number): string {
    // TODO: how to format NaN?
273
274
275
276
    return accuracy
        .toFixed(6)
        .replace(/0+$/, '')
        .replace(/\.$/, '');
277
278
}

Lijiaoa's avatar
Lijiaoa committed
279
280
281
282
283
284
285
286
function formatComplexTypeValue(value: any): string | number {
    if (['number', 'string'].includes(typeof value)) {
        return value;
    } else {
        return value.toString();
    }
}

287
288
289
290
function isManagerExperimentPage(): boolean {
    return location.pathname.indexOf('experiment') === -1 ? false : true;
}

291
292
293
294
295
296
297
function caclMonacoEditorHeight(height): number {
    // [Search space 56px] + [marginBottom 18px] +
    // button[height: 32px, marginTop: 45px, marginBottom: 7px]
    // panel own padding-bottom: 20px;
    return height - 178;
}

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
function copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): any {
    const key = columnKey as keyof T;
    return items.slice(0).sort(function(a: T, b: T): any {
        if (
            a[key] === undefined ||
            Object.is(a[key], NaN) ||
            Object.is(a[key], Infinity) ||
            Object.is(a[key], -Infinity) ||
            typeof a[key] === 'object'
        ) {
            return 1;
        }
        if (
            b[key] === undefined ||
            Object.is(b[key], NaN) ||
            Object.is(b[key], Infinity) ||
            Object.is(b[key], -Infinity) ||
            typeof b[key] === 'object'
        ) {
            return -1;
        }
        return (isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1;
    });
}
Lijiaoa's avatar
Lijiaoa committed
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360

function disableTensorboard(selectedRowIds: string[], queryTensorboardList: Tensorboard[]): boolean {
    let flag = true;

    if (selectedRowIds.length !== 0) {
        flag = false;
    }

    if (selectedRowIds.length === 0 && queryTensorboardList.length !== 0) {
        flag = false;
    }

    return flag;
}

function getTensorboardMenu(queryTensorboardList: Tensorboard[], stopFunc, seeDetailFunc): IContextualMenuProps {
    const result: Array<object> = [];
    if (queryTensorboardList.length !== 0) {
        result.push({
            key: 'delete',
            text: 'Stop all tensorBoard',
            className: 'clearAll',
            onClick: stopFunc
        });
        queryTensorboardList.forEach(item => {
            result.push({
                key: item.id,
                text: `${item.id}`,
                className: `CommandBarButton-${item.status}`,
                onClick: (): void => seeDetailFunc(item)
            });
        });
    }
    const tensorboardMenu: IContextualMenuProps = {
        items: result.reverse() as any
    };

    return tensorboardMenu;
}
361
362
363
364
365
366
367
368
369
370
371
372
373

// search space type map list: now get type from search space
const parametersType = (): Map<string, string> => {
    const parametersTypeMap = new Map();
    const trialParameterlist = Object.keys(EXPERIMENT.searchSpace);

    trialParameterlist.forEach(item => {
        parametersTypeMap.set(item, typeof EXPERIMENT.searchSpace[item]._value[0]);
    });

    return parametersTypeMap;
};

374
export {
375
    getPrefix,
376
377
    convertTime,
    convertDuration,
378
    convertTimeAsUnit,
379
380
381
382
383
384
385
386
387
    getFinalResult,
    getFinal,
    downFile,
    intermediateGraphOption,
    killJob,
    filterByStatus,
    filterDuration,
    formatAccuracy,
    formatTimestamp,
388
    expformatTimestamp,
389
390
391
392
393
    metricAccuracy,
    parseMetrics,
    isArrayType,
    requestAxios,
    isNaNorInfinity,
394
    formatComplexTypeValue,
395
    isManagerExperimentPage,
396
    caclMonacoEditorHeight,
Lijiaoa's avatar
Lijiaoa committed
397
398
    copyAndSort,
    disableTensorboard,
399
400
    getTensorboardMenu,
    parametersType
401
};