mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 10:33:59 -05:00
- latest biome, and fix its configuration - fixes "static" content to be globally configured too (instead of per-line) - fixes issues: - imports fixed up - `Date.now()` vs `+new Date()` - some unused things `_` prefixed After discussion with the team, turned off the unused parameter warning.
193 lines
7.3 KiB
TypeScript
193 lines
7.3 KiB
TypeScript
// Copyright (c) 2023, Compiler Explorer Authors
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import * as oldfs from 'node:fs';
|
|
import {createWriteStream, constants as fsConstants, WriteStream} from 'node:fs';
|
|
import * as fs from 'node:fs/promises';
|
|
import * as net from 'node:net';
|
|
import path from 'node:path';
|
|
import {pipeline} from 'node:stream';
|
|
|
|
import {ExecutionOptions} from '../../types/compilation/compilation.interfaces.js';
|
|
import {
|
|
RuntimeToolOptions,
|
|
TypicalExecutionFunc,
|
|
UnprocessedExecResult,
|
|
} from '../../types/execution/execution.interfaces.js';
|
|
import {CompilationEnvironment} from '../compilation-env.js';
|
|
import {executeDirect} from '../exec.js';
|
|
import {logger} from '../logger.js';
|
|
import {PropertyGetter} from '../properties.interfaces.js';
|
|
|
|
import {BaseRuntimeTool} from './base-runtime-tool.js';
|
|
|
|
const O_NONBLOCK = fsConstants.O_NONBLOCK;
|
|
|
|
export class HeaptrackWrapper extends BaseRuntimeTool {
|
|
private rawOutput: string;
|
|
private pipe: string;
|
|
private interpretedPath: string;
|
|
private heaptrackPath: string;
|
|
private mkfifoPath: string;
|
|
private preload: string;
|
|
private interpreter: string;
|
|
private printer: string;
|
|
|
|
public static FlamegraphFilename = 'heaptrack.flamegraph.txt';
|
|
|
|
constructor(
|
|
dirPath: string,
|
|
sandboxFunc: TypicalExecutionFunc,
|
|
execFunc: TypicalExecutionFunc,
|
|
options: RuntimeToolOptions,
|
|
ceProps: PropertyGetter,
|
|
sandboxType: string,
|
|
) {
|
|
super(dirPath, sandboxFunc, execFunc, options, sandboxType);
|
|
|
|
this.mkfifoPath = ceProps('mkfifo', '/usr/bin/mkfifo');
|
|
|
|
this.pipe = path.join(this.dirPath, 'heaptrack_fifo');
|
|
this.rawOutput = path.join(this.dirPath, 'heaptrack_raw.txt');
|
|
this.interpretedPath = path.join(this.dirPath, 'heaptrack_interpreted.txt');
|
|
|
|
this.heaptrackPath = ceProps('heaptrackPath', '');
|
|
|
|
this.preload = path.join(this.heaptrackPath, 'lib/libheaptrack_preload.so');
|
|
this.interpreter = path.join(this.heaptrackPath, 'libexec/heaptrack_interpret');
|
|
this.printer = path.join(this.heaptrackPath, 'bin/heaptrack_print');
|
|
}
|
|
|
|
public static isSupported(compiler: CompilationEnvironment) {
|
|
return process.platform !== 'win32' && compiler.ceProps('heaptrackPath', '') !== '';
|
|
}
|
|
|
|
private async mkfifo(path: string, rights: number) {
|
|
await executeDirect(this.mkfifoPath, ['-m', rights.toString(8), path], {});
|
|
}
|
|
|
|
private async makePipe() {
|
|
await this.mkfifo(this.pipe, 0o666);
|
|
}
|
|
|
|
private addToEnv(execOptions: ExecutionOptions) {
|
|
if (!execOptions.env) execOptions.env = {};
|
|
|
|
if (execOptions.env.LD_PRELOAD) {
|
|
execOptions.env.LD_PRELOAD = this.preload + ':' + execOptions.env.LD_PRELOAD;
|
|
} else {
|
|
execOptions.env.LD_PRELOAD = this.preload;
|
|
}
|
|
|
|
if (this.sandboxType === 'nsjail') {
|
|
execOptions.env.DUMP_HEAPTRACK_OUTPUT = '/app/heaptrack_fifo';
|
|
} else {
|
|
execOptions.env.DUMP_HEAPTRACK_OUTPUT = this.pipe;
|
|
}
|
|
}
|
|
|
|
private async interpret(execOptions: ExecutionOptions): Promise<UnprocessedExecResult> {
|
|
return this.execFunc(this.interpreter, [this.rawOutput], execOptions);
|
|
}
|
|
|
|
private async finishPipesAndStreams(fd: number, file: WriteStream, socket: net.Socket): Promise<void> {
|
|
socket.push(null);
|
|
await new Promise(resolve => socket.end(() => resolve(true)));
|
|
|
|
await new Promise(resolve => file.end(() => resolve(true)));
|
|
|
|
file.write(Buffer.from([0]));
|
|
|
|
// Don't manually close fd - the socket owns it and closes it during cleanup
|
|
if (socket.resetAndDestroy) socket.resetAndDestroy();
|
|
socket.unref();
|
|
|
|
await new Promise(resolve => {
|
|
file.close(err => {
|
|
if (err) logger.error('Error while closing heaptrack log: ', err);
|
|
resolve(true);
|
|
});
|
|
});
|
|
}
|
|
|
|
private async interpretAndSave(execOptions: ExecutionOptions, result: UnprocessedExecResult) {
|
|
execOptions.input = await fs.readFile(this.rawOutput, 'utf-8');
|
|
|
|
const interpretResults = await this.interpret(execOptions);
|
|
|
|
if (this.getOptionValue('summary') === 'stderr') {
|
|
result.stderr += interpretResults.stderr;
|
|
}
|
|
|
|
await fs.writeFile(this.interpretedPath, interpretResults.stdout);
|
|
}
|
|
|
|
private async saveFlamegraph(execOptions: ExecutionOptions, result: UnprocessedExecResult) {
|
|
const args = [this.interpretedPath];
|
|
|
|
if (this.getOptionValue('graph') === 'yes') {
|
|
const flamesFilepath = path.join(this.dirPath, HeaptrackWrapper.FlamegraphFilename);
|
|
args.push('-F', flamesFilepath);
|
|
}
|
|
|
|
const printResults = await this.execFunc(this.printer, args, execOptions);
|
|
if (printResults.stderr) result.stderr += printResults.stderr;
|
|
|
|
if (this.getOptionValue('details') === 'stderr') {
|
|
result.stderr += printResults.stdout;
|
|
}
|
|
}
|
|
|
|
public async exec(filepath: string, args: string[], execOptions: ExecutionOptions): Promise<UnprocessedExecResult> {
|
|
const runOptions = JSON.parse(JSON.stringify(execOptions));
|
|
const interpretOptions = JSON.parse(JSON.stringify(execOptions));
|
|
interpretOptions.maxOutput = 1024 * 1024 * 1024;
|
|
this.addToEnv(runOptions);
|
|
|
|
await this.makePipe();
|
|
|
|
const fd = oldfs.openSync(this.pipe, O_NONBLOCK | fsConstants.O_RDWR);
|
|
const socket = new net.Socket({fd: fd, readable: true, writable: true});
|
|
|
|
const file = createWriteStream(this.rawOutput);
|
|
pipeline(socket, file, err => {
|
|
if (err) {
|
|
logger.error('Error during heaptrack pipeline: ', err);
|
|
}
|
|
});
|
|
|
|
const result = await this.sandboxFunc(filepath, args, runOptions);
|
|
|
|
await this.finishPipesAndStreams(fd, file, socket);
|
|
|
|
await fs.unlink(this.pipe);
|
|
|
|
await this.interpretAndSave(interpretOptions, result);
|
|
|
|
await this.saveFlamegraph(execOptions, result);
|
|
|
|
return result;
|
|
}
|
|
}
|