提交 8a912886 编写于 作者: C chrmarti 提交者: GitHub

#55: Batch results for IPC (#9380)

上级 e5ac0b63
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import uri from 'vs/base/common/uri';
import { always } from 'vs/base/common/async';
import { ITestChannel, TestServiceClient, ITestService } from './testService';
function createClient(): Client {
return new Client(uri.parse(require.toUrl('bootstrap')).fsPath, {
serverName: 'TestServer',
env: { AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true }
});
}
// Rename to ipc.perf.test.ts and run with ./scripts/test.sh --grep IPC.performance --timeout 60000
suite('IPC performance', () => {
test('increasing batch size', () => {
if (process.env['VSCODE_PID']) {
return; // TODO@Ben find out why test fails when run from within VS Code
}
const client = createClient();
const channel = client.getChannel<ITestChannel>('test');
const service = new TestServiceClient(channel);
const runs = [
{ batches: 250000, size: 1 },
{ batches: 2500, size: 100 },
{ batches: 500, size: 500 },
{ batches: 250, size: 1000 },
{ batches: 50, size: 5000 },
{ batches: 25, size: 10000 },
// { batches: 10, size: 25000 },
// { batches: 5, size: 50000 },
// { batches: 1, size: 250000 },
];
const dataSizes = [
100,
250,
];
let i = 0, j = 0;
const result = measure(service, 10, 10, 250) // warm-up
.then(() => {
return (function nextRun() {
if (i >= runs.length) {
if (++j >= dataSizes.length) {
return;
}
i = 0;
}
const run = runs[i++];
return measure(service, run.batches, run.size, dataSizes[j])
.then(() => {
return nextRun();
});
})();
});
return always(result, () => client.dispose());
});
test('increasing raw data size', () => {
if (process.env['VSCODE_PID']) {
return; // TODO@Ben find out why test fails when run from within VS Code
}
const client = createClient();
const channel = client.getChannel<ITestChannel>('test');
const service = new TestServiceClient(channel);
const runs = [
{ batches: 250000, dataSize: 100 },
{ batches: 25000, dataSize: 1000 },
{ batches: 2500, dataSize: 10000 },
{ batches: 1250, dataSize: 20000 },
{ batches: 500, dataSize: 50000 },
{ batches: 250, dataSize: 100000 },
{ batches: 125, dataSize: 200000 },
{ batches: 50, dataSize: 500000 },
{ batches: 25, dataSize: 1000000 },
];
let i = 0;
const result = measure(service, 10, 10, 250) // warm-up
.then(() => {
return (function nextRun() {
if (i >= runs.length) {
return;
}
const run = runs[i++];
return measure(service, run.batches, 1, run.dataSize)
.then(() => {
return nextRun();
});
})();
});
return always(result, () => client.dispose());
});
function measure(service: ITestService, batches: number, size: number, dataSize: number) {
const start = Date.now();
let hits = 0;
let count = 0;
return service.batchPerf(batches, size, dataSize)
.then(() => {
console.log(`Batches: ${batches}, size: ${size}, dataSize: ${dataSize}, n: ${batches * size * dataSize}, duration: ${Date.now() - start}`);
assert.strictEqual(hits, batches);
assert.strictEqual(count, batches * size);
}, err => assert.fail(err),
batch => {
hits++;
count += batch.length;
});
}
});
\ No newline at end of file
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { TPromise, PPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import Event, { Emitter } from 'vs/base/common/event';
......@@ -17,6 +17,7 @@ export interface ITestService {
marco(): TPromise<string>;
pong(ping:string): TPromise<{ incoming:string, outgoing:string }>;
cancelMe(): TPromise<boolean>;
batchPerf(batches: number, size: number, dataSize: number): PPromise<any, any[]>;
}
export class TestService implements ITestService {
......@@ -24,6 +25,8 @@ export class TestService implements ITestService {
private _onMarco = new Emitter<IMarcoPoloEvent>();
onMarco: Event<IMarcoPoloEvent> = this._onMarco.event;
private _data = 'abcdefghijklmnopqrstuvwxyz';
marco(): TPromise<string> {
this._onMarco.fire({ answer: 'polo' });
return TPromise.as('polo');
......@@ -36,12 +39,39 @@ export class TestService implements ITestService {
cancelMe(): TPromise<boolean> {
return TPromise.timeout(100).then(() => true);
}
batchPerf(batches: number, size: number, dataSize: number): PPromise<any, any[]> {
while(this._data.length < dataSize) {
this._data += this._data;
}
const self = this;
return new PPromise((complete, error, progress) => {
let j = 0;
function send() {
if (j >= batches) {
complete(null);
return;
}
j++;
const batch = [];
for (let i = 0; i < size; i++) {
batch.push({
prop: `${i}${self._data}`.substr(0, dataSize)
});
}
progress(batch);
process.nextTick(send);
};
process.nextTick(send);
});
}
}
export interface ITestChannel extends IChannel {
call(command: 'marco'): TPromise<any>;
call(command: 'pong', ping: string): TPromise<any>;
call(command: 'cancelMe'): TPromise<any>;
call(command: 'batchPerf', args: { batches: number; size: number; dataSize: number; }): PPromise<any, any[]>;
call(command: string, ...args: any[]): TPromise<any>;
}
......@@ -55,6 +85,7 @@ export class TestChannel implements ITestChannel {
case 'cancelMe': return this.testService.cancelMe();
case 'marco': return this.testService.marco();
case 'event:marco': return eventToCall(this.testService.onMarco);
case 'batchPerf': return this.testService.batchPerf(args[0].batches, args[0].size, args[0].dataSize);
default: return TPromise.wrapError(new Error('command not found'));
}
}
......@@ -80,4 +111,8 @@ export class TestServiceClient implements ITestService {
cancelMe(): TPromise<boolean> {
return this.channel.call('cancelMe');
}
batchPerf(batches: number, size: number, dataSize: number): PPromise<any, any[]> {
return this.channel.call('batchPerf', { batches, size, dataSize });
}
}
\ No newline at end of file
......@@ -18,10 +18,12 @@ import {IRawSearchService, IRawSearch, ISerializedSearchProgressItem, ISerialize
export class SearchService implements IRawSearchService {
private static BATCH_SIZE = 500;
public fileSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
let engine = new FileSearchEngine(config);
return this.doSearch(engine);
return this.doSearch(engine, SearchService.BATCH_SIZE);
}
public textSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
......@@ -34,18 +36,30 @@ export class SearchService implements IRawSearchService {
maxFilesize: MAX_FILE_SIZE
}));
return this.doSearch(engine);
return this.doSearch(engine, SearchService.BATCH_SIZE);
}
private doSearch(engine: ISearchEngine): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
public doSearch(engine: ISearchEngine, batchSize?: number): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
return new PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>((c, e, p) => {
let batch = [];
engine.search((match) => {
if (match) {
p(match);
if (batchSize) {
batch.push(match);
if (batchSize > 0 && batch.length >= batchSize) {
p(batch);
batch = [];
}
} else {
p(match);
}
}
}, (progress) => {
p(progress);
}, (error, isLimitHit) => {
if (batch.length) {
p(batch);
}
if (error) {
e(error);
} else {
......
......@@ -40,6 +40,5 @@ export interface ISerializedFileMatch {
lineMatches?: ILineMatch[];
}
export interface ISerializedSearchProgressItem extends ISerializedFileMatch, IProgress {
// Marker interface to indicate the possible values for progress calls from the engine
}
// Type of the possible values for progress calls from the engine
export type ISerializedSearchProgressItem = ISerializedFileMatch | ISerializedFileMatch[] | IProgress;
......@@ -17,7 +17,7 @@ import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/unti
import {IModelService} from 'vs/editor/common/services/modelService';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, IRawSearchService} from './search';
import {IRawSearch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedFileMatch, IRawSearchService} from './search';
import {ISearchChannel, SearchChannelClient} from './searchIpc';
export class SearchService implements ISearchService {
......@@ -187,7 +187,7 @@ export class SearchService implements ISearchService {
}
}
class DiskSearch {
export class DiskSearch {
private raw: IRawSearchService;
......@@ -211,7 +211,6 @@ class DiskSearch {
}
public search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> {
let result: IFileMatch[] = [];
let request: PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>;
let rawSearch: IRawSearch = {
......@@ -234,6 +233,11 @@ class DiskSearch {
request = this.raw.textSearch(rawSearch);
}
return DiskSearch.collectResults(request);
}
public static collectResults(request: PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem>): PPromise<ISearchComplete, ISearchProgressItem> {
let result: IFileMatch[] = [];
return new PPromise<ISearchComplete, ISearchProgressItem>((c, e, p) => {
request.done((complete) => {
c({
......@@ -242,17 +246,17 @@ class DiskSearch {
});
}, e, (data) => {
// Matches
if (Array.isArray(data)) {
const fileMatches = data.map(d => this.createFileMatch(d));
result = result.concat(fileMatches);
fileMatches.forEach(p);
}
// Match
if (data.path) {
let fileMatch = new FileMatch(uri.file(data.path));
else if ((<ISerializedFileMatch>data).path) {
const fileMatch = this.createFileMatch(data);
result.push(fileMatch);
if (data.lineMatches) {
for (let j = 0; j < data.lineMatches.length; j++) {
fileMatch.lineMatches.push(new LineMatch(data.lineMatches[j].preview, data.lineMatches[j].lineNumber, data.lineMatches[j].offsetAndLengths));
}
}
p(fileMatch);
}
......@@ -263,4 +267,14 @@ class DiskSearch {
});
}, () => request.cancel());
}
private static createFileMatch(data: ISerializedFileMatch): FileMatch {
let fileMatch = new FileMatch(uri.file(data.path));
if (data.lineMatches) {
for (let j = 0; j < data.lineMatches.length; j++) {
fileMatch.lineMatches.push(new LineMatch(data.lineMatches[j].preview, data.lineMatches[j].lineNumber, data.lineMatches[j].offsetAndLengths));
}
}
return fileMatch;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import assert = require('assert');
import {IProgress} from 'vs/platform/search/common/search';
import {ISearchEngine, ISerializedFileMatch} from 'vs/workbench/services/search/node/search';
import {SearchService as RawSearchService} from 'vs/workbench/services/search/node/rawSearchService';
import {DiskSearch} from 'vs/workbench/services/search/node/searchService';
class TestSearchEngine implements ISearchEngine {
constructor(private result: () => ISerializedFileMatch) {
}
public search(onResult: (match: ISerializedFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, isLimitHit: boolean) => void): void {
const self = this;
(function next() {
process.nextTick(() => {
const result = self.result();
if (!result) {
done(null, false);
} else {
onResult(result);
next();
}
});
})();
}
public cancel(): void {
}
}
suite('SearchService', () => {
test('Individual results', function () {
const path = '/some/where';
let i = 5;
const engine = new TestSearchEngine(() => i-- && { path });
const service = new RawSearchService();
let results = 0;
return service.doSearch(engine)
.then(() => {
assert.strictEqual(results, 5);
}, null, value => {
if (!Array.isArray(value)) {
assert.strictEqual((<any>value).path, path);
results++;
} else {
assert.fail(value);
}
});
});
test('Batch results', function () {
const path = '/some/where';
let i = 25;
const engine = new TestSearchEngine(() => i-- && { path });
const service = new RawSearchService();
const results = [];
return service.doSearch(engine, 10)
.then(() => {
assert.deepStrictEqual(results, [10, 10, 5]);
}, null, value => {
if (Array.isArray(value)) {
value.forEach(match => {
assert.strictEqual(match.path, path);
});
results.push(value.length);
} else {
assert.fail(value);
}
});
});
test('Collect batched results', function () {
const path = '/some/where';
let i = 25;
const engine = new TestSearchEngine(() => i-- && { path });
const service = new RawSearchService();
const diskSearch = new DiskSearch(false);
const progressResults = [];
return DiskSearch.collectResults(service.doSearch(engine, 10))
.then(result => {
assert.strictEqual(result.results.length, 25, 'Result');
assert.strictEqual(progressResults.length, 25, 'Progress');
}, null, match => {
assert.strictEqual(match.resource.path, path);
progressResults.push(match);
});
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册