mirror of
https://github.com/overleaf/overleaf.git
synced 2025-01-27 00:44:18 +00:00
add memory check and periodic gc
This commit is contained in:
parent
175e3efd5f
commit
46ec20ef9c
2 changed files with 87 additions and 0 deletions
86
libraries/metrics/memory.coffee
Normal file
86
libraries/metrics/memory.coffee
Normal file
|
@ -0,0 +1,86 @@
|
|||
# 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 = 1 # 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 * oneMegaByte # how much 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) ->
|
||||
# use setTimeout add some randomisation of the start time
|
||||
setTimeout () ->
|
||||
interval = setInterval () ->
|
||||
MemoryMonitor.Check(logger)
|
||||
, oneMinute
|
||||
, Math.floor Math.random()*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)
|
||||
|
||||
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)
|
|
@ -40,6 +40,7 @@ module.exports =
|
|||
http: require "./http"
|
||||
open_sockets: require "./open_sockets"
|
||||
event_loop: require "./event_loop"
|
||||
memory: require "./memory"
|
||||
|
||||
close: () ->
|
||||
for func in destructors
|
||||
|
|
Loading…
Reference in a new issue