util.ts 8.09 KB
Newer Older
liuzhe-lz's avatar
liuzhe-lz committed
1
2
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
3
4
5

'use strict';

6
7
import * as cpp from 'child-process-promise';
import * as cp from 'child_process';
8
import * as fs from 'fs';
9
import ignore from 'ignore';
10
import * as path from 'path';
11
import * as tar from 'tar';
12
import { getLogger } from '../../common/log';
13
import { String } from 'typescript-string-operations';
14
import { GPU_INFO_COLLECTOR_FORMAT_WINDOWS } from './gpuData';
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
/**
 * List all files in directory except those ignored by .nniignore.
 * @param source
 * @param destination
 */
export function* listDirWithIgnoredFiles(root: string, relDir: string, ignoreFiles: string[]): Iterable<string> {
    let ignoreFile = undefined;
    const source = path.join(root, relDir);
    if (fs.existsSync(path.join(source, '.nniignore'))) {
        ignoreFile = path.join(source, '.nniignore');
        ignoreFiles.push(ignoreFile);
    }
    const ig = ignore();
    ignoreFiles.forEach((i) => ig.add(fs.readFileSync(i).toString()));
    for (const d of fs.readdirSync(source)) {
        const entry = path.join(relDir, d);
        if (ig.ignores(entry))
            continue;
        const entryStat = fs.statSync(path.join(root, entry));
        if (entryStat.isDirectory()) {
            yield entry;
            yield* listDirWithIgnoredFiles(root, entry, ignoreFiles);
        }
        else if (entryStat.isFile())
            yield entry;
    }
    if (ignoreFile !== undefined) {
        ignoreFiles.pop();
    }
}

47
48
/**
 * Validate codeDir, calculate file count recursively under codeDir, and throw error if any rule is broken
49
 *
50
51
52
 * @param codeDir codeDir in nni config file
 * @returns file number under codeDir
 */
chicm-ms's avatar
chicm-ms committed
53
export async function validateCodeDir(codeDir: string): Promise<number> {
54
55
56
57
58
59
60
61
62
63
64
65
66
    let fileCount: number = 0;
    let fileTotalSize: number = 0;
    for (const relPath of listDirWithIgnoredFiles(codeDir, '', [])) {
        const d = path.join(codeDir, relPath);
        fileCount += 1;
        fileTotalSize += fs.statSync(d).size;
        if (fileCount > 2000) {
            throw new Error(`Too many files and directories (${fileCount} already scanned) in ${codeDir},`
                + ` please check if it's a valid code dir`);
        }
        if (fileTotalSize > 300 * 1024 * 1024) {
            throw new Error(`File total size too large in code dir (${fileTotalSize} bytes already scanned, exceeds 300MB).`);
        }
67
68
69
        // NOTE: We added this test in case any training service or shared storage (e.g. HDFS) does not support complex file name.
        // If there is no bug found for long time, feel free to remove it.
        const fileNameValid = relPath.split(path.sep).every(fpart => (fpart.match('^[a-z0-9A-Z._-]*$') !== null));
70
        if (!fileNameValid) {
71
72
73
74
75
76
            const message = [
                `File ${relPath} in directory ${codeDir} contains spaces or special characters in its name.`,
                'This might cause problem when uploading to cloud or remote machine.',
                'If you encounter any error, please report an issue: https://github.com/microsoft/nni/issues'
            ].join(' ');
            getLogger('validateCodeDir').warning(message);
77
        }
78
    }
79
80

    return fileCount;
81
82
83
84
}

/**
 * crete a new directory
85
 * @param directory
86
 */
87
export async function execMkdir(directory: string, share: boolean = false): Promise<void> {
88
    if (process.platform === 'win32') {
SparkSnail's avatar
SparkSnail committed
89
        await cpp.exec(`powershell.exe New-Item -Path "${directory}" -ItemType "directory" -Force`);
90
    } else if (share) {
SparkSnail's avatar
SparkSnail committed
91
        await cpp.exec(`(umask 0; mkdir -p '${directory}')`);
92
    } else {
SparkSnail's avatar
SparkSnail committed
93
        await cpp.exec(`mkdir -p '${directory}'`);
94
    }
95

96
97
98
    return Promise.resolve();
}

99
100
101
102
103
104
/**
 * copy files to the directory
 * @param source
 * @param destination
 */
export async function execCopydir(source: string, destination: string): Promise<void> {
105
106
107
108
109
110
    if (!fs.existsSync(destination))
        await fs.promises.mkdir(destination);
    for (const relPath of listDirWithIgnoredFiles(source, '', [])) {
        const sourcePath = path.join(source, relPath);
        const destPath = path.join(destination, relPath);
        if (fs.statSync(sourcePath).isDirectory()) {
SparkSnail's avatar
SparkSnail committed
111
112
113
            if (!fs.existsSync(destPath)) {
                await fs.promises.mkdir(destPath);
            }
114
        } else {
liuzhe-lz's avatar
liuzhe-lz committed
115
            getLogger('execCopydir').debug(`Copying file from ${sourcePath} to ${destPath}`);
116
117
            await fs.promises.copyFile(sourcePath, destPath);
        }
118
    }
119

120
121
122
    return Promise.resolve();
}

123
124
/**
 * crete a new file
125
 * @param filename
126
127
128
 */
export async function execNewFile(filename: string): Promise<void> {
    if (process.platform === 'win32') {
SparkSnail's avatar
SparkSnail committed
129
        await cpp.exec(`powershell.exe New-Item -Path "${filename}" -ItemType "file" -Force`);
130
    } else {
SparkSnail's avatar
SparkSnail committed
131
        await cpp.exec(`touch '${filename}'`);
132
    }
133

134
135
136
137
    return Promise.resolve();
}

/**
138
 * run script using powershell or bash
139
140
 * @param filePath
 */
141
export function runScript(filePath: string): cp.ChildProcess {
142
    if (process.platform === 'win32') {
SparkSnail's avatar
SparkSnail committed
143
        return cp.exec(`powershell.exe -ExecutionPolicy Bypass -file "${filePath}"`);
144
    } else {
SparkSnail's avatar
SparkSnail committed
145
        return cp.exec(`bash '${filePath}'`);
146
147
148
149
150
    }
}

/**
 * output the last line of a file
151
 * @param filePath
152
153
154
155
 */
export async function execTail(filePath: string): Promise<cpp.childProcessPromise.Result> {
    let cmdresult: cpp.childProcessPromise.Result;
    if (process.platform === 'win32') {
SparkSnail's avatar
SparkSnail committed
156
        cmdresult = await cpp.exec(`powershell.exe Get-Content "${filePath}" -Tail 1`);
157
    } else {
SparkSnail's avatar
SparkSnail committed
158
        cmdresult = await cpp.exec(`tail -n 1 '${filePath}'`);
159
    }
160

161
162
163
164
165
    return Promise.resolve(cmdresult);
}

/**
 * delete a directory
166
 * @param directory
167
 */
168
export async function execRemove(directory: string): Promise<void> {
169
    if (process.platform === 'win32') {
SparkSnail's avatar
SparkSnail committed
170
        await cpp.exec(`powershell.exe Remove-Item "${directory}" -Recurse -Force`);
171
    } else {
SparkSnail's avatar
SparkSnail committed
172
        await cpp.exec(`rm -rf '${directory}'`);
173
    }
174

175
176
177
178
179
    return Promise.resolve();
}

/**
 * kill a process
180
 * @param directory
181
 */
182
export async function execKill(pid: string): Promise<void> {
183
    if (process.platform === 'win32') {
Yuge Zhang's avatar
Yuge Zhang committed
184
        await cpp.exec(`cmd.exe /c taskkill /PID ${pid} /T /F`);
185
186
187
    } else {
        await cpp.exec(`pkill -P ${pid}`);
    }
188

189
190
191
192
    return Promise.resolve();
}

/**
193
 * get command of setting environment variable
194
 * @param  variable
195
 * @returns command string
196
 */
197
export function setEnvironmentVariable(variable: { key: string; value: string }): string {
198
199
    if (process.platform === 'win32') {
        return `$env:${variable.key}="${variable.value}"`;
200
    } else {
SparkSnail's avatar
SparkSnail committed
201
        return `export ${variable.key}='${variable.value}'`;
202
203
204
    }
}

205
206
/**
 * Compress files in directory to tar file
207
208
 * @param  sourcePath
 * @param  tarPath
209
 */
210
export async function tarAdd(tarPath: string, sourcePath: string): Promise<void> {
211
212
213
    const fileList = [];
    for (const d of listDirWithIgnoredFiles(sourcePath, '', [])) {
        fileList.push(d);
214
    }
215
216
217
218
219
220
221
222
223
    tar.create(
        {
            gzip: true,
            file: tarPath,
            sync: true,
            cwd: sourcePath,
        },
        fileList
    );
224
225
    return Promise.resolve();
}
226
227
228

/**
 * generate script file name
229
 * @param fileNamePrefix
230
231
232
 */
export function getScriptName(fileNamePrefix: string): string {
    if (process.platform === 'win32') {
233
        return String.Format('{0}.ps1', fileNamePrefix);
234
    } else {
235
        return String.Format('{0}.sh', fileNamePrefix);
236
237
238
    }
}

239
export function getGpuMetricsCollectorBashScriptContent(scriptFolder: string): string {
240
241
    return `echo $$ > ${scriptFolder}/pid ; METRIC_OUTPUT_DIR=${scriptFolder} python3 -m nni.tools.gpu_tool.gpu_metrics_collector \
1>${scriptFolder}/stdout 2>${scriptFolder}/stderr`;
242
243
244
}

export function runGpuMetricsCollector(scriptFolder: string): void {
245
    if (process.platform === 'win32') {
246
247
248
        const scriptPath = path.join(scriptFolder, 'gpu_metrics_collector.ps1');
        const content = String.Format(GPU_INFO_COLLECTOR_FORMAT_WINDOWS, scriptFolder, path.join(scriptFolder, 'pid'));
        fs.writeFile(scriptPath, content, { encoding: 'utf8' }, () => { runScript(scriptPath); });
249
    } else {
250
        cp.exec(getGpuMetricsCollectorBashScriptContent(scriptFolder), { shell: '/bin/bash' });
251
252
    }
}