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