project files added

This commit is contained in:
mhalfmann
2021-06-15 16:00:08 +02:00
parent e156e2f053
commit db46afa351
13928 changed files with 1569902 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
# electron-process-reporter
Utility to extract interesting process reports of an Electron application.
## Installation
```bash
$ npm install --save electron-process-reporter
```
## Usage
### onExtendedProcessMetrics
Returns an Rx.Observable that emits reports of `ExtendedProcessMetric` every `options.samplingInterval` ms.
```js
import { app } from 'electron';
import { onExtendedProcessMetrics } from 'electron-process-reporter';
onExtendedProcessMetrics(app, { samplingInterval: 1000 }) // returns a rx.Observable
.subscribe(report => console.log(report))
```
### onExcessiveCPUUsage
Will emit `ExtendedProcessMetric[]` when a process exceeds the `options.percentCPUUsageThreshold` on more than `options.samplesCount` samples.
```js
import { app } from 'electron';
import { onExcessiveCPUUsage } from 'electron-process-reporter';
onExcessiveCPUUsage(
app,
{
samplesCount: 1,
percentCPUUsageThreshold: 90,
}) // returns a rx.Observable
.subscribe(report => console.log(report))
```
### onProcessTreeMetricsForPid
Returns an Rx.Observable that emits `PidUsage[]` every `options.samplingInterval` ms.
```js
import { onProcessTreeMetricsForPid } from 'electron-process-reporter';
onProcessTreeMetricsForPid(process.pid, { samplingInterval: 1000 }) // returns a rx.Observable
.subscribe(report => console.log(report))
```
### onExcessiveCPUUsageInProcessTree
Will emit `PidUsage[]` when a process of the tree exceeds the `options.percentCPUUsageThreshold` on more than `options.samplesCount` samples.
```js
import { onExcessiveCPUUsageInProcessTree } from 'electron-process-reporter';
onExcessiveCPUUsageInProcessTree(
process.pid, // in the main process
{
samplesCount: 1,
percentCPUUsageThreshold: 90,
}) // returns a rx.Observable
.subscribe(report => console.log(report))
```
@@ -0,0 +1,3 @@
import * as memoize from 'memoizee';
declare const _default: ((urlString: string) => string) & memoize.Memoized<(urlString: string) => string>;
export default _default;
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const memoize = require("memoizee");
const url_1 = require("url");
const extractURLDomain = (urlString) => {
const url = url_1.parse(urlString);
if (url.protocol == 'https:' || url.protocol == 'http:') {
return url.hostname;
}
return '';
};
// memoize it for performance
exports.default = memoize(extractURLDomain, { max: 100 });
//# sourceMappingURL=extractURLDomain.js.map
@@ -0,0 +1 @@
{"version":3,"file":"extractURLDomain.js","sourceRoot":"","sources":["../src/extractURLDomain.ts"],"names":[],"mappings":";;AAAA,oCAAoC;AACpC,6BAA4B;AAE5B,MAAM,gBAAgB,GAAG,CAAC,SAAiB,EAAU,EAAE;IACrD,MAAM,GAAG,GAAG,WAAK,CAAC,SAAS,CAAC,CAAC;IAE7B,IAAI,GAAG,CAAC,QAAQ,IAAI,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,OAAO,EAAE;QACvD,OAAO,GAAG,CAAC,QAAQ,CAAC;KACrB;IAED,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,6BAA6B;AAC7B,kBAAe,OAAO,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC"}
+107
View File
@@ -0,0 +1,107 @@
import { Observable } from 'rxjs';
export interface onProcessMetricsOptions {
/** in ms */
samplingInterval?: number;
}
export interface PidUsage {
cpu: number;
memory: number;
pid: number;
ppid: number;
ctime: number;
elapsed: number;
timestamp: number;
}
export declare const getAppUsage: (pid: number) => Promise<PidUsage[]>;
/**
* Returns an Observable that emits Electron.ProcessMetric[] on a regular interval.
*
* For a given `app` and a given `samplingInterval`, the returned observable is shared
* for performance reasons.
*
* options.samplingInterval = 1000 (1s) by default
* @param app
* @param options
*/
export declare const onProcessMetrics: (app: Electron.App, options: onProcessMetricsOptions) => Observable<Electron.ProcessMetric[]>;
/**
* Returns an Rx.Observable that emits `PidUsage[]` every `options.samplingInterval` ms.
*
* For a given `pid` and a given `samplingInterval`, the returned observable is shared
* for performance reasons.
* `pid` is the root of the process tree.
*
* @param pid
* @param {object} options
* @param {number} options.samplingInterval - 1000 (1s) by default
*
* @example
* - pid: main process
* - rendererPid1: renderer process
* - rendererPid2: renderer process
*/
export declare const onProcessTreeMetricsForPid: (pid: number, options: onProcessMetricsOptions) => Observable<PidUsage[]>;
export interface ExtendedProcessMetric extends Electron.ProcessMetric {
webContents?: {
type: string;
id: number;
pid: number;
URL: string;
URLDomain: string;
}[];
}
/**
* Returns an Rx.Observable that emits reports of `ExtendedProcessMetric`
* every `options.samplingInterval` ms.
*
* Default `options.samplingInterval` = 1000ms
*
* Compared to `onProcessMetrics` it adds data on the `webContents` associated
* to the given process.
*
* @param app the electron app instance
* @param options
*/
export declare const onExtendedProcessMetrics: (app: Electron.App, options?: onProcessMetricsOptions) => Observable<ExtendedProcessMetric[]>;
export interface onExcessiveCPUUsageOptions extends onProcessMetricsOptions {
/**Number of samples to consider */
samplesCount?: number;
/**CPU usage percent minimum to consider a sample exceeds CPU usage */
percentCPUUsageThreshold?: number;
}
/**
* Will emit an array of `PidUsage` when a process of the tree exceeds the
* `options.percentCPUUsageThreshold` on more than `options.samplesCount`
* samples.
* It monitors the whole tree of pids, starting from `childPid`.
* The reason behind this is that the `process.pid` of the main process is at the same
* level as all renderers.
* So we fetch their common ancestor, which is the `ppid` of the main process.
* The parent pid of `childPid` is not part of the end result
* (that way, we monitor the same processes as `getAppMetrics`).
*
* In opposite to onExcessiveCPUUsage, onExcessiveCPUUsageInProcessTree does not use
* Electron's internal measurement but rather use `pidusage`, a cross-platform
* process cpu % and memory usage of a PID. It is known to have lower pressure on CPU.
* Also, as this leverage `pidusage`, the measures on Windows can be considered
* as not accurate.
*
* Default `options.samplesCount` = 10
* Default `options.percentCPUUsageThreshold` = 80
*
* @param pid - the pid of the main process
* @param options
*/
export declare const onExcessiveCPUUsageInProcessTree: (pid: number, options: onExcessiveCPUUsageOptions) => Observable<PidUsage[]>;
/**
* Will emit an array `ExtendedProcessMetric` when a process exceeds the
* `options.percentCPUUsageThreshold` on more than `options.samplesCount`
* samples.
*
* Default `options.samplesCount` = 10
* Default `options.percentCPUUsageThreshold` = 80
*
* @param app the electron app instance
* @param options
*/
export declare const onExcessiveCPUUsage: (app: Electron.App, options: onExcessiveCPUUsageOptions) => Observable<ExtendedProcessMetric[]>;
+150
View File
@@ -0,0 +1,150 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const electron_1 = require("electron");
const memoize = require("memoizee");
// @ts-ignore: no declaration file
const pidtree = require("pidtree");
// @ts-ignore: no declaration file
const pidusage = require("pidusage");
const rxjs_1 = require("rxjs");
const extractURLDomain_1 = require("./extractURLDomain");
exports.getAppUsage = (pid) => {
return pidtree(pid, { root: true })
.then(pidusage)
.then((usages) => Object.values(usages).filter(Boolean));
};
let getSharedProcessMetricsPollerByPid = (pid, samplingInterval) => rxjs_1.Observable.timer(0, samplingInterval)
.map(() => rxjs_1.Observable.fromPromise(exports.getAppUsage(pid)))
.mergeAll()
.share();
getSharedProcessMetricsPollerByPid = memoize(getSharedProcessMetricsPollerByPid);
let getSharedProcessMetricsPollerByApp = (app, samplingInterval) => rxjs_1.Observable.timer(0, samplingInterval)
.map(() => app.getAppMetrics())
.share();
getSharedProcessMetricsPollerByApp = memoize(getSharedProcessMetricsPollerByApp);
/**
* Returns an Observable that emits Electron.ProcessMetric[] on a regular interval.
*
* For a given `app` and a given `samplingInterval`, the returned observable is shared
* for performance reasons.
*
* options.samplingInterval = 1000 (1s) by default
* @param app
* @param options
*/
exports.onProcessMetrics = (app, options) => {
options = Object.assign({ samplingInterval: 1000 }, options);
return getSharedProcessMetricsPollerByApp(app, options.samplingInterval);
};
/**
* Returns an Rx.Observable that emits `PidUsage[]` every `options.samplingInterval` ms.
*
* For a given `pid` and a given `samplingInterval`, the returned observable is shared
* for performance reasons.
* `pid` is the root of the process tree.
*
* @param pid
* @param {object} options
* @param {number} options.samplingInterval - 1000 (1s) by default
*
* @example
* - pid: main process
* - rendererPid1: renderer process
* - rendererPid2: renderer process
*/
exports.onProcessTreeMetricsForPid = (pid, options) => {
options = Object.assign({ samplingInterval: 1000 }, options);
return getSharedProcessMetricsPollerByPid(pid, options.samplingInterval);
};
const getExtendedAppMetrics = (appMetrics) => {
const allWebContents = electron_1.webContents.getAllWebContents();
const webContentsInfo = allWebContents.map((wc) => ({
type: wc.getType(),
id: wc.id,
pid: wc.getOSProcessId(),
URL: wc.getURL(),
URLDomain: extractURLDomain_1.default(wc.getURL()),
}));
return appMetrics.map(proc => {
const report = proc;
const wc = webContentsInfo.find(wc => wc.pid === proc.pid);
if (!wc)
return report;
report.webContents = [wc];
return report;
});
};
/**
* Returns an Rx.Observable that emits reports of `ExtendedProcessMetric`
* every `options.samplingInterval` ms.
*
* Default `options.samplingInterval` = 1000ms
*
* Compared to `onProcessMetrics` it adds data on the `webContents` associated
* to the given process.
*
* @param app the electron app instance
* @param options
*/
exports.onExtendedProcessMetrics = (app, options = {}) => exports.onProcessMetrics(app, options).map(getExtendedAppMetrics);
/**
* Will emit an array of `PidUsage` when a process of the tree exceeds the
* `options.percentCPUUsageThreshold` on more than `options.samplesCount`
* samples.
* It monitors the whole tree of pids, starting from `childPid`.
* The reason behind this is that the `process.pid` of the main process is at the same
* level as all renderers.
* So we fetch their common ancestor, which is the `ppid` of the main process.
* The parent pid of `childPid` is not part of the end result
* (that way, we monitor the same processes as `getAppMetrics`).
*
* In opposite to onExcessiveCPUUsage, onExcessiveCPUUsageInProcessTree does not use
* Electron's internal measurement but rather use `pidusage`, a cross-platform
* process cpu % and memory usage of a PID. It is known to have lower pressure on CPU.
* Also, as this leverage `pidusage`, the measures on Windows can be considered
* as not accurate.
*
* Default `options.samplesCount` = 10
* Default `options.percentCPUUsageThreshold` = 80
*
* @param pid - the pid of the main process
* @param options
*/
exports.onExcessiveCPUUsageInProcessTree = (pid, options) => {
options = Object.assign({ samplesCount: 10, percentCPUUsageThreshold: 80 }, options);
return exports.onProcessTreeMetricsForPid(pid, options)
.map(appUsage => rxjs_1.Observable.from(appUsage))
.mergeAll()
.groupBy(appUsage => appUsage.pid)
.map(g => g.bufferCount(options.samplesCount))
.mergeAll()
.filter(processMetricsSamples => {
const excessiveSamplesCount = processMetricsSamples.filter(p => p.cpu >= options.percentCPUUsageThreshold).length;
return excessiveSamplesCount === processMetricsSamples.length;
});
};
/**
* Will emit an array `ExtendedProcessMetric` when a process exceeds the
* `options.percentCPUUsageThreshold` on more than `options.samplesCount`
* samples.
*
* Default `options.samplesCount` = 10
* Default `options.percentCPUUsageThreshold` = 80
*
* @param app the electron app instance
* @param options
*/
exports.onExcessiveCPUUsage = (app, options) => {
options = Object.assign({ samplesCount: 10, percentCPUUsageThreshold: 80 }, options);
return exports.onExtendedProcessMetrics(app, options)
.map(report => rxjs_1.Observable.from(report))
.mergeAll()
.groupBy(processMetric => processMetric.pid)
.map(g => g.bufferCount(options.samplesCount))
.mergeAll()
.filter(processMetricsSamples => {
const excessiveSamplesCount = processMetricsSamples.filter(p => p.cpu.percentCPUUsage >= options.percentCPUUsageThreshold).length;
return excessiveSamplesCount == processMetricsSamples.length;
});
};
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
+78
View File
@@ -0,0 +1,78 @@
{
"_args": [
[
"electron-process-reporter@1.4.0",
"C:\\Daten\\Git\\Tumortisch"
]
],
"_from": "electron-process-reporter@1.4.0",
"_id": "electron-process-reporter@1.4.0",
"_inBundle": false,
"_integrity": "sha512-zI5IV7zplV2eHznNbeYtMwATVqXO+g9i2ChpurvGTvExA/rpi7BdSrp38bsTz5tjJLLTz9LmzeSt85uIZ051Qw==",
"_location": "/electron-process-reporter",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "electron-process-reporter@1.4.0",
"name": "electron-process-reporter",
"escapedName": "electron-process-reporter",
"rawSpec": "1.4.0",
"saveSpec": null,
"fetchSpec": "1.4.0"
},
"_requiredBy": [
"/electron-process-manager"
],
"_resolved": "https://registry.npmjs.org/electron-process-reporter/-/electron-process-reporter-1.4.0.tgz",
"_spec": "1.4.0",
"_where": "C:\\Daten\\Git\\Tumortisch",
"dependencies": {
"memoizee": "^0.4.14",
"pidtree": "^0.3.0",
"pidusage": "2.0.16",
"rxjs": "^5.5.6"
},
"description": "Utility to extract interesting process reports of an Electron application.",
"devDependencies": {
"@types/chai": "^4.1.6",
"@types/memoizee": "^0.4.2",
"@types/mocha": "^5.2.5",
"@types/node": "^8.0.34",
"chai": "^4.2.0",
"electron": "^2.0.0",
"eslint": "^5.7.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "^3.1.0",
"eslint-config-typescript": "^1.1.0",
"eslint-formatter-pretty": "^1.3.0",
"eslint-import-resolver-typescript": "^1.1.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-typescript": "^0.12.0",
"mocha": "^5.2.0",
"prettier": "^1.14.3",
"rimraf": "^2.6.2",
"spectron": "^4.0.0",
"ts-node": "^7.0.1",
"typescript": "^3.1.3",
"typescript-eslint-parser": "^20.0.0",
"xvfb-maybe": "^0.2.1"
},
"files": [
"lib/"
],
"license": "MIT",
"main": "lib/index.js",
"name": "electron-process-reporter",
"scripts": {
"build": "rimraf lib && tsc -p .",
"format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'",
"lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
"prepublish": "npm run build",
"test": "xvfb-maybe mocha --exit --opts tests/mocha.opts tests/*-test.ts && npm run build"
},
"typings": "lib/index.d.ts",
"version": "1.4.0"
}