The Emacs built-in command (garbage-collect) gives detailed information about the data structures that currently consume memory. It is propably not the most usefull information but I wanted to collect the data and plot it. I started with writing functions to access the list returned from (garbage-collect):
(defsubst get-mem-conses (mi)
(let ((data (nth 0 mi)))
(/ (* (nth 1 data) (+ (nth 2 data) (nth 3 data))) (* 1024 1024.0))))
(defsubst get-mem-symbols (mi)
(let ((data (nth 1 mi)))
(/ (* (nth 1 data) (+ (nth 2 data) (nth 3 data))) (* 1024 1024.0))))
(defsubst get-mem-misc (mi)
(let ((data (nth 2 mi)))
(/ (* (nth 1 data) (+ (nth 2 data) (nth 3 data))) (* 1024 1024.0))))
(defsubst get-mem-string-header (mi)
(let ((data (nth 3 mi)))
(/ (* (nth 1 data) (+ (nth 2 data) (nth 3 data))) (* 1024 1024.0))))
(defsubst get-mem-string-bytes (mi)
(let ((data (nth 4 mi)))
(/ (* (nth 1 data) (nth 2 data)) (* 1024 1024.0))))
(defsubst get-mem-vector-header (mi)
(let ((data (nth 5 mi)))
(/ (* (nth 1 data) (nth 2 data)) (* 1024 1024.0))))
(defsubst get-mem-vector-slots (mi)
(let ((data (nth 6 mi)))
(/ (* (nth 1 data) (+ (nth 2 data) (nth 3 data))) (* 1024 1024.0))))
(defsubst get-mem-floats (mi)
(let ((data (nth 7 mi)))
(/ (* (nth 1 data) (+ (nth 2 data) (nth 3 data))) (* 1024 1024.0))))
(defsubst get-mem-intervals (mi)
(let ((data (nth 8 mi)))
(/ (* (nth 1 data) (+ (nth 2 data) (nth 3 data))) (* 1024 1024.0))))
(defsubst get-mem-buffers (mi)
(let ((data (nth 9 mi)))
(/ (* (nth 1 data) (nth 2 data)) (* 1024 1024.0))))
Then I had need for a function that will be called periodically. This function will call (garbage-collect) and store the data in the file-system:
(defun collector (filename)
"Write memory data into file with FILENAME."
(let ((mi (garbage-collect)))
(with-temp-buffer
(insert
(format "%f %f %f %f %f %f %f %f %f %f %f\r\n"
(float-time)
(get-mem-conses mi)
(get-mem-symbols mi)
(get-mem-misc mi)
(get-mem-string-header mi)
(get-mem-string-bytes mi)
(get-mem-vector-header mi)
(get-mem-vector-slots mi)
(get-mem-floats mi)
(get-mem-intervals mi)
(get-mem-buffers mi)))
(let ((message-log-max nil))
(append-to-file (point-min) (point-max) filename)))))
Next I have need for a function that starts the collection process and one that stops it again:
(defvar collector-timer nil)
(defun start-collection (filename interval)
(interactive "FEnter filename:\nMEnter interval: ")
(setq collector-filename filename
collector-timer (run-at-time
2
(string-to-number interval)
'collector filename)))
(defun stop-collection ()
(interactive)
(when (timerp collector-timer)
(cancel-timer collector-timer)))
Finally the collected data should be plotted into a nice graph:
(defun plot-data (datafile imagefile)
(interactive "FEnter data-filename: \nFEnter image-filename:")
(let ((gnuplot (start-process "gnuplot" "*gnuplot*" "gnuplot")))
(process-send-string gnuplot "set term png\n")
(process-send-string gnuplot (format "set output \"%s\"\n" imagefile))
(process-send-string gnuplot "set grid\n")
(process-send-string gnuplot "set title \"Emacs memory consumption by category\"\n")
(process-send-string gnuplot "set xlabel \"interval\"\n")
(process-send-string gnuplot "set autoscale\n")
(process-send-string gnuplot "set ylabel \"2^{20} bytes\"\n")
(process-send-string gnuplot (format "plot \"%s\" using 2 title \"cons cells\" with lines" datafile))
(process-send-string gnuplot (format "
, \"%s\" using 3 title \"symbols\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 4 title \"\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 5 title \"string header\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 6 title \"string bytes\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 7 title \"vector header\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 8 title \"vector slots\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 9 title \"floats\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 10 title \"intervals\" with lines" datafile))
(process-send-string gnuplot (format ", \"%s\" using 11 title \"buffers\" with lines\n" datafile))))
Turns out that my emacs usage was really calm in the time when I sampled the data 🙂 In fact I have entered some kilobytes of test data into the scratch buffer with two seconds between two samples. (resulting chart lost, sorry…)
have you heard of macros??
What’s your point?
I think he’s referring to the 10 defsubsts, which could be implemented as a single function–a macro isn’t even necessary. 🙂
Thanks for sharing this! I’ll have to give it a try.
but… If I decide that I do not want to see the numbers for vector-slots and vector-headers I only have to remove two lines from (plot-data). My guess is that you have to bend a little more 😉
I have also published the complete snippet here: https://raw.githubusercontent.com/mpfeifer/this_and_that/master/memanalize.el