| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485 |
- # 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)
|