main
1;;; usage-metrics.el --- Dump Emacs usage data for usage-metrics collector -*- lexical-binding: t; -*-
2
3;;; Commentary:
4;; Periodically dumps loaded features and command frequency data to a JSON file
5;; that the usage-collect script reads.
6
7;;; Code:
8
9(defvar usage-metrics-output-file
10 (expand-file-name "usage-metrics/emacs-dump.json"
11 (or (getenv "XDG_DATA_HOME")
12 (expand-file-name ".local/share" (getenv "HOME"))))
13 "Path to write the emacs usage dump.")
14
15(defvar usage-metrics-timer nil
16 "Timer for periodic usage metrics dump.")
17
18(defun usage-metrics--get-loaded-features ()
19 "Return list of loaded feature names as strings."
20 (mapcar #'symbol-name features))
21
22(defun usage-metrics--get-command-frequency ()
23 "Return alist of (command . count) from keyfreq data if available."
24 (when (and (fboundp 'keyfreq-table)
25 (boundp 'keyfreq-table)
26 (hash-table-p keyfreq-table))
27 (let ((result '()))
28 (maphash
29 (lambda (key count)
30 (when (symbolp (cdr key))
31 (let* ((cmd-name (symbol-name (cdr key)))
32 (existing (assoc cmd-name result)))
33 (if existing
34 (setcdr existing (+ (cdr existing) count))
35 (push (cons cmd-name count) result)))))
36 keyfreq-table)
37 ;; Sort by frequency descending
38 (sort result (lambda (a b) (> (cdr a) (cdr b)))))))
39
40(defun usage-metrics--get-package-list ()
41 "Return list of installed package names."
42 (cond
43 ;; straight.el
44 ((fboundp 'straight--installed-packages)
45 (mapcar #'symbol-name (hash-table-keys straight--recipe-cache)))
46 ;; elpaca
47 ((fboundp 'elpaca--queued)
48 (mapcar (lambda (e) (symbol-name (elpaca<-package e)))
49 (elpaca--queued)))
50 ;; package.el
51 ((bound-and-true-p package-activated-list)
52 (mapcar #'symbol-name package-activated-list))
53 (t '())))
54
55(defun usage-metrics-dump ()
56 "Dump current Emacs usage data to JSON file."
57 (interactive)
58 (let* ((features-list (usage-metrics--get-loaded-features))
59 (cmd-freq (usage-metrics--get-command-frequency))
60 (packages (usage-metrics--get-package-list))
61 (data `(("timestamp" . ,(format-time-string "%Y-%m-%dT%H:%M:%S%z"))
62 ("loaded_features" . ,(vconcat features-list))
63 ("installed_packages" . ,(vconcat packages))
64 ("command_frequency" . ,(let ((ht (make-hash-table :test 'equal)))
65 (dolist (pair (seq-take cmd-freq 200))
66 (puthash (car pair) (cdr pair) ht))
67 ht))))
68 (dir (file-name-directory usage-metrics-output-file)))
69 (unless (file-directory-p dir)
70 (make-directory dir t))
71 (with-temp-file usage-metrics-output-file
72 (insert (json-serialize data)))
73 (message "usage-metrics: dumped to %s" usage-metrics-output-file)))
74
75(defun usage-metrics-start ()
76 "Start periodic usage metrics dumping (every 30 minutes)."
77 (interactive)
78 (usage-metrics-stop)
79 (setq usage-metrics-timer
80 (run-with-timer 300 1800 #'usage-metrics-dump)) ; first dump after 5min, then every 30min
81 (add-hook 'kill-emacs-hook #'usage-metrics-dump)
82 (message "usage-metrics: started"))
83
84(defun usage-metrics-stop ()
85 "Stop periodic usage metrics dumping."
86 (interactive)
87 (when usage-metrics-timer
88 (cancel-timer usage-metrics-timer)
89 (setq usage-metrics-timer nil))
90 (remove-hook 'kill-emacs-hook #'usage-metrics-dump)
91 (message "usage-metrics: stopped"))
92
93;; Auto-start when loaded
94(usage-metrics-start)
95
96(provide 'usage-metrics)
97;;; usage-metrics.el ends here