Unverified Commit 3d221da9 authored by fishyds's avatar fishyds Committed by GitHub
Browse files

Merge latest code changes into Github Master (#54)

* Merge latest code changes into Github Master

* temporary modification for travis

* temporary modification for travis
parent c015421c
......@@ -28,16 +28,19 @@ import { NNIError, NNIErrorNames } from '../common/errors';
import { isNewExperiment } from '../common/experimentStartupInfo';
import { getLogger, Logger } from '../common/log';
import { ExperimentProfile, Manager, TrialJobStatistics} from '../common/manager';
import { RestServer } from './server';
import { ValidationSchemas } from './restValidationSchemas';
import { NNIRestServer } from './nniRestServer';
import { TensorBoard } from './tensorboard';
const expressJoi = require('express-joi-validator');
class NNIRestHandler {
private restServer: RestServer;
private restServer: NNIRestServer;
private nniManager: Manager;
private tb: TensorBoard;
private log: Logger;
constructor(rs: RestServer) {
constructor(rs: NNIRestServer) {
this.nniManager = component.get(Manager);
this.restServer = rs;
this.tb = new TensorBoard();
......@@ -75,6 +78,15 @@ class NNIRestHandler {
this.startTensorBoard(router);
this.stopTensorBoard(router);
// Express-joi-validator configuration
router.use((err: any, req: Request, res: Response, next: any) => {
if (err.isBoom) {
this.log.error(err.output.payload);
return res.status(err.output.statusCode).json(err.output.payload);
}
});
return router;
}
......@@ -96,7 +108,7 @@ class NNIRestHandler {
router.get('/check-status', (req: Request, res: Response) => {
const ds: DataStore = component.get<DataStore>(DataStore);
ds.init().then(() => {
res.send();
res.send(this.nniManager.getStatus());
}).catch(async (err: Error) => {
this.handle_error(err, res);
this.log.error(err.message);
......@@ -117,7 +129,7 @@ class NNIRestHandler {
}
private updateExperimentProfile(router: Router): void {
router.put('/experiment', (req: Request, res: Response) => {
router.put('/experiment', expressJoi(ValidationSchemas.UPDATEEXPERIMENT), (req: Request, res: Response) => {
this.nniManager.updateExperimentProfile(req.body, req.query.update_type).then(() => {
res.send();
}).catch((err: Error) => {
......@@ -127,7 +139,7 @@ class NNIRestHandler {
}
private startExperiment(router: Router): void {
router.post('/experiment', (req: Request, res: Response) => {
router.post('/experiment', expressJoi(ValidationSchemas.STARTEXPERIMENT), (req: Request, res: Response) => {
if (isNewExperiment()) {
this.nniManager.startExperiment(req.body).then((eid: string) => {
res.send({
......@@ -171,7 +183,9 @@ class NNIRestHandler {
}
private setClusterMetaData(router: Router): void {
router.put('/experiment/cluster-metadata', async (req: Request, res: Response) => {
router.put(
'/experiment/cluster-metadata', expressJoi(ValidationSchemas.SETCLUSTERMETADATA),
async (req: Request, res: Response) => {
// tslint:disable-next-line:no-any
const metadata: any = req.body;
const keys: string[] = Object.keys(metadata);
......@@ -241,7 +255,7 @@ class NNIRestHandler {
}
private startTensorBoard(router: Router): void {
router.post('/tensorboard', async (req: Request, res: Response) => {
router.post('/tensorboard', expressJoi(ValidationSchemas.STARTTENSORBOARD), async (req: Request, res: Response) => {
const jobIds: string[] = req.query.job_ids.split(',');
const tensorboardCmd: string | undefined = req.query.tensorboard_cmd;
this.tb.startTensorBoard(jobIds, tensorboardCmd).then((endPoint: string) => {
......@@ -253,7 +267,7 @@ class NNIRestHandler {
}
private stopTensorBoard(router: Router): void {
router.delete('/tensorboard', async (req: Request, res: Response) => {
router.delete('/tensorboard', expressJoi(ValidationSchemas.STOPTENSORBOARD), async (req: Request, res: Response) => {
const endPoint: string = req.query.endpoint;
this.tb.stopTensorBoard(endPoint).then(() => {
res.send();
......@@ -285,7 +299,7 @@ class NNIRestHandler {
}
}
export function createRestHandler(rs: RestServer): Router {
export function createRestHandler(rs: NNIRestServer): Router {
const handler: NNIRestHandler = new NNIRestHandler(rs);
return handler.createRestHandler();
......
/**
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';
const joi = require('joi');
export namespace ValidationSchemas {
export const SETCLUSTERMETADATA = {
body: {
machine_list: joi.array().items(joi.object({
username: joi.string().required(),
ip: joi.string().ip().required(),
port: joi.number().min(1).max(65535).required(),
passwd: joi.string().required(),
sshKeyPath: joi.string(),
passphrase: joi.string()
})),
trial_config: joi.object({
gpuNum: joi.number().min(0).required(),
codeDir: joi.string().min(1).required(),
command: joi.string().min(1).required()
})
}
};
export const STARTEXPERIMENT = {
body: {
experimentName: joi.string().required(),
authorName: joi.string(),
maxTrialNum: joi.number().min(0).required(),
trialConcurrency: joi.number().min(0).required(),
searchSpace: joi.string().required(),
maxExecDuration: joi.number().min(0).required(),
tuner: joi.object({
builtinTunerName: joi.string().valid('TPE', 'Random', 'Anneal', 'Evolution'),
codeDir: joi.string(),
classFileName: joi.string(),
className: joi.string(),
classArgs: joi.any(),
gpuNum: joi.number().min(0),
checkpointDir: joi.string()
}).required(),
assessor: joi.object({
builtinAssessorName: joi.string().valid('Medianstop'),
codeDir: joi.string(),
classFileName: joi.string(),
className: joi.string(),
classArgs: joi.any(),
gpuNum: joi.number().min(0),
checkpointDir: joi.string()
}),
clusterMetaData: joi.array().items(joi.object({
key: joi.string(),
value: joi.any()
}))
}
};
export const UPDATEEXPERIMENT = {
query: {
update_type: joi.string().required().valid('TRIAL_CONCURRENCY', 'MAX_EXEC_DURATION', 'SEARCH_SPACE')
},
body: {
id: joi.string().required(),
revision: joi.number().min(0).required(),
params: joi.object(STARTEXPERIMENT.body).required(),
execDuration: joi.number().required(),
startTime: joi.number(),
endTime: joi.number()
}
};
export const STARTTENSORBOARD = {
query: {
job_ids: joi.string().min(5).max(5).required()
}
};
export const STOPTENSORBOARD = {
query: {
endpoint: joi.string().uri().required()
}
};
}
......@@ -26,7 +26,7 @@ import { MetricDataRecord, MetricType, TrialJobInfo } from '../../common/datasto
import { MethodNotImplementedError } from '../../common/errors';
import {
ExperimentParams, ExperimentProfile, Manager, ProfileUpdateType,
TrialJobStatistics
TrialJobStatistics, NNIManagerStatus
} from '../../common/manager';
import {
TrialJobApplicationForm, TrialJobDetail, TrialJobStatus
......@@ -37,6 +37,12 @@ export const testManagerProvider: Provider = {
};
export class MockedNNIManager extends Manager {
public getStatus(): NNIManagerStatus {
return {
status: 'EXPERIMENT_RUNNING',
errors: []
}
}
public updateExperimentProfile(experimentProfile: ExperimentProfile, updateType: ProfileUpdateType): Promise<void> {
return Promise.resolve();
}
......@@ -65,9 +71,9 @@ export class MockedNNIManager extends Manager {
const jobDetail: TrialJobDetail = {
id: '1234',
status: 'RUNNING',
submitTime: new Date(),
startTime: new Date(),
endTime: new Date(),
submitTime: Date.now(),
startTime: Date.now(),
endTime: Date.now(),
tags: ['test'],
// tslint:disable-next-line:no-http-string
url: 'http://test',
......@@ -108,8 +114,8 @@ export class MockedNNIManager extends Manager {
const jobInfo: TrialJobInfo = {
id: '1234',
status: 'SUCCEEDED',
startTime: new Date(),
endTime: new Date()
startTime: Date.now(),
endTime: Date.now()
};
deferred.resolve(jobInfo);
......@@ -137,8 +143,8 @@ export class MockedNNIManager extends Manager {
},
id: '2345',
execDuration: 0,
startTime: new Date(),
endTime: new Date(),
startTime: Date.now(),
endTime: Date.now(),
revision: 0
};
......@@ -148,15 +154,15 @@ export class MockedNNIManager extends Manager {
const job1: TrialJobInfo = {
id: '1234',
status: 'SUCCEEDED',
startTime: new Date(),
endTime: new Date(),
startTime: Date.now(),
endTime: Date.now(),
finalMetricData: 'lr: 0.01, val accuracy: 0.89, batch size: 256'
};
const job2: TrialJobInfo = {
id: '3456',
status: 'FAILED',
startTime: new Date(),
endTime: new Date(),
startTime: Date.now(),
endTime: Date.now(),
finalMetricData: ''
};
......
......@@ -32,7 +32,7 @@ import { TrainingService } from '../../common/trainingService';
import { cleanupUnitTest, prepareUnitTest } from '../../common/utils';
import { MockedDataStore } from '../../core/test/mockedDatastore';
import { MockedTrainingService } from '../../core/test/mockedTrainingService';
import { RestServer } from '../server';
import { NNIRestServer } from '../nniRestServer';
import { testManagerProvider } from './mockedNNIManager';
describe('Unit test for rest server', () => {
......@@ -44,7 +44,7 @@ describe('Unit test for rest server', () => {
Container.bind(Manager).provider(testManagerProvider);
Container.bind(DataStore).to(MockedDataStore);
Container.bind(TrainingService).to(MockedTrainingService);
const restServer: RestServer = component.get(RestServer);
const restServer: NNIRestServer = component.get(NNIRestServer);
restServer.start().then(() => {
ROOT_URL = `${restServer.endPoint}/api/v1/nni`;
done();
......@@ -54,7 +54,7 @@ describe('Unit test for rest server', () => {
});
after(() => {
component.get<RestServer>(RestServer).stop();
component.get<NNIRestServer>(NNIRestServer).stop();
cleanupUnitTest();
});
......
......@@ -65,16 +65,16 @@ function decodeCommand(data: Buffer): [boolean, string, string, Buffer] {
class LocalTrialJobDetail implements TrialJobDetail {
public id: string;
public status: TrialJobStatus;
public submitTime: Date;
public startTime?: Date;
public endTime?: Date;
public submitTime: number;
public startTime?: number;
public endTime?: number;
public tags?: string[];
public url?: string;
public workingDirectory: string;
public form: JobApplicationForm;
public pid?: number;
constructor(id: string, status: TrialJobStatus, submitTime: Date, workingDirectory: string, form: JobApplicationForm) {
constructor(id: string, status: TrialJobStatus, submitTime: number, workingDirectory: string, form: JobApplicationForm) {
this.id = id;
this.status = status;
this.submitTime = submitTime;
......@@ -152,7 +152,7 @@ class LocalTrainingService implements TrainingService {
}
if (!alive) {
trialJob.endTime = new Date();
trialJob.endTime = Date.now();
this.setTrialJobStatus(trialJob, 'FAILED');
try {
const state: string = await fs.promises.readFile(path.join(trialJob.workingDirectory, '.nni', 'state'), 'utf8');
......@@ -162,7 +162,7 @@ class LocalTrainingService implements TrainingService {
if (parseInt(code, 10) === 0) {
this.setTrialJobStatus(trialJob, 'SUCCEEDED');
}
trialJob.endTime = new Date(parseInt(timestamp, 10));
trialJob.endTime = parseInt(timestamp, 10);
}
} catch (error) {
//ignore
......@@ -191,7 +191,7 @@ class LocalTrainingService implements TrainingService {
const trialJobDetail: LocalTrialJobDetail = new LocalTrialJobDetail(
trialJobId,
'WAITING',
new Date(),
Date.now(),
path.join(this.rootDir, 'trials', trialJobId),
form);
this.jobQueue.push(trialJobId);
......@@ -339,7 +339,7 @@ class LocalTrainingService implements TrainingService {
const process: cp.ChildProcess = cp.exec(`bash ${path.join(trialJobDetail.workingDirectory, 'run.sh')}`);
this.setTrialJobStatus(trialJobDetail, 'RUNNING');
trialJobDetail.startTime = new Date();
trialJobDetail.startTime = Date.now();
trialJobDetail.pid = process.pid;
this.setExtraProperties(trialJobDetail, resource);
......@@ -372,7 +372,7 @@ class LocalTrainingService implements TrainingService {
const jobDetail: LocalTrialJobDetail = {
id: jobId,
status: 'RUNNING',
submitTime: new Date(),
submitTime: Date.now(),
workingDirectory: workDir,
form: form,
pid: process.pid
......
/**
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
\ No newline at end of file
......@@ -19,11 +19,12 @@
'use strict';
import * as assert from 'assert';
import { Client } from 'ssh2';
import { Deferred } from 'ts-deferred';
import { getLogger, Logger } from '../../common/log';
import { randomSelect } from '../../common/utils';
import { GPUInfo } from '../common/gpuData';
import { RemoteMachineMeta, RemoteMachineScheduleResult, RemoteMachineScheduleInfo, ScheduleResultType } from './remoteMachineData';
import { RemoteMachineMeta, RemoteMachineScheduleResult, ScheduleResultType } from './remoteMachineData';
/**
* A simple GPU scheduler implementation
......@@ -45,82 +46,64 @@ export class GPUScheduler {
* Schedule a machine according to the constraints (requiredGPUNum)
* @param requiredGPUNum required GPU number
*/
public scheduleMachine(requiredGPUNum : Number | undefined, trialJobId : string) : RemoteMachineScheduleResult {
const deferred: Deferred<RemoteMachineScheduleResult> = new Deferred<RemoteMachineScheduleResult>();
let scheduleResult : RemoteMachineScheduleResult = {
resultType : ScheduleResultType.TMP_NO_AVAILABLE_GPU,
scheduleInfo : undefined
};
// Step 0: Check if required GPU number not exceeds the total GPU number in all machines
const eligibleRM : RemoteMachineMeta[] = Array.from(this.machineSSHClientMap.keys()).filter((rmMeta : RemoteMachineMeta) =>
rmMeta.gpuSummary === undefined || requiredGPUNum === undefined || rmMeta.gpuSummary.gpuCount >= requiredGPUNum );
if(eligibleRM.length == 0) {
public scheduleMachine(requiredGPUNum: number, trialJobId : string) : RemoteMachineScheduleResult {
assert(requiredGPUNum >= 0);
const allRMs: RemoteMachineMeta[] = Array.from(this.machineSSHClientMap.keys());
assert(allRMs.length > 0);
// Step 1: Check if required GPU number not exceeds the total GPU number in all machines
const eligibleRM: RemoteMachineMeta[] = allRMs.filter((rmMeta : RemoteMachineMeta) =>
rmMeta.gpuSummary === undefined || requiredGPUNum === 0 || rmMeta.gpuSummary.gpuCount >= requiredGPUNum);
if (eligibleRM.length === 0) {
// If the required gpu number exceeds the upper limit of all machine's GPU number
// Return REQUIRE_EXCEED_TOTAL directly
return ({
resultType : ScheduleResultType.REQUIRE_EXCEED_TOTAL,
scheduleInfo : undefined
resultType: ScheduleResultType.REQUIRE_EXCEED_TOTAL,
scheduleInfo: undefined
});
}
// Step 1: Generate GPU resource map for remote machines
const totalResourceMap : Map<RemoteMachineMeta, GPUInfo[]> = this.gpuResourceDetection(requiredGPUNum);
// Step 2: Find machine whose GPU can be allocated based on user GPU requirement, and allocate GPU
for (const rmMeta of Array.from(totalResourceMap.keys())) {
const gpuInfos : GPUInfo[] | undefined = totalResourceMap.get(rmMeta);
if(gpuInfos !== undefined && (requiredGPUNum === undefined || gpuInfos.length >= requiredGPUNum)) {
const allocatedGPUIndex : number[] = Array();
// Allocate
gpuInfos.forEach((gpuInfo : GPUInfo) => {
rmMeta.gpuReservation.set(gpuInfo.index, trialJobId);
allocatedGPUIndex.push(gpuInfo.index);
});
// Construct scheduling return object
const sshClient : Client | undefined = this.machineSSHClientMap.get(rmMeta);
if(sshClient !== undefined) {
this.log.info(`Found available machine, trialJobId is ${trialJobId}, ip is ${rmMeta.ip}, gpu allocated is ${allocatedGPUIndex.toString()}`);
// We found the first available machine whose GPU resource can match user requirement
return {
resultType : ScheduleResultType.SUCCEED,
scheduleInfo : {
rmMeta : rmMeta,
client : sshClient,
cuda_visible_device : allocatedGPUIndex.join(',')
}
};
}
}
// Step 2: Allocate Host/GPU for specified trial job
// Currenty the requireGPUNum parameter for all trial jobs are identical.
if (requiredGPUNum > 0) {
// Trial job requires GPU
const result: RemoteMachineScheduleResult | undefined = this.scheduleGPUHost(requiredGPUNum, trialJobId);
if (result !== undefined) {
return result;
}
} else {
// Trail job does not need GPU
const allocatedRm: RemoteMachineMeta = this.selectMachine(allRMs);
// Step 3: If not found machine whose GPU is availabe, then find the first machine whose GPU summary is unknown
for (const rmMeta of Array.from(this.machineSSHClientMap.keys())) {
const client : Client | undefined = this.machineSSHClientMap.get(rmMeta);
if(rmMeta.gpuSummary == undefined && client !== undefined) {
// We found the firstmachine whose GPU summary is unknown
return {
resultType : ScheduleResultType.SUCCEED,
scheduleInfo :{
rmMeta : rmMeta,
client : client,
//Since gpu information is unknown, make all GPU resources visible to the job
cuda_visible_device : ''
}
};
return this.allocateHost(requiredGPUNum, allocatedRm, [], trialJobId);
}
};
this.log.warning(`Scheduler: trialJob id ${trialJobId}, no machine can be scheduled, return TMP_NO_AVAILABLE_GPU `);
this.log.warning(`Scheduler: trialJob id ${trialJobId}, no machine can be scheduled, resolve as TMP_NO_AVAILABLE_GPU `);
// Otherwise, no machine can be scheduled, resolve as TMP_NO_AVAILABLE_GPU
return {
resultType : ScheduleResultType.TMP_NO_AVAILABLE_GPU,
scheduleInfo : undefined
};
}
private scheduleGPUHost(requiredGPUNum: number, trialJobId: string): RemoteMachineScheduleResult | undefined {
const totalResourceMap: Map<RemoteMachineMeta, GPUInfo[]> = this.gpuResourceDetection();
const qualifiedRMs: RemoteMachineMeta[] = [];
totalResourceMap.forEach((gpuInfos: GPUInfo[], rmMeta: RemoteMachineMeta) => {
if (gpuInfos !== undefined && gpuInfos.length >= requiredGPUNum) {
qualifiedRMs.push(rmMeta);
}
});
if (qualifiedRMs.length > 0) {
const allocatedRm: RemoteMachineMeta = this.selectMachine(qualifiedRMs);
const gpuInfos: GPUInfo[] | undefined = totalResourceMap.get(allocatedRm);
if (gpuInfos !== undefined) { // should always true
return this.allocateHost(requiredGPUNum, allocatedRm, gpuInfos, trialJobId);
} else {
assert(false, 'gpuInfos is undefined');
}
}
}
/**
* Detect available GPU resource for a remote machine
* @param rmMeta Remote machine metadata
......@@ -128,33 +111,56 @@ export class GPUScheduler {
* @param availableGPUMap available GPU resource filled by this detection
* @returns Available GPU number on this remote machine
*/
private gpuResourceDetection(requiredGPUNum : Number | undefined) : Map<RemoteMachineMeta, GPUInfo[]> {
private gpuResourceDetection() : Map<RemoteMachineMeta, GPUInfo[]> {
const totalResourceMap : Map<RemoteMachineMeta, GPUInfo[]> = new Map<RemoteMachineMeta, GPUInfo[]>();
this.machineSSHClientMap.forEach((client: Client, rmMeta: RemoteMachineMeta) => {
// Assgin totoal GPU count as init available GPU number
if(rmMeta.gpuSummary !== undefined) {
const availableGPUs : GPUInfo[] = Array();
if(rmMeta.gpuReservation === undefined) {
if (rmMeta.gpuSummary !== undefined) {
const availableGPUs: GPUInfo[] = [];
if (rmMeta.gpuReservation === undefined) {
rmMeta.gpuReservation = new Map<number, string>();
}
const gpuReservation = rmMeta.gpuReservation;
rmMeta.gpuSummary.gpuInfos.forEach((gpuInfo: GPUInfo) => {
//this.log.info(`GPU index:${gpuInfo.index}, activeProcessNum is ${gpuInfo.activeProcessNum}, GPU reservation is ${JSON.stringify([...gpuReservation])}`);
// if the GPU has active process, OR be reserved by a job,
// We should NOT allocate this GPU
if (gpuInfo.activeProcessNum === 0
&& !gpuReservation.has(gpuInfo.index)
&& requiredGPUNum !== undefined
&& availableGPUs.length < requiredGPUNum) {
if (gpuInfo.activeProcessNum === 0 && !rmMeta.gpuReservation.has(gpuInfo.index)) {
availableGPUs.push(gpuInfo);
}
});
totalResourceMap.set(rmMeta, availableGPUs);
}
});
return totalResourceMap;
}
private selectMachine(rmMetas: RemoteMachineMeta[]): RemoteMachineMeta {
assert(rmMetas !== undefined && rmMetas.length > 0);
return randomSelect(rmMetas);
}
private selectGPUsForTrial(gpuInfos: GPUInfo[], requiredGPUNum: number): GPUInfo[] {
// Sequentially allocate GPUs
return gpuInfos.slice(0, requiredGPUNum);
}
private allocateHost(requiredGPUNum: number, rmMeta: RemoteMachineMeta,
gpuInfos: GPUInfo[], trialJobId: string): RemoteMachineScheduleResult {
assert(gpuInfos.length >= requiredGPUNum);
const allocatedGPUs: GPUInfo[] = this.selectGPUsForTrial(gpuInfos, requiredGPUNum);
allocatedGPUs.forEach((gpuInfo: GPUInfo) => {
rmMeta.gpuReservation.set(gpuInfo.index, trialJobId);
});
return {
resultType: ScheduleResultType.SUCCEED,
scheduleInfo: {
rmMeta: rmMeta,
cuda_visible_device: allocatedGPUs.map((gpuInfo: GPUInfo) => { return gpuInfo.index; }).join(',')
}
};
}
}
......@@ -87,16 +87,16 @@ export class JobMetrics {
export class RemoteMachineTrialJobDetail implements TrialJobDetail {
public id: string;
public status: TrialJobStatus;
public submitTime: Date;
public startTime?: Date;
public endTime?: Date;
public submitTime: number;
public startTime?: number;
public endTime?: number;
public tags?: string[];
public url?: string;
public workingDirectory: string;
public form: JobApplicationForm;
public rmMeta?: RemoteMachineMeta;
constructor(id: string, status: TrialJobStatus, submitTime: Date, workingDirectory: string, form: JobApplicationForm) {
constructor(id: string, status: TrialJobStatus, submitTime: number, workingDirectory: string, form: JobApplicationForm) {
this.id = id;
this.status = status;
this.submitTime = submitTime;
......@@ -106,9 +106,9 @@ export class RemoteMachineTrialJobDetail implements TrialJobDetail {
}
}
export type RemoteMachineScheduleResult = { scheduleInfo : RemoteMachineScheduleInfo | undefined, resultType : ScheduleResultType};
export type RemoteMachineScheduleResult = { scheduleInfo : RemoteMachineScheduleInfo | undefined; resultType : ScheduleResultType};
export type RemoteMachineScheduleInfo = { client: Client; rmMeta : RemoteMachineMeta; cuda_visible_device : string};
export type RemoteMachineScheduleInfo = { rmMeta : RemoteMachineMeta; cuda_visible_device : string};
export enum ScheduleResultType {
/* Schedule succeeded*/
......
......@@ -19,6 +19,7 @@
'use strict';
import * as assert from 'assert';
import * as cpp from 'child-process-promise';
import { EventEmitter } from 'events';
import * as fs from 'fs';
......@@ -54,8 +55,6 @@ import { SSHClientUtility } from './sshClientUtility';
class RemoteMachineTrainingService implements TrainingService {
private machineSSHClientMap: Map<RemoteMachineMeta, Client>;
private trialJobsMap: Map<string, RemoteMachineTrialJobDetail>;
private experimentId: string | undefined;
// Experiment root directory
private expRootDir: string;
private remoteExpRootDir: string;
private trialConfig: TrialConfig | undefined;
......@@ -72,9 +71,8 @@ class RemoteMachineTrainingService implements TrainingService {
this.machineSSHClientMap = new Map<RemoteMachineMeta, Client>();
this.gpuScheduler = new GPUScheduler(this.machineSSHClientMap);
this.jobQueue = [];
this.experimentId = getExperimentId();
this.expRootDir = getExperimentRootDir();
this.remoteExpRootDir = this.getRemoteModeExperimentRootDir();
this.remoteExpRootDir = this.getRemoteExperimentRootDir();
this.timer = timer;
this.log = getLogger();
}
......@@ -183,7 +181,7 @@ class RemoteMachineTrainingService implements TrainingService {
const trialJobDetail: RemoteMachineTrialJobDetail = new RemoteMachineTrialJobDetail(
trialJobId,
'WAITING',
new Date(),
Date.now(),
trialWorkingFolder,
form);
this.jobQueue.push(trialJobId);
......@@ -326,6 +324,7 @@ class RemoteMachineTrainingService implements TrainingService {
}
this.machineSSHClientMap.set(rmMeta, conn);
conn.on('ready', async () => {
this.machineSSHClientMap.set(rmMeta, conn);
await this.initRemoteMachineOnConnected(rmMeta, conn);
if (++connectedRMNum === rmMetaList.length) {
deferred.resolve();
......@@ -392,7 +391,7 @@ class RemoteMachineTrainingService implements TrainingService {
trialJobDetail.status = 'RUNNING';
trialJobDetail.url = `file://${rmScheduleInfo.rmMeta.ip}:${trialWorkingFolder}`;
trialJobDetail.startTime = new Date();
trialJobDetail.startTime = Date.now();
trialJobDetail.rmMeta = rmScheduleInfo.rmMeta;
deferred.resolve(true);
......@@ -412,7 +411,13 @@ class RemoteMachineTrainingService implements TrainingService {
throw new Error('trial config is not initialized');
}
const cuda_visible_device: string = rmScheduleInfo.cuda_visible_device;
const sshClient: Client = rmScheduleInfo.client;
const sshClient: Client | undefined = this.machineSSHClientMap.get(rmScheduleInfo.rmMeta);
if (sshClient === undefined) {
assert(false, 'sshClient is undefined.');
// for lint
return;
}
const trialLocalTempFolder: string = path.join(this.expRootDir, 'trials-local', trialJobId);
await SSHClientUtility.remoteExeCommand(`mkdir -p ${trialWorkingFolder}`, sshClient);
......@@ -472,9 +477,9 @@ class RemoteMachineTrainingService implements TrainingService {
path.join(localDir, 'run.sh'), path.join(remoteDir, 'run.sh'), sshClient);
SSHClientUtility.remoteExeCommand(`bash ${path.join(remoteDir, 'run.sh')}`, sshClient);
const jobDetail: RemoteMachineTrialJobDetail = new RemoteMachineTrialJobDetail(jobId, 'RUNNING', new Date(), remoteDir, form);
const jobDetail: RemoteMachineTrialJobDetail = new RemoteMachineTrialJobDetail(jobId, 'RUNNING', Date.now(), remoteDir, form);
jobDetail.rmMeta = rmMeta;
jobDetail.startTime = new Date();
jobDetail.startTime = Date.now();
this.trialJobsMap.set(jobId, jobDetail);
this.log.debug(`runHostJob: return: ${JSON.stringify(jobDetail)} `);
......@@ -510,7 +515,7 @@ class RemoteMachineTrainingService implements TrainingService {
} else {
trialJob.status = 'FAILED';
}
trialJob.endTime = new Date(parseInt(timestamp, 10));
trialJob.endTime = parseInt(timestamp, 10);
}
this.log.info(`trailJob status update: ${trialJob.id}, ${trialJob.status}`);
}
......@@ -536,7 +541,7 @@ class RemoteMachineTrainingService implements TrainingService {
return path.join(this.remoteExpRootDir, 'hostjobs', jobId);
}
private getRemoteModeExperimentRootDir(): string{
private getRemoteExperimentRootDir(): string{
return path.join(os.tmpdir(), 'nni', 'experiments', getExperimentId());
}
......
......@@ -11,5 +11,12 @@
"no-console": [true, "log"],
"no-multiline-string": false
},
"rulesDirectory": []
"rulesDirectory": [],
"linterOptions": {
"exclude": [
"training_service/test/*",
"rest_server/test/*",
"core/test/*"
]
}
}
\ No newline at end of file
......@@ -343,6 +343,24 @@ body-parser@1.18.2:
raw-body "2.3.2"
type-is "~1.6.15"
boom@2.6.x:
version "2.6.1"
resolved "https://registry.yarnpkg.com/boom/-/boom-2.6.1.tgz#4dc8ef9b6dfad9c43bbbfbe71fa4c21419f22753"
dependencies:
hoek "2.x.x"
boxen@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
dependencies:
ansi-align "^2.0.0"
camelcase "^4.0.0"
chalk "^2.0.1"
cli-boxes "^1.0.0"
string-width "^2.0.0"
term-size "^1.2.0"
widest-line "^2.0.0"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
......@@ -582,6 +600,38 @@ etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
execa@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
dependencies:
cross-spawn "^5.0.1"
get-stream "^3.0.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
dependencies:
cross-spawn "^5.0.1"
get-stream "^3.0.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
express-joi-validator@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/express-joi-validator/-/express-joi-validator-2.0.0.tgz#24e26e6a8327f69985ed72588f00e295dc3e3234"
dependencies:
boom "2.6.x"
extend "2.0.x"
joi "6.x.x"
express@^4.16.3:
version "4.16.3"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
......@@ -617,6 +667,10 @@ express@^4.16.3:
utils-merge "1.0.1"
vary "~1.1.2"
extend@2.0.x:
version "2.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-2.0.2.tgz#1b74985400171b85554894459c978de6ef453ab7"
extend@~3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
......@@ -763,6 +817,10 @@ he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
http-errors@1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
......@@ -852,6 +910,10 @@ isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
isemail@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
......@@ -860,6 +922,15 @@ isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
joi@6.x.x:
version "6.10.1"
resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
dependencies:
hoek "2.x.x"
isemail "1.x.x"
moment "2.x.x"
topo "1.x.x"
js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
......@@ -982,6 +1053,10 @@ mocha@^5.2.0:
mkdirp "0.5.1"
supports-color "5.4.0"
moment@2.x.x:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
......@@ -1459,12 +1534,24 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
topo@1.x.x:
version "1.1.0"
resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5"
dependencies:
hoek "2.x.x"
tough-cookie@~2.3.3:
version "2.3.4"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
dependencies:
punycode "^1.4.1"
toxic@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toxic/-/toxic-1.0.1.tgz#8c2e2528da591100adc3883f2c0e56acfb1c7288"
dependencies:
lodash "^4.17.10"
tree-kill@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
......
......@@ -58,8 +58,6 @@ class Accuracy extends React.Component<{}, ChartState> {
yAxis: {
name: 'Accuracy',
type: 'value',
min: 0,
max: 1,
data: yAxis
},
series: [{
......@@ -85,7 +83,7 @@ class Accuracy extends React.Component<{}, ChartState> {
accArr.push(parseFloat(accData[item].finalMetricData.data));
}
});
accY.push({yAxis: accArr});
accY.push({ yAxis: accArr });
let optionObj = this.getOption(accY[0]);
this.setState({ option: optionObj }, () => {
if (accArr.length === 0) {
......
import * as React from 'react';
import axios from 'axios';
import { MANAGER_IP } from '../const';
import {
message,
Tabs,
Button
} from 'antd';
const TabPane = Tabs.TabPane;
import '../style/logdetail.css';
interface LogState {
trialId: string;
slotLog: string;
processLog: string;
}
class Logdetail extends React.Component<{}, LogState> {
public _isMounted = false;
constructor(props: {}) {
super(props);
this.state = {
trialId: '',
slotLog: '',
processLog: ''
};
}
getJobLog = () => {
Object.keys(this.props).map(item => {
if (item === 'location') {
if (this._isMounted) {
this.setState({ trialId: this.props[item].state }, () => {
const { trialId } = this.state;
let id = trialId;
axios(`${MANAGER_IP}/jobLog`, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
data: {
id
}
})
.then(res => {
if (res.status === 200 && this._isMounted) {
this.setState({
slotLog: res.data.trial_slot_log,
processLog: res.data.trial_process_log
});
}
});
});
}
}
});
}
getPaiDetail = (id: string) => {
axios(`${MANAGER_IP}/jobPaiPage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
data: {
id
}
})
.then(res => {
if (res.status === 200) {
message.success('Successful send');
setTimeout(this.openPage(res.data.url), 100);
}
});
}
openPage = (pailog: string) => {
window.open(pailog);
}
paiLog = () => {
axios(`${MANAGER_IP}/paiPage`, {
method: 'POST'
})
.then(res => {
if (res.status === 200) {
setTimeout(this.openPage(res.data.url), 200);
}
});
}
componentDidMount() {
this._isMounted = true;
this.getJobLog();
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { trialId, slotLog, processLog } = this.state;
return (
<div className="log">
<div>
<Tabs type="card">
<TabPane tab="trial_slot_log" key="1">
<pre>{slotLog}</pre>
</TabPane>
<TabPane tab="trial_process_log" key="2">
<pre>{processLog}</pre>
</TabPane>
</Tabs>
</div>
<div className="pai">
<Button
type="primary"
className="tableButton marginTab"
onClick={this.getPaiDetail.bind(this, trialId)}
>
pai
</Button>
<Button
type="primary"
className="tableButton"
onClick={this.paiLog}
>
main job log
</Button>
</div>
</div>
);
}
}
export default Logdetail;
\ No newline at end of file
......@@ -35,6 +35,11 @@ interface ParaObj {
parallelAxis: Array<Dimobj>;
}
interface VisualMapValue {
maxAccuracy: number;
minAccuracy: number;
}
interface ParaState {
option: object;
paraBack: ParaObj;
......@@ -42,6 +47,7 @@ interface ParaState {
swapAxisArr: Array<string>;
percent: number;
paraNodata: string;
visualValue: VisualMapValue;
}
message.config({
......@@ -69,6 +75,10 @@ class Para extends React.Component<{}, ParaState> {
swapAxisArr: [],
percent: 0,
paraNodata: '',
visualValue: {
minAccuracy: 0,
maxAccuracy: 1
}
};
}
......@@ -110,7 +120,11 @@ class Para extends React.Component<{}, ParaState> {
const dimName = Object.keys(speDimName[0]);
if (this._isMounted) {
this.setState(() => ({
dimName: dimName
dimName: dimName,
visualValue: {
minAccuracy: accPara.length !== 0 ? Math.min(...accPara) : 0,
maxAccuracy: accPara.length !== 0 ? Math.max(...accPara) : 1
}
}));
}
// search space range and specific value [only number]
......@@ -159,6 +173,11 @@ class Para extends React.Component<{}, ParaState> {
Object.keys(paraYdata).map(item => {
paraYdata[item].push(accPara[item]);
});
// according acc to sort ydata
if (paraYdata.length !== 0) {
const len = paraYdata[0].length - 1;
paraYdata.sort((a, b) => b[len] - a[len]);
}
this.setState(() => ({
paraBack: {
parallelAxis: parallelAxis,
......@@ -205,6 +224,7 @@ class Para extends React.Component<{}, ParaState> {
// deal with response data into pic data
getOption = (dataObj: ParaObj) => {
const { visualValue } = this.state;
let parallelAxis = dataObj.parallelAxis;
let paralleData = dataObj.data;
let optionown = {
......@@ -223,8 +243,6 @@ class Para extends React.Component<{}, ParaState> {
borderColor: '#ddd'
}
},
feature: {
},
z: 202
},
parallel: {
......@@ -236,8 +254,8 @@ class Para extends React.Component<{}, ParaState> {
},
visualMap: {
type: 'continuous',
min: 0,
max: 1,
min: visualValue.minAccuracy,
max: visualValue.maxAccuracy,
// gradient color
color: ['#fb7c7c', 'yellow', 'lightblue']
},
......@@ -363,7 +381,7 @@ class Para extends React.Component<{}, ParaState> {
<div className="paraTitle">
<div className="paraLeft">Hyper Parameter</div>
<div className="paraRight">
{/* <span>top</span> */}
<span>top</span>
<Select
className="parapercent"
style={{ width: '20%' }}
......
import * as React from 'react';
import axios from 'axios';
import { Table, Select, Row, Col, Icon } from 'antd';
import { MANAGER_IP, overviewItem, roundNum } from '../const';
// import ReactEcharts from 'echarts-for-react';
import { MANAGER_IP, overviewItem } from '../const';
const Option = Select.Option;
import JSONTree from 'react-json-tree';
// require('echarts/lib/chart/line');
// require('echarts/lib/component/tooltip');
// require('echarts/lib/component/title');
require('../style/sessionpro.css');
interface TableObj {
......@@ -183,6 +179,13 @@ class Sessionpro extends React.Component<{}, SessionState> {
let sessionData = res.data;
let tunerAsstemp = [];
let trialPro = [];
const startExper = new Date(sessionData.startTime).toLocaleString();
let experEndStr: string;
if (sessionData.endTime !== undefined) {
experEndStr = new Date(sessionData.endTime).toLocaleString();
} else {
experEndStr = 'not over';
}
trialPro.push({
id: sessionData.id,
author: sessionData.params.authorName,
......@@ -191,8 +194,8 @@ class Sessionpro extends React.Component<{}, SessionState> {
maxDuration: sessionData.params.maxExecDuration,
execDuration: sessionData.execDuration,
MaxTrialNum: sessionData.params.maxTrialNum,
startTime: sessionData.startTime,
endTime: sessionData.endTime === undefined ? 'not over' : sessionData.endTime
startTime: startExper,
endTime: experEndStr
});
tunerAsstemp.push({
tuner: sessionData.params.tuner,
......@@ -225,14 +228,12 @@ class Sessionpro extends React.Component<{}, SessionState> {
const desJobDetail: Parameters = {
parameters: {}
};
const startTime = Date.parse(tableData[item].startTime);
const duration = (Date.parse(tableData[item].endTime) - startTime) / 1000;
const startTime = new Date(tableData[item].startTime).toLocaleString();
const endTime = new Date(tableData[item].endTime).toLocaleString();
const duration = (tableData[item].endTime - tableData[item].startTime) / 1000;
let acc;
if (tableData[item].finalMetricData) {
const accFloat = parseFloat(tableData[item].finalMetricData.data);
acc = roundNum(accFloat, 5);
} else {
acc = 0;
acc = parseFloat(tableData[item].finalMetricData.data);
}
desJobDetail.parameters = JSON.parse(tableData[item].hyperParameters).parameters;
if (tableData[item].logPath !== undefined) {
......@@ -242,8 +243,8 @@ class Sessionpro extends React.Component<{}, SessionState> {
key: topTableData.length,
id: tableData[item].id,
duration: duration,
start: tableData[item].startTime,
end: tableData[item].endTime,
start: startTime,
end: endTime,
status: tableData[item].status,
acc: acc,
description: desJobDetail
......@@ -265,27 +266,6 @@ class Sessionpro extends React.Component<{}, SessionState> {
trialRun: trialRunData.sort(this.sortNumber)
});
}
// draw CDF
// const { trialRun } = this.state;
// if (this._isMounted) {
// this.setState({
// option: this.getOption(trialRun)
// });
// }
// CDF graph 'No data' judge
// if (trialRun.length === 0) {
// if (this._isMounted) {
// this.setState({
// noData: 'No data'
// });
// }
// } else {
// if (this._isMounted) {
// this.setState({
// noData: ''
// });
// }
// }
}
});
}
......@@ -424,9 +404,6 @@ class Sessionpro extends React.Component<{}, SessionState> {
</p>
<span>End Time</span>
<p className="messcont">{trialProfile.endTime}</p>
{/* <div className="logo">
<Icon className="thrpink" type="clock-circle-o" />
</div> */}
</div>
<div className="cdf">
<div className="message">
......@@ -501,13 +478,6 @@ class Sessionpro extends React.Component<{}, SessionState> {
scroll={{ x: '100%', y: 540 }}
/>
</div>
{/* <div className="cdf">
<ReactEcharts
option={option}
style={{ height: 500, padding: '0px' }}
/>
<div className="addNodata">{noData}</div>
</div> */}
</div>
);
}
......
import * as React from 'react';
import { browserHistory } from 'react-router';
import axios from 'axios';
import { Table, Button, Popconfirm, message } from 'antd';
import { MANAGER_IP, roundNum, trialJobStatus } from '../const';
import { Table, Button, Popconfirm, message, Modal } from 'antd';
import { MANAGER_IP, trialJobStatus } from '../const';
import JSONTree from 'react-json-tree';
import ReactEcharts from 'echarts-for-react';
const echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/bar');
require('echarts/lib/chart/line');
require('echarts/lib/chart/scatter');
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('../style/trialStatus.css');
......@@ -45,6 +47,8 @@ interface TabState {
downhref: string;
option: object;
trialJobs: object;
intermediateOption: object;
modalVisible: boolean;
}
class TrialStatus extends React.Component<{}, TabState> {
......@@ -71,11 +75,50 @@ class TrialStatus extends React.Component<{}, TabState> {
}],
downhref: 'javascript:;',
option: {},
trialJobs: {}
// trialJobs: this.getTrialJobs()
intermediateOption: {},
trialJobs: {},
modalVisible: false
};
}
showIntermediateModal = (id: string) => {
axios(`${MANAGER_IP}/metric-data/${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
})
.then(res => {
if (res.status === 200) {
const intermediateArr: number[] = [];
const xinter: number[] = [];
Object.keys(res.data).map(item => {
intermediateArr.push(parseFloat(res.data[item].data));
xinter.push(res.data[item].sequence);
});
if (this._isMounted) {
this.setState({
intermediateOption: this.intermediateGraphOption(intermediateArr, id, xinter)
});
}
}
});
if (this._isMounted) {
this.setState({
modalVisible: true
});
}
}
hideIntermediateModal = () => {
if (this._isMounted) {
this.setState({
modalVisible: false
});
}
}
getOption = (dataObj: Runtrial) => {
let xAxis = dataObj.trialTime;
let yAxis = dataObj.trialId;
......@@ -146,9 +189,9 @@ class TrialStatus extends React.Component<{}, TabState> {
const end = trialJobs[item].endTime;
const start = trialJobs[item].startTime;
if (start && end) {
duration = (Date.parse(end) - Date.parse(start)) / 1000;
duration = (end - start) / 1000;
} else {
duration = (new Date().getTime() - Date.parse(start)) / 1000;
duration = (new Date().getTime() - start) / 1000;
}
trialId.push(trialJobs[item].id);
trialTime.push(duration);
......@@ -184,14 +227,11 @@ class TrialStatus extends React.Component<{}, TabState> {
const status = trialJobs[item].status !== undefined
? trialJobs[item].status
: '';
const acc = trialJobs[item].finalMetricData !== undefined
? roundNum(parseFloat(trialJobs[item].finalMetricData.data), 5)
: 0;
const startTime = trialJobs[item].startTime !== undefined
? trialJobs[item].startTime
? new Date(trialJobs[item].startTime).toLocaleString()
: '';
const endTime = trialJobs[item].endTime !== undefined
? trialJobs[item].endTime
? new Date(trialJobs[item].endTime).toLocaleString()
: '';
let desc: DescObj = {
parameters: {}
......@@ -202,11 +242,15 @@ class TrialStatus extends React.Component<{}, TabState> {
if (trialJobs[item].logPath !== undefined) {
desc.logPath = trialJobs[item].logPath;
}
let acc = 0;
if (trialJobs[item].finalMetricData !== undefined) {
acc = parseFloat(trialJobs[item].finalMetricData.data);
}
let duration = 0;
if (startTime !== '' && endTime !== '') {
duration = (Date.parse(endTime) - Date.parse(startTime)) / 1000;
duration = (trialJobs[item].endTime - trialJobs[item].startTime) / 1000;
} else if (startTime !== '' && endTime === '') {
duration = (new Date().getTime() - Date.parse(startTime)) / 1000;
duration = (new Date().getTime() - trialJobs[item].startTime) / 1000;
} else {
duration = 0;
}
......@@ -262,6 +306,28 @@ class TrialStatus extends React.Component<{}, TabState> {
browserHistory.push(path);
}
intermediateGraphOption = (intermediateArr: number[], id: string, xinter: number[]) => {
return {
tooltip: {
trigger: 'item'
},
xAxis: {
name: 'Trial',
data: xinter
},
yAxis: {
name: 'Accuracy',
type: 'value',
data: intermediateArr
},
series: [{
symbolSize: 6,
type: 'scatter',
data: intermediateArr
}]
};
}
componentDidMount() {
this._isMounted = true;
......@@ -281,6 +347,7 @@ class TrialStatus extends React.Component<{}, TabState> {
}
render() {
const { intermediateOption, modalVisible, option, tableData } = this.state;
let bgColor = '';
const trialJob: Array<TrialJob> = [];
trialJobStatus.map(item => {
......@@ -401,11 +468,17 @@ class TrialStatus extends React.Component<{}, TabState> {
getItemString={() => (<span />)} // remove the {} items
data={record.description}
/>
<Button
type="primary"
className="tableButton"
onClick={this.showIntermediateModal.bind(this, record.id)}
>
Intermediate Result
</Button>
</pre>
);
};
const { option, tableData } = this.state;
return (
<div className="hyper" id="tableCenter">
<ReactEcharts
......@@ -422,6 +495,23 @@ class TrialStatus extends React.Component<{}, TabState> {
bordered={true}
scroll={{ x: '100%', y: window.innerHeight * 0.78 }}
/>
<Modal
title="Intermediate Result"
visible={modalVisible}
onCancel={this.hideIntermediateModal}
footer={null}
destroyOnClose={true}
width="80%"
>
<ReactEcharts
option={intermediateOption}
style={{
width: '100%',
height: 0.7 * window.innerHeight
}}
theme="my_theme"
/>
</Modal>
</div>
);
}
......
......@@ -14,4 +14,3 @@ export const CONTROLTYPE = [
'MAX_EXEC_DURATION'
];
export const overviewItem = 5;
export const roundNum = (acc: number, n: number) => Math.round(acc * 10 ** n) / 10 ** n;
......@@ -10,12 +10,10 @@ import TrialStatus from './components/TrialStatus';
import Tensor from './components/Tensor';
import Control from './components/Control';
import Sessionpro from './components/Sessionpro';
import Logdetail from './components/Logdetail';
import './index.css';
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/log" component={Logdetail} />
<Route path="/" component={App}>
<IndexRedirect to="/oview" />
<Route path="/oview" component={Sessionpro} />
......
......@@ -52,8 +52,8 @@ pre.hyperpar{
Button.tableButton{
background: #3c8dbc;
border-color: #3c8dbc;
height: 26px;
line-height: 26px;
height: 28px;
line-height: 28px;
/* padding: 3px 4px; */
}
/* status bgcolor */
......@@ -98,6 +98,9 @@ Button.tableButton:hover, Button.tableButton:focus{
background-color: #3c8dbc;
border-color: #3c8dbc;
}
.ant-modal-title{
font-size: 20px;
}
......@@ -12,10 +12,16 @@ tuner:
codeDir: .
classFileName: naive_tuner.py
className: NaiveTuner
classArgs:
optimize_mode: maximize
gpuNum: 0
assessor:
codeDir: .
classFileName: naive_assessor.py
className: NaiveAssessor
classArgs:
optimize_mode: maximize
gpuNum: 0
trial:
command: python3 naive_trial.py
codeDir: .
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment