提交 7b61ee4f 编写于 作者: R Rob Lourens

Text search - add telemetry for searches with invalid encoding

上级 c70a347e
......@@ -107,7 +107,6 @@ export interface ITextQueryProps<U extends UriComponents> extends ICommonQueryPr
contentPattern?: IPatternInfo;
previewOptions?: ITextSearchPreviewOptions;
fileEncoding?: string;
maxFileSize?: number;
usePCRE2?: boolean;
}
......
......@@ -76,7 +76,6 @@ export class QueryBuilder {
type: QueryType.Text,
contentPattern,
previewOptions: options && options.previewOptions,
fileEncoding: options && options.fileEncoding,
maxFileSize: options && options.maxFileSize,
usePCRE2: searchConfig.search.usePCRE2
};
......
......@@ -87,15 +87,7 @@ export class SearchService implements IRawSearchService {
config.maxFileSize = MAX_FILE_SIZE;
const engine = new TextSearchEngineAdapter(config);
return new Promise<ISerializedSearchSuccess>((c, e) => {
engine.search(token, progressCallback, progressCallback, (error, stats) => {
if (error) {
e(error);
} else {
c(stats);
}
});
});
return engine.search(token, progressCallback, progressCallback);
}
doFileSearch(EngineClass: { new(config: IRawSearch): ISearchEngine<IRawFileMatch>; }, config: IRawSearch, progressCallback: IProgressCallback, token?: CancellationToken, batchSize?: number): TPromise<ISerializedSearchSuccess> {
......
......@@ -118,8 +118,8 @@ export class RipgrepTextSearchEngine {
export function rgErrorMsgForDisplay(msg: string): Maybe<string> {
const firstLine = msg.split('\n')[0].trim();
if (startsWith(firstLine, 'Error parsing regex')) {
return firstLine;
if (startsWith(firstLine, 'regex parse error')) {
return 'Regex parse error';
}
let match = firstLine.match(/grep config error: unknown encoding: (.*)/);
......@@ -127,21 +127,11 @@ export function rgErrorMsgForDisplay(msg: string): Maybe<string> {
return `Unknown encoding: ${match[1]}`;
}
if (startsWith(firstLine, 'error parsing glob')) {
if (startsWith(firstLine, 'error parsing glob') || startsWith(firstLine, 'the literal')) {
// Uppercase first letter
return firstLine.charAt(0).toUpperCase() + firstLine.substr(1);
}
if (firstLine === `Literal '\\n' not allowed.`) {
// I won't localize this because none of the Ripgrep error messages are localized
return `Literal '\\n' currently not supported`;
}
if (startsWith(firstLine, 'Literal ')) {
// Other unsupported chars
return firstLine;
}
return undefined;
}
......
......@@ -236,6 +236,17 @@ export class SearchService extends Disposable implements ISearchService {
this.sendTelemetry(query, endToEndTime, complete);
});
return completes;
}, errs => {
const endToEndTime = e2eSW.elapsed();
this.logService.trace(`SearchService#search: ${endToEndTime}ms`);
errs = errs
.filter(e => !!e);
errs.forEach(e => {
this.sendTelemetry(query, endToEndTime, null, e);
});
throw errs[0];
});
}
......@@ -252,14 +263,14 @@ export class SearchService extends Disposable implements ISearchService {
return queries;
}
private sendTelemetry(query: ISearchQuery, endToEndTime: number, complete: ISearchComplete): void {
private sendTelemetry(query: ISearchQuery, endToEndTime: number, complete?: ISearchComplete, err?: Error): void {
const fileSchemeOnly = query.folderQueries.every(fq => fq.folder.scheme === 'file');
const otherSchemeOnly = query.folderQueries.every(fq => fq.folder.scheme !== 'file');
const scheme = fileSchemeOnly ? 'file' :
otherSchemeOnly ? 'other' :
'mixed';
if (query.type === QueryType.File && complete.stats) {
if (query.type === QueryType.File && complete && complete.stats) {
const fileSearchStats = complete.stats as IFileSearchStats;
if (fileSearchStats.fromCache) {
const cacheStats: ICachedSearchStats = fileSearchStats.detailStats as ICachedSearchStats;
......@@ -328,6 +339,32 @@ export class SearchService extends Disposable implements ISearchService {
scheme
});
}
} else if (query.type === QueryType.Text) {
let errorType: string;
if (err) {
errorType = err.message.indexOf('Regex parse error') >= 0 ? 'regex' :
err.message.indexOf('Unknown encoding') >= 0 ? 'encoding' :
err.message.indexOf('Error parsing glob') >= 0 ? 'glob' :
err.message.indexOf('The literal') >= 0 ? 'literal' :
'other';
}
/* __GDPR__
"textSearchComplete" : {
"reason" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"workspaceFolderCount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"endToEndTime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"scheme" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"error" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
this.telemetryService.publicLog('textSearchComplete', {
reason: query._reason,
workspaceFolderCount: query.folderQueries.length,
endToEndTime: endToEndTime,
scheme,
error: errorType
});
}
}
......
......@@ -15,17 +15,15 @@ export class TextSearchEngineAdapter {
constructor(private query: ITextQuery) {
}
// TODO@Rob - make promise-based once the old search is gone, and I don't need them to have matching interfaces anymore
search(token: CancellationToken, onResult: (matches: ISerializedFileMatch[]) => void, onMessage: (message: IProgress) => void, done: (error: Error, complete: ISerializedSearchSuccess) => void): void {
search(token: CancellationToken, onResult: (matches: ISerializedFileMatch[]) => void, onMessage: (message: IProgress) => void): Promise<ISerializedSearchSuccess> {
if (!this.query.folderQueries.length && !this.query.extraFileResources.length) {
done(null, {
return Promise.resolve(<ISerializedSearchSuccess>{
type: 'success',
limitHit: false,
stats: <ITextSearchStats>{
type: 'searchProcess'
}
});
return;
}
const pretendOutputChannel = {
......@@ -34,13 +32,17 @@ export class TextSearchEngineAdapter {
}
};
const textSearchManager = new TextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), extfs);
textSearchManager
.search(
matches => {
onResult(matches.map(fileMatchToSerialized));
},
token)
.then(() => done(null, { limitHit: false, stats: null, type: 'success' }));
return new Promise((resolve, reject) => {
return textSearchManager
.search(
matches => {
onResult(matches.map(fileMatchToSerialized));
},
token)
.then(
() => resolve({ limitHit: false, stats: null, type: 'success' }),
reject);
});
}
}
......
......@@ -128,7 +128,7 @@ export class TextSearchManager {
useIgnoreFiles: !fq.disregardIgnoreFiles,
useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles,
followSymlinks: !fq.ignoreSymlinks,
encoding: this.query.fileEncoding,
encoding: fq.fileEncoding,
maxFileSize: this.query.maxFileSize,
maxResults: this.query.maxResults,
previewOptions: this.query.previewOptions
......
......@@ -65,29 +65,21 @@ function doLegacySearchTest(config: IRawSearch, expectedResultCount: number | Fu
}
function doRipgrepSearchTest(query: ITextQuery, expectedResultCount: number | Function): TPromise<void> {
return new TPromise<void>((resolve, reject) => {
let engine = new TextSearchEngineAdapter(query);
let c = 0;
engine.search(new CancellationTokenSource().token, (results) => {
if (results) {
c += results.reduce((acc, cur) => acc + cur.numMatches, 0);
}
}, () => { }, (error) => {
try {
assert.ok(!error);
if (typeof expectedResultCount === 'function') {
assert(expectedResultCount(c));
} else {
assert.equal(c, expectedResultCount, `rg ${c} !== ${expectedResultCount}`);
}
} catch (e) {
reject(e);
let engine = new TextSearchEngineAdapter(query);
let c = 0;
return engine.search(new CancellationTokenSource().token, (results) => {
if (results) {
c += results.reduce((acc, cur) => acc + cur.numMatches, 0);
}
}, () => { }).then(
() => {
if (typeof expectedResultCount === 'function') {
assert(expectedResultCount(c));
} else {
assert.equal(c, expectedResultCount, `rg ${c} !== ${expectedResultCount}`);
}
resolve(undefined);
});
});
}
function doSearchTest(query: ITextQuery, expectedResultCount: number) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册