Skip to content

Commit fb5f155

Browse files
Merge pull request #4761 from NativeScript/vladimirov/fix-slow-source-maps-2
fix: device logs are parsed slowly
2 parents 4ae3fef + 578c374 commit fb5f155

13 files changed

+108
-40
lines changed

lib/common/definitions/mobile.d.ts

+20
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ declare module Mobile {
164164
* Describes methods for providing device logs to a specific consumer.
165165
*/
166166
interface IDeviceLogProvider extends NodeJS.EventEmitter {
167+
/**
168+
* Sets the path to source file from which the logs are produced,
169+
* i.e. the original file location of the file running on device.
170+
* @param {string} pathToSourceFile Path to the source file.
171+
* @returns {Promise<void>}
172+
*/
173+
setSourceFileLocation(pathToSourceFile: string): Promise<void>;
174+
167175
/**
168176
* Logs data in the specific way for the consumer.
169177
* @param {string} line String from the device logs.
@@ -258,6 +266,12 @@ declare module Mobile {
258266
* Replaces file paths in device log with their original location
259267
*/
260268
interface ILogSourceMapService {
269+
/**
270+
* Sets the sourceMapConsumer instance for specified file.
271+
* @param {string} filePath Full path to a local file containing both content and inline source map.
272+
* @return {Promise<void>}
273+
*/
274+
setSourceMapConsumerForFile(filePath: string): Promise<void>;
261275
replaceWithOriginalFileLocations(platform: string, messageData: string, loggingOptions: Mobile.IDeviceLogOptions): string
262276
}
263277

@@ -307,6 +321,12 @@ declare module Mobile {
307321
tryStartApplication(appData: IApplicationData): Promise<void>;
308322
getDebuggableApps(): Promise<Mobile.IDeviceApplicationInformation[]>;
309323
getDebuggableAppViews(appIdentifiers: string[]): Promise<IDictionary<Mobile.IDebugWebViewInfo[]>>;
324+
/**
325+
* Sets the files transferred on device.
326+
* @param {string[]} files Local paths to files transferred on device.
327+
* @returns {Promise<void>}
328+
*/
329+
setTransferredAppFiles(files: string[]): Promise<void>;
310330
}
311331

312332
/**

lib/common/mobile/android/android-application-manager.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ export class AndroidApplicationManager extends ApplicationManagerBase {
1414
private $logcatHelper: Mobile.ILogcatHelper,
1515
private $androidProcessService: Mobile.IAndroidProcessService,
1616
private $httpClient: Server.IHttpClient,
17-
private $deviceLogProvider: Mobile.IDeviceLogProvider,
17+
protected $deviceLogProvider: Mobile.IDeviceLogProvider,
1818
private $errors: IErrors,
1919
$logger: ILogger,
2020
$hooksService: IHooksService) {
21-
super($logger, $hooksService);
21+
super($logger, $hooksService, $deviceLogProvider);
2222
}
2323

2424
public async getInstalledApplications(): Promise<string[]> {

lib/common/mobile/application-manager-base.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ export abstract class ApplicationManagerBase extends EventEmitter implements Mob
77
private lastAvailableDebuggableAppViews: IDictionary<Mobile.IDebugWebViewInfo[]> = {};
88

99
constructor(protected $logger: ILogger,
10-
protected $hooksService: IHooksService) {
10+
protected $hooksService: IHooksService,
11+
protected $deviceLogProvider: Mobile.IDeviceLogProvider) {
1112
super();
1213
}
1314

15+
public async setTransferredAppFiles(files: string[]): Promise<void> {
16+
for (const file of files) {
17+
await this.$deviceLogProvider.setSourceFileLocation(file);
18+
}
19+
}
20+
1421
public async reinstallApplication(appIdentifier: string, packageFilePath: string): Promise<void> {
1522
const isApplicationInstalled = await this.isApplicationInstalled(appIdentifier);
1623

lib/common/mobile/device-log-emitter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ export class DeviceLogEmitter extends DeviceLogProviderBase {
55
constructor(protected $logFilter: Mobile.ILogFilter,
66
$logger: ILogger,
77
private $loggingLevels: Mobile.ILoggingLevels,
8-
private $logSourceMapService: Mobile.ILogSourceMapService) {
9-
super($logFilter, $logger);
8+
protected $logSourceMapService: Mobile.ILogSourceMapService) {
9+
super($logFilter, $logger, $logSourceMapService);
1010
}
1111

1212
public logData(line: string, platform: string, deviceIdentifier: string): void {

lib/common/mobile/device-log-provider-base.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@ export abstract class DeviceLogProviderBase extends EventEmitter implements Mobi
55
protected devicesLogOptions: IDictionary<Mobile.IDeviceLogOptions> = {};
66

77
constructor(protected $logFilter: Mobile.ILogFilter,
8-
protected $logger: ILogger) {
8+
protected $logger: ILogger,
9+
protected $logSourceMapService: Mobile.ILogSourceMapService) {
910
super();
1011
}
1112

13+
public async setSourceFileLocation(pathToOriginalFile: string): Promise<void> {
14+
try {
15+
await this.$logSourceMapService.setSourceMapConsumerForFile(pathToOriginalFile);
16+
} catch (err) {
17+
this.$logger.trace("Error while trying to set source map file", err);
18+
}
19+
}
20+
1221
public abstract logData(lineText: string, platform: string, deviceIdentifier: string): void;
1322

1423
public abstract setLogLevel(logLevel: string, deviceIdentifier?: string): void;

lib/common/mobile/device-log-provider.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { LoggerConfigData } from "../../constants";
55
export class DeviceLogProvider extends DeviceLogProviderBase {
66
constructor(protected $logFilter: Mobile.ILogFilter,
77
protected $logger: ILogger,
8-
private $logSourceMapService: Mobile.ILogSourceMapService) {
9-
super($logFilter, $logger);
8+
protected $logSourceMapService: Mobile.ILogSourceMapService) {
9+
super($logFilter, $logger, $logSourceMapService);
1010
}
1111

1212
public logData(lineText: string, platform: string, deviceIdentifier: string): void {

lib/common/mobile/ios/device/ios-application-manager.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export class IOSApplicationManager extends ApplicationManagerBase {
1313
private $iOSNotificationService: IiOSNotificationService,
1414
private $iosDeviceOperations: IIOSDeviceOperations,
1515
private $options: IOptions,
16-
private $deviceLogProvider: Mobile.IDeviceLogProvider) {
17-
super($logger, $hooksService);
16+
protected $deviceLogProvider: Mobile.IDeviceLogProvider) {
17+
super($logger, $hooksService, $deviceLogProvider);
1818
}
1919

2020
public async getInstalledApplications(): Promise<string[]> {

lib/common/mobile/ios/simulator/ios-simulator-application-manager.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ export class IOSSimulatorApplicationManager extends ApplicationManagerBase {
1515
private device: Mobile.IiOSDevice,
1616
private $options: IOptions,
1717
private $fs: IFileSystem,
18-
private $deviceLogProvider: Mobile.IDeviceLogProvider,
18+
protected $deviceLogProvider: Mobile.IDeviceLogProvider,
1919
$logger: ILogger,
2020
$hooksService: IHooksService) {
21-
super($logger, $hooksService);
21+
super($logger, $hooksService, $deviceLogProvider);
2222
}
2323

2424
public async getInstalledApplications(): Promise<string[]> {

lib/common/test/unit-tests/mobile/application-manager-base.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { Yok } from "../../../yok";
22
import { assert } from "chai";
3-
import { CommonLoggerStub, HooksServiceStub } from "../stubs";
3+
import { CommonLoggerStub, HooksServiceStub, DeviceLogProviderStub } from "../stubs";
44
import { ApplicationManagerBase } from "../../../mobile/application-manager-base";
55

66
let currentlyAvailableAppsForDebugging: Mobile.IDeviceApplicationInformation[];
77
let currentlyInstalledApps: string[];
88
let currentlyAvailableAppWebViewsForDebugging: IDictionary<Mobile.IDebugWebViewInfo[]>;
99

1010
class ApplicationManager extends ApplicationManagerBase {
11-
constructor($logger: ILogger, $hooksService: IHooksService) {
12-
super($logger, $hooksService);
11+
constructor($logger: ILogger, $hooksService: IHooksService, $deviceLogProvider: Mobile.IDeviceLogProvider) {
12+
super($logger, $hooksService, $deviceLogProvider);
1313
}
1414

1515
public async installApplication(packageFilePath: string): Promise<void> {
@@ -46,6 +46,7 @@ function createTestInjector(): IInjector {
4646
testInjector.register("logger", CommonLoggerStub);
4747
testInjector.register("hooksService", HooksServiceStub);
4848
testInjector.register("applicationManager", ApplicationManager);
49+
testInjector.register("deviceLogProvider", DeviceLogProviderStub);
4950
return testInjector;
5051
}
5152

lib/common/test/unit-tests/stubs.ts

+3
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ export class DeviceLogProviderStub extends EventEmitter implements Mobile.IDevic
164164
public currentDeviceProjectNames: IStringDictionary = {};
165165
public currentDeviceProjectDirs: IStringDictionary = {};
166166

167+
async setSourceFileLocation(pathToSourceFile: string): Promise<void> {
168+
}
169+
167170
logData(line: string, platform: string, deviceIdentifier: string): void {
168171
this.logger.info(line, platform, deviceIdentifier, { [LoggerConfigData.skipNewLine]: true });
169172
}

lib/services/livesync/platform-livesync-service-base.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,14 @@ export abstract class PlatformLiveSyncServiceBase {
128128
};
129129
}
130130

131-
protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceData: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
131+
private async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceData: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
132132
let transferredFiles: Mobile.ILocalToDevicePathData[] = [];
133133
const deviceLiveSyncService = this.getDeviceLiveSyncService(deviceAppData.device, projectData);
134134

135135
transferredFiles = await deviceLiveSyncService.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncDeviceData, options);
136136

137+
await deviceAppData.device.applicationManager.setTransferredAppFiles(localToDevicePaths.map(l => l.getLocalPath()));
138+
137139
this.logFilesSyncInformation(transferredFiles, "Successfully transferred %s on device %s.", this.$logger.info, deviceAppData.device.deviceInfo.identifier);
138140

139141
return transferredFiles;

lib/services/log-source-map-service.ts

+41-23
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,34 @@ interface IFileLocation {
2222
export class LogSourceMapService implements Mobile.ILogSourceMapService {
2323
private static FILE_PREFIX = "file:///";
2424
private getProjectData: Function;
25+
private cache: IDictionary<sourcemap.SourceMapConsumer> = {};
26+
2527
constructor(
2628
private $fs: IFileSystem,
2729
private $projectDataService: IProjectDataService,
2830
private $injector: IInjector,
29-
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) {
30-
this.getProjectData = _.memoize(this.$projectDataService.getProjectData.bind(this.$projectDataService));
31+
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
32+
private $logger: ILogger) {
33+
this.getProjectData = _.memoize(this.$projectDataService.getProjectData.bind(this.$projectDataService));
34+
}
35+
36+
public async setSourceMapConsumerForFile(filePath: string): Promise<void> {
37+
try {
38+
if (!this.$fs.getFsStats(filePath).isDirectory()) {
39+
const source = this.$fs.readText(filePath);
40+
const sourceMapRaw = sourceMapConverter.fromSource(source);
41+
let smc: sourcemap.SourceMapConsumer = null;
42+
if (sourceMapRaw && sourceMapRaw.sourcemap) {
43+
const sourceMap = sourceMapRaw.sourcemap;
44+
smc = new sourcemap.SourceMapConsumer(sourceMap);
45+
}
46+
47+
this.cache[filePath] = smc;
48+
}
49+
} catch (err) {
50+
this.$logger.trace(`Unable to set sourceMapConsumer for file ${filePath}. Error is: ${err}`);
51+
}
52+
3153
}
3254

3355
public replaceWithOriginalFileLocations(platform: string, messageData: string, loggingOptions: Mobile.IDeviceLogOptions): string {
@@ -46,7 +68,7 @@ export class LogSourceMapService implements Mobile.ILogSourceMapService {
4668
const originalLocation = this.getOriginalFileLocation(platform, parsedLine, projectData);
4769

4870
if (originalLocation && originalLocation.sourceFile) {
49-
const {sourceFile, line, column} = originalLocation;
71+
const { sourceFile, line, column } = originalLocation;
5072
outputData = `${outputData}${parsedLine.messagePrefix}${LogSourceMapService.FILE_PREFIX}${sourceFile}:${line}:${column}${parsedLine.messageSuffix}\n`;
5173
} else if (rawLine !== "") {
5274
outputData = `${outputData}${rawLine}\n`;
@@ -57,25 +79,21 @@ export class LogSourceMapService implements Mobile.ILogSourceMapService {
5779
}
5880

5981
private getOriginalFileLocation(platform: string, parsedLine: IParsedMessage, projectData: IProjectData): IFileLocation {
60-
const fileLoaction = path.join(this.getFilesLocation(platform, projectData), APP_FOLDER_NAME);
82+
const fileLocation = path.join(this.getFilesLocation(platform, projectData), APP_FOLDER_NAME);
6183

6284
if (parsedLine && parsedLine.filePath) {
63-
const sourceMapFile = path.join(fileLoaction, parsedLine.filePath);
64-
if (this.$fs.exists(sourceMapFile)) {
65-
const source = this.$fs.readText(sourceMapFile);
66-
const sourceMapRaw = sourceMapConverter.fromSource(source);
67-
if (sourceMapRaw && sourceMapRaw.sourcemap) {
68-
const sourceMap = sourceMapRaw.sourcemap;
69-
const smc = new sourcemap.SourceMapConsumer(sourceMap);
70-
const originalPosition = smc.originalPositionFor({ line: parsedLine.line, column: parsedLine.column });
71-
let sourceFile = originalPosition.source && originalPosition.source.replace("webpack:///", "");
72-
if (sourceFile) {
73-
if (!_.startsWith(sourceFile, NODE_MODULES_FOLDER_NAME)) {
74-
sourceFile = path.join(projectData.getAppDirectoryRelativePath(), sourceFile);
75-
}
76-
sourceFile = stringReplaceAll(sourceFile, "/", path.sep);
77-
return { sourceFile, line: originalPosition.line, column: originalPosition.column};
85+
const sourceMapFile = path.join(fileLocation, parsedLine.filePath);
86+
const smc = this.cache[sourceMapFile];
87+
if (smc) {
88+
const originalPosition = smc.originalPositionFor({ line: parsedLine.line, column: parsedLine.column });
89+
let sourceFile = originalPosition.source && originalPosition.source.replace("webpack:///", "");
90+
if (sourceFile) {
91+
if (!_.startsWith(sourceFile, NODE_MODULES_FOLDER_NAME)) {
92+
sourceFile = path.join(projectData.getAppDirectoryRelativePath(), sourceFile);
7893
}
94+
95+
sourceFile = stringReplaceAll(sourceFile, "/", path.sep);
96+
return { sourceFile, line: originalPosition.line, column: originalPosition.column };
7997
}
8098
}
8199
}
@@ -111,7 +129,7 @@ export class LogSourceMapService implements Mobile.ILogSourceMapService {
111129
// "/data/data/org.nativescript.sourceMap/files/app/"
112130
const devicePath = `${deviceProjectPath}/${APP_FOLDER_NAME}/`;
113131
// "bundle.js"
114-
filePath = path.relative(devicePath, `${"/"}${parts[0]}`);
132+
filePath = path.relative(devicePath, `${"/"}${parts[0]}`);
115133
line = parseInt(parts[1]);
116134
column = parseInt(parts[2]);
117135
messagePrefix = rawMessage.substring(0, fileIndex);
@@ -123,7 +141,7 @@ export class LogSourceMapService implements Mobile.ILogSourceMapService {
123141
}
124142
}
125143

126-
return { filePath, line, column, messagePrefix, messageSuffix};
144+
return { filePath, line, column, messagePrefix, messageSuffix };
127145
}
128146

129147
private parseIosLog(rawMessage: string): IParsedMessage {
@@ -138,10 +156,10 @@ export class LogSourceMapService implements Mobile.ILogSourceMapService {
138156
parts = fileSubstring.split(":");
139157

140158
if (parts && parts.length >= 3) {
141-
filePath = parts[0];
159+
filePath = parts[0];
142160
// "app/vendor.js"
143161
if (_.startsWith(filePath, APP_FOLDER_NAME)) {
144-
filePath = path.relative(APP_FOLDER_NAME , parts[0]);
162+
filePath = path.relative(APP_FOLDER_NAME, parts[0]);
145163
}
146164
line = parseInt(parts[1]);
147165
column = parseInt(parts[2]);

test/services/log-source-map-service.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { LogSourceMapService } from "../../lib/services/log-source-map-service";
55
import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants";
66
import { FileSystem } from "../../lib/common/file-system";
77
import { stringReplaceAll } from "../../lib/common/helpers";
8+
import { LoggerStub } from "../stubs";
89

910
function createTestInjector(): IInjector {
1011
const testInjector = new Yok();
@@ -30,6 +31,7 @@ function createTestInjector(): IInjector {
3031
});
3132
testInjector.register("fs", FileSystem);
3233
testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
34+
testInjector.register("logger", LoggerStub);
3335
testInjector.register("logSourceMapService", LogSourceMapService);
3436

3537
return testInjector;
@@ -83,9 +85,15 @@ const testCases: IDictionary<Array<{caseName: string, message: string, expected:
8385
describe("log-source-map-service", () => {
8486
describe("replaceWithOriginalFileLocations", () => {
8587
let logSourceMapService: Mobile.ILogSourceMapService;
86-
before(() => {
88+
before(async () => {
8789
const testInjector = createTestInjector();
8890
logSourceMapService = testInjector.resolve("logSourceMapService");
91+
const originalFilesLocation = path.join(__dirname, ".." , "files", "sourceMapBundle");
92+
const fs = testInjector.resolve<IFileSystem>("fs");
93+
const files = fs.enumerateFilesInDirectorySync(originalFilesLocation);
94+
for (const file of files) {
95+
await logSourceMapService.setSourceMapConsumerForFile(file);
96+
}
8997
});
9098

9199
_.forEach(testCases, ( cases, platform ) => {

0 commit comments

Comments
 (0)