| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @implements {SDK.SDKModelObserver} |
| */ |
| export default class IsolateManager extends Common.Object { |
| constructor() { |
| super(); |
| console.assert(!SDK.isolateManager, 'Use SDK.isolateManager singleton.'); |
| /** @type {!Map<string, !Isolate>} */ |
| this._isolates = new Map(); |
| // _isolateIdByModel contains null while the isolateId is being retrieved. |
| /** @type {!Map<!SDK.RuntimeModel, ?string>} */ |
| this._isolateIdByModel = new Map(); |
| /** @type {!Set<!Observer>} */ |
| this._observers = new Set(); |
| SDK.targetManager.observeModels(SDK.RuntimeModel, this); |
| this._pollId = 0; |
| } |
| |
| /** |
| * @param {!Observer} observer |
| */ |
| observeIsolates(observer) { |
| if (this._observers.has(observer)) { |
| throw new Error('Observer can only be registered once'); |
| } |
| if (!this._observers.size) { |
| this._poll(); |
| } |
| this._observers.add(observer); |
| for (const isolate of this._isolates.values()) { |
| observer.isolateAdded(isolate); |
| } |
| } |
| |
| /** |
| * @param {!Observer} observer |
| */ |
| unobserveIsolates(observer) { |
| this._observers.delete(observer); |
| if (!this._observers.size) { |
| ++this._pollId; |
| } // Stops the current polling loop. |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.RuntimeModel} model |
| */ |
| modelAdded(model) { |
| this._modelAdded(model); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} model |
| */ |
| async _modelAdded(model) { |
| this._isolateIdByModel.set(model, null); |
| const isolateId = await model.isolateId(); |
| if (!this._isolateIdByModel.has(model)) { |
| // The model has been removed during await. |
| return; |
| } |
| if (!isolateId) { |
| this._isolateIdByModel.delete(model); |
| return; |
| } |
| this._isolateIdByModel.set(model, isolateId); |
| let isolate = this._isolates.get(isolateId); |
| if (!isolate) { |
| isolate = new Isolate(isolateId); |
| this._isolates.set(isolateId, isolate); |
| } |
| isolate._models.add(model); |
| if (isolate._models.size === 1) { |
| for (const observer of this._observers) { |
| observer.isolateAdded(isolate); |
| } |
| } else { |
| for (const observer of this._observers) { |
| observer.isolateChanged(isolate); |
| } |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.RuntimeModel} model |
| */ |
| modelRemoved(model) { |
| const isolateId = this._isolateIdByModel.get(model); |
| this._isolateIdByModel.delete(model); |
| if (!isolateId) { |
| return; |
| } |
| const isolate = this._isolates.get(isolateId); |
| isolate._models.delete(model); |
| if (isolate._models.size) { |
| for (const observer of this._observers) { |
| observer.isolateChanged(isolate); |
| } |
| return; |
| } |
| for (const observer of this._observers) { |
| observer.isolateRemoved(isolate); |
| } |
| this._isolates.delete(isolateId); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} model |
| * @return {?Isolate} |
| */ |
| isolateByModel(model) { |
| return this._isolates.get(this._isolateIdByModel.get(model) || '') || null; |
| } |
| |
| /** |
| * @return {!IteratorIterable<!Isolate>} |
| */ |
| isolates() { |
| return this._isolates.values(); |
| } |
| |
| async _poll() { |
| const pollId = this._pollId; |
| while (pollId === this._pollId) { |
| await Promise.all(Array.from(this.isolates(), isolate => isolate._update())); |
| await new Promise(r => setTimeout(r, PollIntervalMs)); |
| } |
| } |
| } |
| |
| /** |
| * @interface |
| */ |
| export class Observer { |
| /** |
| * @param {!Isolate} isolate |
| */ |
| isolateAdded(isolate) { |
| } |
| |
| /** |
| * @param {!Isolate} isolate |
| */ |
| isolateRemoved(isolate) { |
| } |
| /** |
| * @param {!Isolate} isolate |
| */ |
| isolateChanged(isolate) { |
| } |
| } |
| |
| /** @enum {symbol} */ |
| export const Events = { |
| MemoryChanged: Symbol('MemoryChanged') |
| }; |
| |
| export const MemoryTrendWindowMs = 120e3; |
| const PollIntervalMs = 2e3; |
| |
| export class Isolate { |
| /** |
| * @param {string} id |
| */ |
| constructor(id) { |
| this._id = id; |
| /** @type {!Set<!SDK.RuntimeModel>} */ |
| this._models = new Set(); |
| this._usedHeapSize = 0; |
| const count = MemoryTrendWindowMs / PollIntervalMs; |
| this._memoryTrend = new MemoryTrend(count); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| id() { |
| return this._id; |
| } |
| |
| /** |
| * @return {!Set<!SDK.RuntimeModel>} |
| */ |
| models() { |
| return this._models; |
| } |
| |
| /** |
| * @return {?SDK.RuntimeModel} |
| */ |
| runtimeModel() { |
| return this._models.values().next().value || null; |
| } |
| |
| /** |
| * @return {?SDK.HeapProfilerModel} |
| */ |
| heapProfilerModel() { |
| const runtimeModel = this.runtimeModel(); |
| return runtimeModel && runtimeModel.heapProfilerModel(); |
| } |
| |
| async _update() { |
| const model = this.runtimeModel(); |
| const usage = model && await model.heapUsage(); |
| if (!usage) { |
| return; |
| } |
| this._usedHeapSize = usage.usedSize; |
| this._memoryTrend.add(this._usedHeapSize); |
| SDK.isolateManager.dispatchEventToListeners(Events.MemoryChanged, this); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| samplesCount() { |
| return this._memoryTrend.count(); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| usedHeapSize() { |
| return this._usedHeapSize; |
| } |
| |
| /** |
| * @return {number} bytes per millisecond |
| */ |
| usedHeapSizeGrowRate() { |
| return this._memoryTrend.fitSlope(); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class MemoryTrend { |
| /** |
| * @param {number} maxCount |
| */ |
| constructor(maxCount) { |
| this._maxCount = maxCount | 0; |
| this.reset(); |
| } |
| |
| reset() { |
| this._base = Date.now(); |
| this._index = 0; |
| /** @type {!Array<number>} */ |
| this._x = []; |
| /** @type {!Array<number>} */ |
| this._y = []; |
| this._sx = 0; |
| this._sy = 0; |
| this._sxx = 0; |
| this._sxy = 0; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| count() { |
| return this._x.length; |
| } |
| |
| /** |
| * @param {number} heapSize |
| * @param {number=} timestamp |
| */ |
| add(heapSize, timestamp) { |
| const x = typeof timestamp === 'number' ? timestamp : Date.now() - this._base; |
| const y = heapSize; |
| if (this._x.length === this._maxCount) { |
| // Turns into a cyclic buffer once it reaches the |_maxCount|. |
| const x0 = this._x[this._index]; |
| const y0 = this._y[this._index]; |
| this._sx -= x0; |
| this._sy -= y0; |
| this._sxx -= x0 * x0; |
| this._sxy -= x0 * y0; |
| } |
| this._sx += x; |
| this._sy += y; |
| this._sxx += x * x; |
| this._sxy += x * y; |
| this._x[this._index] = x; |
| this._y[this._index] = y; |
| this._index = (this._index + 1) % this._maxCount; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| fitSlope() { |
| // We use the linear regression model to find the slope. |
| const n = this.count(); |
| return n < 2 ? 0 : (this._sxy - this._sx * this._sy / n) / (this._sxx - this._sx * this._sx / n); |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.SDK = self.SDK || {}; |
| |
| /* Legacy exported object */ |
| SDK = SDK || {}; |
| |
| /** @constructor */ |
| SDK.IsolateManager = IsolateManager; |
| |
| /** @interface */ |
| SDK.IsolateManager.Observer = Observer; |
| |
| /** @enum {symbol} */ |
| SDK.IsolateManager.Events = Events; |
| |
| SDK.IsolateManager.MemoryTrendWindowMs = MemoryTrendWindowMs; |
| |
| /** @constructor */ |
| SDK.IsolateManager.Isolate = Isolate; |
| |
| /** @constructor */ |
| SDK.IsolateManager.MemoryTrend = MemoryTrend; |
| |
| SDK.isolateManager = new IsolateManager(); |