branch: elpa-admin
commit 9e437910e84189618be1708a8e2794ed4b3e6ef7
Author: Stefan Monnier <monn...@iro.umontreal.ca>
Commit: Stefan Monnier <monn...@iro.umontreal.ca>

    elpa-admin.el: First steps to keep track of access stats
    
    * elpa-admin.el (elpaa--wsl-time-re, elpaa--wsl-line-re): New constants.
    (elpaa--wsl-read, elpaa--wsl-one-file, elpaa--wsl-collect): New functions.
    (elpaa--wsl-stats-file, elpaa--wsl-directory): New vars.
---
 elpa-admin.el | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 114 insertions(+), 2 deletions(-)

diff --git a/elpa-admin.el b/elpa-admin.el
index 71d9b667ac..fb2adb88ba 100644
--- a/elpa-admin.el
+++ b/elpa-admin.el
@@ -1980,6 +1980,120 @@ arbitrary code."
          (default-directory (file-name-directory (expand-file-name ac-file))))
     (elpaa--html-make-index (cdr ac))))
 
+;;; Statistics from the web server log
+
+(defconst elpaa--wsl-time-re
+  (rx (group (repeat 2 digit))          ;Day
+      "/" (group (repeat 3 alpha))      ;Month
+      "/" (group (repeat 4 digit))      ;Year
+      ":" (group                        ;Time
+           (repeat 2 digit) ":" (repeat 2 digit) ":" (repeat 2 digit)
+           " " (or "+" "-") (repeat 4 digit))))
+
+(defconst elpaa--wsl-line-re
+  (rx bol (1+ (or xdigit "." ":"))      ; IP of client
+      " - - "
+      "[" (group (+ (not "]"))) "]"                 ; Date/time
+      " \"" (or (seq (1+ (or alpha "_"))                ; Method
+                     " " (group (1+ (not (any blank)))) ; Path
+                     " " "HTTP/" (1+ (or alnum ".")))   ; Protocol
+                "-")
+      "\""
+      " " (1+ digit)                    ; Status code
+      " " (1+ digit)                    ; Size
+      " \"" (1+ (or (not (any "\"")) "\\\"")) "\" " ; Referrer
+      "\"" (1+ (or (not (any "\"")) "\\\"")) "\""
+      eol))
+
+(defun elpaa--wsl-read (logfile fn)
+  (with-temp-buffer
+    (insert-file-contents logfile)
+    (goto-char (point-min))
+    (while (not (eobp))
+      (if (not (looking-at elpaa--wsl-line-re))
+          (message "Unrecognized log line: %s"
+                   (buffer-substring (point) (line-end-position)))
+        (let* ((timestr (match-string 1))
+               (file (match-string 2))
+               (timestr
+                (if (string-match "/\\([^/]*\\)/\\([^/:]*\\):" timestr)
+                    (replace-match " \\1 \\2 " t nil timestr)
+                  (message "Unrecognized timestamp: %s" timestr)
+                  timestr))
+               (time (encode-time (parse-time-string timestr))))
+          (when file
+            (let ((pkg (if (string-match
+                            (rx bos "/"
+                                (or "packages" "devel" "nongnu" "nongnu-devel")
+                                "/"
+                                (group (+? any))
+                                (\?
+                                 "-" (or
+                                      (seq
+                                       (+ (or digit "."))
+                                       (* (or "pre" "beta" "alpha" "snapshot")
+                                          (+ (or digit "."))))
+                                      "readme"))
+                                "."
+                                (or "tar" "txt" "el" "html"))
+                            file)
+                           (match-string 1 file))))
+              (funcall fn time pkg file)))))
+      (forward-line 1))))
+
+(defun elpaa--wsl-one-file (logfile stats)
+  (elpaa--wsl-read
+   logfile
+   ;; Keep a counter of accesses indexed by package and week.
+   (lambda (time pkg _file)
+     (let* ((secs (time-convert time 'integer))
+            (week (/ secs 3600 24 7)))
+       (cl-incf (alist-get week (gethash pkg stats) 0))))))
+
+(defvar elpaa--wsl-stats-file "wsl-stats.eld")
+
+(defvar elpaa--wsl-directory "/var/log/apache2/")
+
+(defun elpaa--wsl-collect ()
+  (let* ((stats (elpaa--form-from-file-contents elpaa--wsl-stats-file))
+         (seen (nth 1 stats))
+         (table (nth 2 stats))
+         (changed nil))
+    (cl-assert (eq :web-server-log-stats (nth 0 stats)))
+    (unless table (setq table (make-hash-table :test 'equal)))
+    ;; Only consider the compressed files, because we don't want to process
+    ;; files that may still be modified.
+    (dolist (logfile (directory-files elpaa--wsl-directory t "\\.[lgx]z\\'"))
+      (let ((attrs (file-attributes logfile)))
+        (cond
+         ((string-match "error_log" logfile) nil) ;Ignore the error log files.
+         ((member attrs seen) nil)                ;Already processed.
+         (t
+          (push attrs seen)
+          (setq changed t)
+          (elpaa--wsl-one-file logfile table)))))
+    (when changed
+      (with-temp-buffer
+        (funcall (if (fboundp 'pp-28) #'pp-28 #'pp)
+                 `(:web-server-log-stats ,seen ,table)
+                 (current-buffer))
+        (princ "\n" (current-buffer))
+        (write-region nil nil elpaa--wsl-stats-file)))))
+
+;; (defun elpaa--wsl-foo ()
+;;   (let ((diff (time-convert (time-subtract curtime time) 'integer))
+;;         (diff-weeks (/ diff 3600 24 7))
+;;         (timelog (/ (logb (1+ diff-weeks)) 2))
+;;         (vec (gethash pkg stats)))
+;;     (unless vec
+;;       (setf (gethash pkg stats) (setq vec (make-vector 4 0))))
+;;     (if (> timelog (length vec))
+;;         (message "Entry too old: %s" timestr)
+;;       (cl-incf (aref vec timelog)))))
+;;       stats)))
+
+;;; Maintain worktrees in the `packages' subdirectory
+
 (defun elpaa--pull (dirname)
   (let ((default-directory (elpaa--dirname dirname)))
     (with-temp-buffer
@@ -2031,8 +2145,6 @@ arbitrary code."
                      " " "\n")
                  (buffer-string))))))
 
-;;; Maintain worktrees in the `packages' subdirectory
-
 (defun elpaa--sync-emacs-repo ()
   "Sync Emacs repository, if applicable.
 Return non-nil if there's an \"emacs\" repository present."

Reply via email to