mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-27 19:02:46 +00:00
85 lines
3.1 KiB
CoffeeScript
85 lines
3.1 KiB
CoffeeScript
# 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.
|
|
|
|
oneMinute = 60 * 1000
|
|
oneMegaByte = 1024 * 1024
|
|
|
|
CpuTimeBucket = 100 # current cpu time allowance in milliseconds
|
|
CpuTimeBucketMax = 100 # maximum amount of cpu time allowed in bucket
|
|
CpuTimeBucketRate = 10 # add this many milliseconds per minute
|
|
|
|
gcInterval = 1 # how many minutes between gc (parameter is dynamically adjusted)
|
|
countSinceLastGc = 0 # how many minutes since last gc
|
|
MemoryChunkSize = 4 # how many megabytes we need to free to consider gc worth doing
|
|
|
|
readyToGc = () ->
|
|
# update allowed cpu time
|
|
CpuTimeBucket = CpuTimeBucket + CpuTimeBucketRate
|
|
CpuTimeBucket = if CpuTimeBucket < CpuTimeBucketMax then CpuTimeBucket else 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)
|
|
|
|
executeAndTime = (fn) ->
|
|
# time the execution of fn() and subtract from cpu allowance
|
|
t0 = process.hrtime()
|
|
fn()
|
|
dt = process.hrtime(t0)
|
|
timeTaken = (dt[0] + dt[1]*1e-9) * 1e3 # in milliseconds
|
|
CpuTimeBucket -= Math.ceil timeTaken
|
|
return timeTaken
|
|
|
|
inMegaBytes = (obj) ->
|
|
# convert process.memoryUsage hash {rss,heapTotal,heapFreed} into megabytes
|
|
result = {}
|
|
for k, v of obj
|
|
result[k] = (v / oneMegaByte).toFixed(2)
|
|
return result
|
|
|
|
updateMemoryStats = (oldMem, newMem) ->
|
|
countSinceLastGc = 0
|
|
delta = {}
|
|
for k of newMem
|
|
delta[k] = (newMem[k] - oldMem[k]).toFixed(2)
|
|
# take the max of all memory measures
|
|
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) ->
|
|
interval = setInterval () ->
|
|
MemoryMonitor.Check(logger)
|
|
, oneMinute
|
|
Metrics = require "./metrics"
|
|
Metrics.registerDestructor () ->
|
|
clearInterval(interval)
|
|
|
|
Check: (logger) ->
|
|
Metrics = require "./metrics"
|
|
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.log mem, "process.memoryUsage()"
|
|
|
|
if global.gc? && readyToGc()
|
|
gcTime = (executeAndTime global.gc).toFixed(2)
|
|
memAfterGc = inMegaBytes process.memoryUsage()
|
|
deltaMem = updateMemoryStats(memBeforeGc, memAfterGc)
|
|
logger.log {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)
|
|
Metrics.gauge("memory.gc-heapused-freed", -deltaMem.heapUsed)
|