overleaf/libraries/metrics/memory.js
Eric Mc Sween e0d91eaa26 Merge pull request #7906 from overleaf/em-downgrade-logs
Downgrade all INFO logs to DEBUG

GitOrigin-RevId: 05ed582ef0721fcada059f0ad158565f50feca27
2022-05-17 08:05:26 +00:00

113 lines
3.8 KiB
JavaScript

/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// record memory usage each minute and run a periodic gc(), keeping cpu
// usage within allowable range of 1ms per minute. Also, dynamically
// adjust the period between gc()'s to reach a target of the gc saving
// 4 megabytes each time.
let MemoryMonitor
const oneMinute = 60 * 1000
const oneMegaByte = 1024 * 1024
let CpuTimeBucket = 100 // current cpu time allowance in milliseconds
const CpuTimeBucketMax = 100 // maximum amount of cpu time allowed in bucket
const CpuTimeBucketRate = 10 // add this many milliseconds per minute
let gcInterval = 1 // how many minutes between gc (parameter is dynamically adjusted)
let countSinceLastGc = 0 // how many minutes since last gc
const MemoryChunkSize = 4 // how many megabytes we need to free to consider gc worth doing
const readyToGc = function () {
// update allowed cpu time
CpuTimeBucket = CpuTimeBucket + CpuTimeBucketRate
CpuTimeBucket =
CpuTimeBucket < CpuTimeBucketMax ? CpuTimeBucket : CpuTimeBucketMax
// update counts since last gc
countSinceLastGc = countSinceLastGc + 1
// check there is enough time since last gc and we have enough cpu
return countSinceLastGc > gcInterval && CpuTimeBucket > 0
}
const executeAndTime = function (fn) {
// time the execution of fn() and subtract from cpu allowance
const t0 = process.hrtime()
fn()
const dt = process.hrtime(t0)
const timeTaken = (dt[0] + dt[1] * 1e-9) * 1e3 // in milliseconds
CpuTimeBucket -= Math.ceil(timeTaken)
return timeTaken
}
const inMegaBytes = function (obj) {
// convert process.memoryUsage hash {rss,heapTotal,heapFreed} into megabytes
const result = {}
for (const k in obj) {
const v = obj[k]
result[k] = (v / oneMegaByte).toFixed(2)
}
return result
}
const updateMemoryStats = function (oldMem, newMem) {
countSinceLastGc = 0
const delta = {}
for (const k in newMem) {
delta[k] = (newMem[k] - oldMem[k]).toFixed(2)
}
// take the max of all memory measures
const savedMemory = Math.max(-delta.rss, -delta.heapTotal, -delta.heapUsed)
delta.megabytesFreed = savedMemory
// did it do any good?
if (savedMemory < MemoryChunkSize) {
gcInterval = gcInterval + 1 // no, so wait longer next time
} else {
gcInterval = Math.max(gcInterval - 1, 1) // yes, wait less time
}
return delta
}
module.exports = MemoryMonitor = {
monitor(logger) {
const interval = setInterval(() => MemoryMonitor.Check(logger), oneMinute)
const Metrics = require('./index')
return Metrics.registerDestructor(() => clearInterval(interval))
},
Check(logger) {
let mem
const Metrics = require('./index')
const memBeforeGc = (mem = inMegaBytes(process.memoryUsage()))
Metrics.gauge('memory.rss', mem.rss)
Metrics.gauge('memory.heaptotal', mem.heapTotal)
Metrics.gauge('memory.heapused', mem.heapUsed)
Metrics.gauge('memory.gc-interval', gcInterval)
// Metrics.gauge("memory.cpu-time-bucket", CpuTimeBucket)
logger.debug(mem, 'process.memoryUsage()')
if (global.gc != null && readyToGc()) {
const gcTime = executeAndTime(global.gc).toFixed(2)
const memAfterGc = inMegaBytes(process.memoryUsage())
const deltaMem = updateMemoryStats(memBeforeGc, memAfterGc)
logger.debug(
{
gcTime,
memBeforeGc,
memAfterGc,
deltaMem,
gcInterval,
CpuTimeBucket,
},
'global.gc() forced'
)
// Metrics.timing("memory.gc-time", gcTime)
Metrics.gauge('memory.gc-rss-freed', -deltaMem.rss)
Metrics.gauge('memory.gc-heaptotal-freed', -deltaMem.heapTotal)
return Metrics.gauge('memory.gc-heapused-freed', -deltaMem.heapUsed)
}
},
}