@awesome-ecs/workers
Version:
The Workers package for Awesome ECS. Provides basic implementation for the Worker communication using ECS.
214 lines (213 loc) • 6.67 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region src/utils/worker-performance-tracker.ts
/**
* Tracks performance metrics for worker operations, allowing for aggregation and analysis of execution times across different worker tasks.
* Metrics are sampled at regular intervals and can be retrieved in summary form for overall or per-worker insights.
*
* @injectable
*/
var WorkerPerformanceTracker = class WorkerPerformanceTracker {
static MAX_SNAPSHOTS = 120;
static SAMPLE_INTERVAL_MS = 250;
_snapshots = [];
_pendingMetrics = /* @__PURE__ */ new Map();
_perWorkerPending = /* @__PURE__ */ new Map();
_aggregate = /* @__PURE__ */ new Map();
_perWorkerAggregate = /* @__PURE__ */ new Map();
_pendingMaxQueueSize = 0;
_pendingStalledCount = 0;
_pendingWaitTimes = [];
_lastSampleTime = 0;
_enabled = false;
_aggregateDirty = true;
_cachedSummaries = [];
_perWorkerDirty = true;
_cachedPerWorker = /* @__PURE__ */ new Map();
get snapshots() {
return this._snapshots;
}
get enabled() {
return this._enabled;
}
set enabled(value) {
this._enabled = value;
}
get queueHistory() {
return this._snapshots.map((s) => s.queueSize);
}
get stalledHistory() {
return this._snapshots.map((s) => s.stalledCount);
}
get waitTimeHistory() {
return this._snapshots.map((s) => s.avgWaitTimeMs);
}
get eventTypeHistories() {
const allNames = /* @__PURE__ */ new Set();
for (const snap of this._snapshots) for (const m of snap.metrics) allNames.add(m.name);
const result = /* @__PURE__ */ new Map();
for (const name of allNames) result.set(name, this._snapshots.map((snap) => {
const m = snap.metrics.find((e) => e.name === name);
return m && m.count > 0 ? m.totalMs / m.count : 0;
}));
return result;
}
get summaries() {
if (!this._aggregateDirty) return this._cachedSummaries;
const result = [];
for (const [name, data] of this._aggregate) result.push({
name,
count: data.count,
totalMs: data.totalMs,
avgMs: data.count > 0 ? data.totalMs / data.count : 0,
minMs: data.minMs,
maxMs: data.maxMs,
lastMs: data.lastMs
});
result.sort((a, b) => b.totalMs - a.totalMs);
this._cachedSummaries = result;
this._aggregateDirty = false;
return result;
}
get perWorkerSummaries() {
if (!this._perWorkerDirty) return this._cachedPerWorker;
const result = /* @__PURE__ */ new Map();
for (const [uid, aggMap] of this._perWorkerAggregate) {
const entries = [];
for (const [name, data] of aggMap) entries.push({
name,
count: data.count,
totalMs: data.totalMs,
avgMs: data.count > 0 ? data.totalMs / data.count : 0,
minMs: data.minMs,
maxMs: data.maxMs,
lastMs: data.lastMs
});
entries.sort((a, b) => b.totalMs - a.totalMs);
result.set(uid, entries);
}
this._cachedPerWorker = result;
this._perWorkerDirty = false;
return result;
}
recordQueueSize(size) {
if (!this._enabled) return;
this._pendingMaxQueueSize = Math.max(this._pendingMaxQueueSize, size);
}
recordStalledCount(count) {
if (!this._enabled) return;
this._pendingStalledCount = Math.max(this._pendingStalledCount, count);
}
recordWaitTimes(times) {
if (!this._enabled) return;
for (const t of times) this._pendingWaitTimes.push(t);
}
addMetric(workerUid, entry) {
if (!this._enabled) return;
const ms = entry.msPassed ?? 0;
const name = entry.name;
const existing = this._pendingMetrics.get(name);
if (existing) {
existing.totalMs += ms;
existing.count += 1;
} else this._pendingMetrics.set(name, {
name,
totalMs: ms,
count: 1
});
let workerMap = this._perWorkerPending.get(workerUid);
if (!workerMap) {
workerMap = /* @__PURE__ */ new Map();
this._perWorkerPending.set(workerUid, workerMap);
}
const workerExisting = workerMap.get(name);
if (workerExisting) {
workerExisting.totalMs += ms;
workerExisting.count += 1;
} else workerMap.set(name, {
name,
totalMs: ms,
count: 1
});
}
addMetrics(workerUid, entries) {
for (const entry of entries) this.addMetric(workerUid, entry);
}
sample(now) {
if (!this._enabled) return;
const timestamp = now ?? performance.now();
if (this._lastSampleTime !== 0 && timestamp - this._lastSampleTime < WorkerPerformanceTracker.SAMPLE_INTERVAL_MS) return;
const metrics = Array.from(this._pendingMetrics.values());
const avgWaitTimeMs = this._pendingWaitTimes.length > 0 ? this._pendingWaitTimes.reduce((s, t) => s + t, 0) / this._pendingWaitTimes.length : 0;
const snapshot = {
timestamp,
queueSize: this._pendingMaxQueueSize,
stalledCount: this._pendingStalledCount,
avgWaitTimeMs,
metrics
};
this._snapshots.push(snapshot);
if (this._snapshots.length > WorkerPerformanceTracker.MAX_SNAPSHOTS) this._snapshots.shift();
this.mergeIntoAggregate(metrics);
this.mergePerWorkerAggregate();
this._pendingMetrics.clear();
this._perWorkerPending.clear();
this._pendingMaxQueueSize = 0;
this._pendingStalledCount = 0;
this._pendingWaitTimes = [];
this._lastSampleTime = timestamp;
}
clear() {
this._snapshots.length = 0;
this._pendingMetrics.clear();
this._perWorkerPending.clear();
this._aggregate.clear();
this._perWorkerAggregate.clear();
this._pendingMaxQueueSize = 0;
this._pendingStalledCount = 0;
this._pendingWaitTimes = [];
this._lastSampleTime = 0;
this._aggregateDirty = true;
this._perWorkerDirty = true;
this._cachedSummaries = [];
this._cachedPerWorker = /* @__PURE__ */ new Map();
}
mergeIntoAggregate(metrics) {
for (const m of metrics) this.mergeEntry(this._aggregate, m.name, m);
this._aggregateDirty = true;
}
mergePerWorkerAggregate() {
for (const [uid, workerMap] of this._perWorkerPending) {
const aggMap = this.getOrCreateWorkerAgg(uid);
for (const [name, m] of workerMap) this.mergeEntry(aggMap, name, m);
}
this._perWorkerDirty = true;
}
getOrCreateWorkerAgg(uid) {
let aggMap = this._perWorkerAggregate.get(uid);
if (!aggMap) {
aggMap = /* @__PURE__ */ new Map();
this._perWorkerAggregate.set(uid, aggMap);
}
return aggMap;
}
mergeEntry(aggMap, name, m) {
const avg = m.count > 0 ? m.totalMs / m.count : 0;
const existing = aggMap.get(name);
if (existing) {
existing.count += m.count;
existing.totalMs += m.totalMs;
existing.minMs = Math.min(avg, existing.minMs);
existing.maxMs = Math.max(avg, existing.maxMs);
existing.lastMs = avg;
} else aggMap.set(name, {
count: m.count,
totalMs: m.totalMs,
minMs: avg,
maxMs: avg,
lastMs: avg
});
}
};
//#endregion
exports.WorkerPerformanceTracker = WorkerPerformanceTracker;
//# sourceMappingURL=index.cjs.map