datastore.ts 8.51 KB
Newer Older
liuzhe-lz's avatar
liuzhe-lz committed
1
2
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
Deshui Yu's avatar
Deshui Yu committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

'use strict';

import { assert } from 'console';
import * as fs from 'fs';
import { Deferred } from 'ts-deferred';

import { DataStore, MetricData, MetricDataRecord, MetricType,
    TrialJobEvent, TrialJobEventRecord, TrialJobInfo } from '../../common/datastore';
import { ExperimentProfile,  TrialJobStatistics } from '../../common/manager';
import { TrialJobStatus } from '../../common/trainingService';

class SimpleDb {
    private name: string = '';
    private fileName: string = '';

    private db: Array<any> = new Array();
    private map: Map<string, number> = new Map<string, number>();  // map key to data index

    constructor (name: string, filename: string) {
        this.name = name;
        this.fileName = filename;
    }

    async saveData(data: any, key?: string): Promise<void> {
        let index;
        if (key && this.map.has(key)) {
            index = this.map.get(key);
        }

        if (index === undefined) {
            index = this.db.push(data) - 1;
        } else {
            this.db[index] = data;
        }

        if (key) {
            this.map.set(key, index);
        }
        await this.persist();
    }

    listAllData(): Promise<Array<any>> {
        const deferred = new Deferred<Array<any>>();
        deferred.resolve(this.db);

        return deferred.promise;
    }

    getData(key: string): Promise<any> {
        const deferred = new Deferred<any>();
        if (this.map.has(key)) {
            const index = this.map.get(key);
            if(index !== undefined && index >= 0) {
                deferred.resolve(this.db[index]);
            } else {
                deferred.reject(new Error(`Key or index not found: ${this.name}, ${key}`));
            }
        } else {
            console.log(`Key not found: ${this.name}, ${key}`);
            deferred.resolve(undefined);
        }
        return deferred.promise;
    }

    persist(): Promise<void> {
        const deferred = new Deferred<void>();
        fs.writeFileSync(this.fileName, JSON.stringify({
            name: this.name,
            data: this.db,
            index: JSON.stringify([...this.map])
        }, null, 4));
        deferred.resolve();
        return deferred.promise;
    }
}

class MockedDataStore implements DataStore {

    private dbExpProfile: SimpleDb = new SimpleDb('exp_profile', './exp_profile.json');
    private dbTrialJobs: SimpleDb = new SimpleDb('trial_jobs', './trial_jobs.json');
    private dbMetrics: SimpleDb = new SimpleDb('metrics', './metrics.json');

demianzhang's avatar
demianzhang committed
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
    trailJob1 = {
        event: 'ADD_CUSTOMIZED',
        timestamp: Date.now(),
        trialJobId: "4321",
        data: ''
    }

    metrics1 = {
        timestamp: Date.now(),
        trialJobId: '4321',
        parameterId: 'param1',
        type: 'CUSTOM',
        sequence: 21,
        data: ''
    }

Deshui Yu's avatar
Deshui Yu committed
102
    init(): Promise<void> {
demianzhang's avatar
demianzhang committed
103
104
        this.dbTrialJobs.saveData(this.trailJob1);
        this.dbMetrics.saveData(this.metrics1);
Deshui Yu's avatar
Deshui Yu committed
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
        return Promise.resolve();
    }

    close(): Promise<void> {
        return Promise.resolve();
    }

    async storeExperimentProfile(experimentProfile: ExperimentProfile): Promise<void> {
        await this.dbExpProfile.saveData(experimentProfile, experimentProfile.id);
    }

    async getExperimentProfile(experimentId: string): Promise<ExperimentProfile> {
        return await this.dbExpProfile.getData(experimentId);
    }

    async storeTrialJobEvent(event: TrialJobEvent, trialJobId: string, data?: string | undefined): Promise<void> {
        const dataRecord: TrialJobEventRecord = {
            event: event,
123
            timestamp: Date.now(),
Deshui Yu's avatar
Deshui Yu committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
            trialJobId: trialJobId,
            data: data
        }
        await this.dbTrialJobs.saveData(dataRecord);
    }

    async getTrialJobStatistics(): Promise<any[]> {
        const result: TrialJobStatistics[] = [];
        const jobs = await this.listTrialJobs();
        const map: Map<TrialJobStatus, number> = new Map();

        jobs.forEach((value) => {
            let n: number|undefined = map.get(value.status);
            if (!n) {
                n = 0;
            }
            map.set(value.status, n + 1);
        })

        map.forEach((value, key) => {
            const statistics: TrialJobStatistics = {
                trialJobStatus: key,
                trialJobNumber: value
            }
            result.push(statistics);
        })
        return result;
    }

    async listTrialJobs(status?: TrialJobStatus): Promise<TrialJobInfo[]> {
        const trialJobEvents: TrialJobEventRecord[] = await this.dbTrialJobs.listAllData();
        const map: Map<string, TrialJobInfo> = this.getTrialJobsByReplayEvents(trialJobEvents);
        const result: TrialJobInfo[]= [];
        for (let key of map.keys()) {
            const jobInfo = map.get(key);
            if (jobInfo === undefined) {
                continue;
            }
            if (!(status && jobInfo.status !== status)) {
                if (jobInfo.status === 'SUCCEEDED') {
J-shang's avatar
J-shang committed
164
                    jobInfo.finalMetricData = await this.getFinalMetricData(jobInfo.trialJobId);
Deshui Yu's avatar
Deshui Yu committed
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
                }
                result.push(jobInfo);
            }
        }
        return result;
    }

    async storeMetricData(trialJobId: string, data: string): Promise<void> {
        const metrics = JSON.parse(data) as MetricData;
        assert(trialJobId === metrics.trial_job_id);
        await this.dbMetrics.saveData({
            trialJobId: metrics.trial_job_id,
            parameterId: metrics.parameter_id,
            type: metrics.type,
            data: metrics.value,
180
            timestamp: Date.now()
Deshui Yu's avatar
Deshui Yu committed
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
        });
    }

    async getMetricData(trialJobId: string, metricType: MetricType): Promise<MetricDataRecord[]> {
        const result: MetricDataRecord[] = []
        const allMetrics = await this.dbMetrics.listAllData();
        allMetrics.forEach((value) => {
            const metrics = <MetricDataRecord>value;
            if (metrics.type === metricType && metrics.trialJobId === trialJobId) {
                result.push(metrics);
            }
        });

        return result;
    }
196
197
198
199
200
201
202
203
204
205

    async exportTrialHpConfigs(): Promise<string> {
        const ret: string = '';
        return Promise.resolve(ret);
    }

    async getImportedData(): Promise<string[]> {
        const ret: string[] = [];
        return Promise.resolve(ret);
    }
Deshui Yu's avatar
Deshui Yu committed
206

207
    public getTrialJob(_trialJobId: string): Promise<TrialJobInfo> {
208
        return Promise.resolve({
J-shang's avatar
J-shang committed
209
            trialJobId: '1234',
210
211
212
213
            status: 'SUCCEEDED',
            startTime: Date.now(),
            endTime: Date.now()
        });
Deshui Yu's avatar
Deshui Yu committed
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
    }

    private async getFinalMetricData(trialJobId: string): Promise<any> {
        const metrics: MetricDataRecord[] = await this.getMetricData(trialJobId, "FINAL");
        assert(metrics.length <= 1);
        if (metrics.length == 1) {
            return metrics[0];
        } else {
            return undefined;
        }
    }

    private getJobStatusByLatestEvent(event: TrialJobEvent): TrialJobStatus {
        switch(event) {
            case 'USER_TO_CANCEL':
                return 'USER_CANCELED';
            case 'ADD_CUSTOMIZED':
                return 'WAITING';
232
233
            case 'ADD_RESUMED':
                return 'WAITING';
Deshui Yu's avatar
Deshui Yu committed
234
235
236
237
238
239
240
241
242
243
244
245
246
        }
        return <TrialJobStatus>event;
    }

    private getTrialJobsByReplayEvents(trialJobEvents: TrialJobEventRecord[]):  Map<string, TrialJobInfo> {
        const map: Map<string, TrialJobInfo> = new Map();
        // assume data is stored by time ASC order
        for (let record of trialJobEvents) {
            let jobInfo: TrialJobInfo | undefined;
            if (map.has(record.trialJobId)) {
                jobInfo = map.get(record.trialJobId);
            } else {
                jobInfo = {
J-shang's avatar
J-shang committed
247
                    trialJobId: record.trialJobId,
Deshui Yu's avatar
Deshui Yu committed
248
249
250
251
252
253
254
255
                    status: this.getJobStatusByLatestEvent(record.event),
                };
            }
            if (!jobInfo) {
                throw new Error('Empty JobInfo');
            }
            switch (record.event) {
                case 'RUNNING':
256
                    jobInfo.startTime = Date.now();
Deshui Yu's avatar
Deshui Yu committed
257
258
259
260
261
                    break;
                case 'SUCCEEDED':
                case 'FAILED':
                case 'USER_CANCELED':
                case 'SYS_CANCELED':
QuanluZhang's avatar
QuanluZhang committed
262
                case 'EARLY_STOPPED':
263
                    jobInfo.endTime = Date.now();
Deshui Yu's avatar
Deshui Yu committed
264
265
266
267
268
269
270
271
            }
            jobInfo.status = this.getJobStatusByLatestEvent(record.event);
            map.set(record.trialJobId, jobInfo);
        }
        return map;
    }
}

QuanluZhang's avatar
QuanluZhang committed
272
export { MockedDataStore };