UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

530 lines (528 loc) 21.8 kB
import { of, throwError } from 'rxjs'; import { filter, map, take } from 'rxjs/operators'; import { CimStream } from '../data/cim-stream'; import { Http } from '../data/http'; import { Net } from '../data/net'; import { PowerShellStream } from '../data/powershell-stream'; import { Logging } from '../diagnostics/logging'; import { RpcObservablePerformanceClient } from '../rpc/performance/rpc-observable-performance-client'; import { RpcObservablePerformanceConfigClient } from '../rpc/performance/rpc-observable-performance-config-client'; import { RpcObservablePerformanceServer } from '../rpc/performance/rpc-observable-performance-server'; import { PerformanceProfileDataType } from './performance-profile-data-type'; import { PerformanceProfileDatabase } from './performance-profile-database'; /** * Performance measurement class. */ export class PerformanceProfile { static monitorName = 'PerformanceProfile'; static instance; database = null; rpc; rpcObservablePerformanceClient; rpcObservablePerformanceServer; subscription; moduleVersionMap = {}; rpcObservablePerformanceConfigClient; /** * Gets the current PerformanceProfile instance. */ static get current() { if (PerformanceProfile.instance) { return PerformanceProfile.instance; } PerformanceProfile.instance = new PerformanceProfile(); return PerformanceProfile.instance; } static get database() { return PerformanceProfile.current.database; } /** * Record Route navigation performance measurement. */ static logRouteNavigation(source, start, end, url, target, errorMessage) { const data = { source, start, end, errorMessage, type: PerformanceProfileDataType.RouteNavigation, routeNavigation: { url, target } }; PerformanceProfile.current.log(data); } /** * Record Null packet. */ static logNull(source) { const data = { source, start: 0, end: 0, errorMessage: null, type: PerformanceProfileDataType.Null }; PerformanceProfile.current.logAnyway(data); } /** * Record XHR or Fetch performance measurement for PowerShell. */ static logXhrFetchPowerShell(source, start, end, url, method, status, powershell, errorMessage) { const data = { source, start, end, errorMessage, type: PerformanceProfileDataType.XhrFetch, xhrFetch: { url, method, status, powershell } }; PerformanceProfile.current.log(data); } /** * Record XHR or Fetch performance measurement for CIM. */ static logXhrFetchCim(source, start, end, url, method, status, cim, errorMessage) { const data = { source, start, end, errorMessage, type: PerformanceProfileDataType.XhrFetch, xhrFetch: { url, method, status, cim } }; PerformanceProfile.current.log(data); } /** * Record XHR or Fetch performance measurement for Batch. */ static logXhrFetchBatch(source, start, end, url, method, status, batch, errorMessage) { const data = { source, start, end, errorMessage, type: PerformanceProfileDataType.XhrFetch, xhrFetch: { url, method, status, batch } }; PerformanceProfile.current.log(data); } /** * Record XHR or Fetch performance measurement for general. */ static logXhrFetch(source, start, end, url, method, status, errorMessage) { const data = { source, start, end, errorMessage, type: PerformanceProfileDataType.XhrFetch, xhrFetch: { url, method, status } }; PerformanceProfile.current.log(data); } /** * Record WebSocket performance measurement. */ static logWebSocketPowerShell(source, nodeName, command, context, errorMessage) { const data = { source, start: context.progressStart, end: context.progressEnd, errorMessage, type: PerformanceProfileDataType.WebSocket, webSocket: { nodeName, id: context.id, count: context.count, itemCount: context.itemCount, powershell: { command } } }; PerformanceProfile.current.log(data); } /** * Record WebSocket performance measurement. */ static logWebSocketCim(source, nodeName, cim, context, errorMessage) { const data = { source, start: context.progressStart, end: context.progressEnd, errorMessage, type: PerformanceProfileDataType.WebSocket, webSocket: { nodeName, id: context.id, count: context.count, itemCount: context.itemCount, cim } }; PerformanceProfile.current.log(data); } static powershellApi(url, body, response) { const powershell = {}; if (body && body.indexOf('\"properties\"') > 0) { powershell.command = PerformanceProfile.getBetween(body, ',\"command\":\"', '\",\"') || PerformanceProfile.getBetween(body, '\"script\":\"##', '##:'); } powershell.completed = response && response.completed; if (!powershell.completed) { powershell.completed = response && response.properties && response.properties.completed; } powershell.sessionId = response && response.sessionId; if (!powershell.sessionId) { powershell.sessionId = response && response.properties && response.properties.sessionId; if (!powershell.sessionId) { powershell.sessionId = PerformanceProfile.getBetween(url, 'features/powershellApi/pssessions/', '?'); } } return powershell; } static cimApi(url, body) { if (url.indexOf('/features/cim/query') > 0) { try { return JSON.parse(body); } catch { return null; } } const namespaceName = PerformanceProfile.getBetween(url, '/namespaces/', '/classes/'); const className = PerformanceProfile.getBetween(url, '/classes/', '/instances'); return { namespace: namespaceName, className: className }; } static batchApi(body, response) { const delimiter = '\x0d\x0a'; const batch = []; let last = body.indexOf(delimiter); let separator = body.substring(0, last + 2); const bodySegments = body.split(separator); last = response.indexOf(delimiter); separator = response.substring(0, last + 2); const responseSegments = response.split(separator); if (bodySegments.length === responseSegments.length && bodySegments.length > 1) { let batchMethod; let batchUrl; let batchBody; let batchStatus; let batchResponse; for (let i = 1; i < bodySegments.length; i++) { const bodySegment = bodySegments[i]; const responseSegment = responseSegments[i]; const bodySegmentLines = bodySegment.split(delimiter); let emptyLines = 0; for (let j = 0; j < bodySegmentLines.length; j++) { const line = bodySegmentLines[j]; if (line.length > 0) { if (emptyLines === 1 && !batchUrl) { // reached header segment. const query = line.split(' '); batchMethod = query[0]; batchUrl = query[1]; } else if (emptyLines >= 2 && !batchBody) { batchBody = line; } } else { emptyLines++; } } emptyLines = 0; const responseSegmentLines = responseSegment.split(delimiter); for (let j = 0; j < responseSegmentLines.length; j++) { const line = responseSegmentLines[j]; if (line.length > 0) { if (emptyLines === 1 && !batchStatus) { // reached header segment. const query = line.split(' '); batchStatus = Number(query[1]); } else if (emptyLines >= 2 && !batchResponse) { try { batchResponse = JSON.parse(line); } catch { batchResponse = {}; } } } else { emptyLines++; } } const batchItem = { url: batchUrl, status: batchStatus, method: batchMethod }; if (batchUrl.indexOf('/features/powershellApi/') > 0) { batchItem.powershell = PerformanceProfile.powershellApi(batchUrl, batchBody, batchResponse); } else if (batchUrl.includes('/features/cim/query')) { batchItem.cim = PerformanceProfile.cimApi(batchUrl, batchBody); } batch.push(batchItem); } } return batch; } static getBetween(source, begin, end) { let index0 = source.indexOf(begin); if (index0 < 0) { return null; } index0 += begin.length; const index1 = source.indexOf(end, index0); if (index1 < 0) { return null; } return source.substring(index0, index1 - index0); } enable(rpc) { MsftSme.setPerformanceProfile(true); this.registerRpc(rpc); } disable(rpc) { MsftSme.setPerformanceProfile(false); this.registerRpc(rpc); } checkPerformanceProfile() { if (!MsftSme.getPerformanceProfile()) { if (this.database) { this.database.close(); this.database = null; } return false; } if (!this.database) { this.database = new PerformanceProfileDatabase(); } return true; } registerRpc(rpc) { if (!this.rpc) { this.rpc = rpc; if (MsftSme.isShell()) { this.subscription = rpc.stateChanged .pipe(filter(active => active), take(1)) .subscribe(() => { this.rpcObservablePerformanceConfigClient = new RpcObservablePerformanceConfigClient(this.rpc); this.rpcObservablePerformanceServer = new RpcObservablePerformanceServer(rpc); this.rpcObservablePerformanceServer.register(request => { if (request.type === PerformanceProfileDataType.Null) { // The packet is null packet and retain only the version data. if (request.sourceVersion === '0.2.0') { // this version can support config command when on/off switch was used. this.moduleVersionMap[request.sourceName] = request.sourceVersion; } return of(null); } if (!this.checkPerformanceProfile()) { return of(null); } return this.database.write(request).pipe(map(() => null)); }); }); } else { this.subscription = rpc.stateChanged .pipe(filter(active => active), take(1)) .subscribe(() => { this.rpcObservablePerformanceClient = new RpcObservablePerformanceClient(rpc); }); } } this.updateMonitors(); } updateMonitors() { const enabled = this.checkPerformanceProfile(); if (this.rpcObservablePerformanceConfigClient) { // enable/disable the performance profile data collection to current modules. // but these must be version 0.2.0. const items = this.rpc.rpcManager.getCurrentRpcOutbound(); if (items) { for (const item of items) { if (this.moduleVersionMap[item.name]) { this.rpcObservablePerformanceConfigClient.config({ enabled }, item).subscribe(); } } } } if (enabled) { this.registerHttp(); this.registerPowerShellStream(); this.registerCimStream(); } else { Http.unregisterMonitors(PerformanceProfile.monitorName); PowerShellStream.unregisterMonitors(PerformanceProfile.monitorName); CimStream.unregisterMonitors(PerformanceProfile.monitorName); } } log(message) { if (!this.checkPerformanceProfile()) { return; } const self = MsftSme.self(); const record = { ...message, ...{ sessionId: self.Init.sessionId, timestamp: Date.now(), moduleName: self.Init.moduleName } }; if (!record.errorMessage) { delete record['errorMessage']; } if (this.rpc && this.rpc.stateActive && this.rpcObservablePerformanceClient) { // send to shell. this.rpcObservablePerformanceClient.log(record).subscribe(); } else { // write to database. this.database.write(record).subscribe(); } } logAnyway(message) { const self = MsftSme.self(); const record = { ...message, ...{ sessionId: self.Init.sessionId, timestamp: Date.now(), moduleName: self.Init.moduleName } }; if (!record.errorMessage) { delete record['errorMessage']; } if (this.rpc && this.rpc.stateActive && this.rpcObservablePerformanceClient) { // send to shell. this.rpcObservablePerformanceClient.log(record).subscribe(); } } registerHttp() { const startPropertyName = '_start_'; Http.registerMonitorSet({ name: PerformanceProfile.monitorName, preMonitor: (request) => { if (!request) { Logging.logWarning('Http', 'Http performance profile measurement error to access the AjaxRequest object.'); return of(request); } request[startPropertyName] = Date.now(); return of(request); }, successMonitor: (response) => { if (!response || !response.request || !response.request.url) { Logging.logWarning('Http', 'Http performance profile measurement error to access the AjaxResponse object.'); return of(response); } const request = response.request; const url = request.url; const method = request.method; const end = Date.now(); const start = request[startPropertyName]; if (url.indexOf('/features/powershellApi/') > 0) { const powershell = PerformanceProfile.powershellApi(url, request.body, response.response); PerformanceProfile.logXhrFetchPowerShell('Http', start, end, url, method, response.status, powershell); } else if (url.includes('/features/cim/')) { const cim = PerformanceProfile.cimApi(url, request.body); PerformanceProfile.logXhrFetchCim('Http', start, end, url, method, response.status, cim); } else if (url.indexOf('/api/batch') > 0) { const batch = PerformanceProfile.batchApi(request.body, response.response); PerformanceProfile.logXhrFetchBatch('Http', start, end, url, method, response.status, batch); } else { PerformanceProfile.logXhrFetch('Http', start, end, url, method, response.status); } return of(response); }, errorMonitor: (error) => { if (!error || !error.response || !error.request || !error.request.url) { Logging.logWarning('Http', 'Http performance profile measurement error to access the AjaxError object.'); return throwError(() => error); } const request = error.request; const url = request.url; const method = request.method; const end = Date.now(); const start = request[startPropertyName]; const message = Net.getErrorMessage(error); if (url.indexOf('features/powershellApi') > 0) { const powershell = PerformanceProfile.powershellApi(url, request.body, error.response); PerformanceProfile.logXhrFetchPowerShell('Http', start, end, url, method, error.status, powershell, message); } else if (url.includes('/features/cim/')) { const cim = PerformanceProfile.cimApi(url, request.body); PerformanceProfile.logXhrFetchCim('Http', start, end, url, method, error.status, cim, message); } else { PerformanceProfile.logXhrFetch('Http', start, end, url, method, error.status, message); } return throwError(() => error); } }); } registerPowerShellStream() { let masterId = 1; PowerShellStream.registerMonitorSet({ name: PerformanceProfile.monitorName, preMonitor: (nodeName, command, options) => { const start = Date.now(); const id = masterId++; const context = { nodeName, command, options, data: { id, start, progressStart: start, count: 0, itemCount: 0 } }; return of(context); }, successMonitor: (response, context) => { context.data.progressEnd = Date.now(); context.data.count++; context.data.itemCount += (response && response.results && response.results.length || 1); PerformanceProfile.logWebSocketPowerShell('PowerShellStream', context.nodeName, context.command.command, context.data); context.data.progressStart = Date.now(); return of(response); }, errorMonitor: (error, context) => { context.data.progressEnd = Date.now(); PerformanceProfile.logWebSocketPowerShell('PowerShellStream', context.nodeName, context.command.command, context.data, Net.getErrorMessage(error)); return throwError(() => error); } }); } registerCimStream() { let masterId = 1; CimStream.registerMonitorSet({ name: PerformanceProfile.monitorName, preMonitor: (nodeName, requestState, request, format, options) => { const start = Date.now(); const id = masterId++; const context = { nodeName, requestState, request, format, options, data: { id, start, progressStart: start, count: 0, itemCount: 0 } }; return of(context); }, successMonitor: (response, context) => { context.data.progressEnd = Date.now(); context.data.count++; context.data.itemCount += (response && response.results && response.results.length || 1); PerformanceProfile.logWebSocketCim('CimStream', context.nodeName, this.removeCimStreamDetailData(context.request), context.data); context.data.progressStart = Date.now(); return of(response); }, errorMonitor: (error, context) => { context.data.progressEnd = Date.now(); PerformanceProfile.logWebSocketCim('CimStream', context.nodeName, this.removeCimStreamDetailData(context.request), context.data, Net.getErrorMessage(error)); return throwError(() => error); } }); } removeCimStreamDetailData(request) { const skipKeys = ['data', 'keyProperties']; const requestRaw = request; const keys = Object.keys(requestRaw); const trimmedKeys = keys.filter(key => skipKeys.indexOf(key) < 0); if (keys.length !== trimmedKeys.length) { const copy = {}; for (const key of trimmedKeys) { copy[key] = requestRaw[key]; } return copy; } return request; } } //# sourceMappingURL=performance-profile.js.map