d75a9133a629e0fe46a2d24b9f0c8fe8baa52311
[rrq/hourglass.git] / control-logic.lsp
1 # Apply timing control to this host
2 # This is run as a cron job to either "close" or "open" the blocking of
3 # the host via a configured control implementation.
4
5 # File control.dat defines the limits, and control mechanism.
6 # File activity-$date.dat is the local activity.
7
8 (define (die)
9   (write-line 2 (join (map string args)))
10   (exit 1))
11
12 (constant
13  'NOW (date-value)
14  'ACTDIR SITE:listener.activity.dir
15  'CONTROL.DAT SITE:control.dat
16  'EXTRA.DAT SITE:control.extra.dat
17  'USAGE.DAT SITE:usage.dat
18  'USAGE.TMP SITE:usage.tmp
19  )
20
21 # Set current time variables in local timezone
22 (map set '(YEAR MONTH DATE HOUR MINUTE SECOND DOY DOW)
23      (date-list (+ NOW (* 60 (now 0 -2)))))
24 ;(println (list YEAR MONTH DATE HOUR MINUTE SECOND DOY DOW))
25
26 (setf
27  DAY (list YEAR MONTH DATE)
28  HM (list HOUR MINUTE)
29  TOTAL '()
30  )
31
32 # Load CONTROL.DAT
33 # ( (control "file") (gap minutes) ( weekday start limit stop ) ... )
34 (setf CONTROL (read-expr (or (read-file CONTROL.DAT)
35                              (die "** Missing " CONTROL.DAT " ** Exiting."))
36                          ))
37 (map set '(dow MODE START LIMIT END)
38      (or (assoc DOW CONTROL)
39          (assoc 'policy CONTROL)
40          (list DOW (6 30) 120 (20 0)))
41      )
42
43 (setf
44  GAP (or (lookup 'gap CONTROL) 15)
45  CLIP (or (lookup 'clip CONTROL) 1000)
46  )
47
48 # Load the configured control mechanism
49 (if (lookup 'control CONTROL) (load $it)
50   (die "** Unknown control mechanism. Exiting!!"))
51 (unless control
52     (die "** Control function (control cmd reason) not defined. Exiting!!"))
53
54 ;; Apply control command with reason, then exit
55 (define (do-control x r) (control x r) (exit 0))
56
57 ;; Utility: Combine an (hour minutes) pair into total minutes
58 (define (minutes x) (+ (* (x 0) 60) (x 1)))
59
60 ;; Utility: Combine hours and minutes into total seconds
61 (define (seconds H M) (+ (* 3600 H) (* 60 M)))
62
63 # Apply EXTRA.DAT. This is a pair of hours and minutes to force open,
64 # relative to the modification time of the file.
65 (setf OVERRIDE
66       (when (regex "([0-9]+) ([0-9]+)" (or (read-file EXTRA.DAT) "") 0)
67         (<= NOW (+ (file-info EXTRA.DAT 6) (seconds (int $1) (int $2))))))
68
69 ;;==== Utilities for activity data
70 # Activity is lines of timestamps. Collect TOTAL as list of unique
71 # time values (H M) within the start-end time span.
72
73 (define (log-name-fmt t)
74   (format "%d%02d%02d-.*\\.dat" (0 3 (date-list t))))
75
76 (define (log-lines f)
77   (find-all "([0-9]+( \\S+)?).*" (read-file (format "%s/%s" ACTDIR f)) $1 0))
78
79 # Collect all timestamps of the UTC date of the given time stamp
80 (define (logs t)
81   (flat (map log-lines (directory ACTDIR (log-name-fmt t)))))
82
83 # Translate timestamp into its local time (hour minute), if it's
84 # within the applicable day, null otherwise.
85 (define (period-minute x)
86   (when x
87     (letn ((d (date-list (+ (int x 0 10) (* 60 (now 0 -2)))))
88            (tm (3 2 d))
89            (on (if (regex "^[0-9]+ ([0-9]+)$" x 0) (> (int $1 0 10) CLIP) 1))
90            )
91       (and on (= (0 3 d) DAY) tm)))) ; (>= tm START) (< tm END) tm))))
92 ;; ======
93
94 # Collect all mentioned minutes from the activity logs
95 (setf TOTAL
96       (unique
97        (clean null?
98               (map period-minute
99                    (sort (extend (logs (- NOW 86400)) (logs NOW)))))))
100
101 # Add all mentioned minutes, and fill in any time periods of less than
102 # the configured GAP minutes between them.
103 (setf SUM 0)
104 (when TOTAL
105   (setf  LAST (minutes (pop TOTAL) SUM 1))
106   (dolist (x TOTAL)
107     (letn ((M (minutes x)) (V (- M LAST)))
108       (inc SUM (if (< V GAP) V 1))
109       (setf LAST M)))
110   )
111
112 # Rework SUM into (h m) format
113 (setf SUM (letn ((h (/ SUM 60)) (m (- SUM (* 60 h)))) (list h m)))
114
115 # Write out current usage with atomic update (jic)
116 (write-file USAGE.TMP (string SUM))
117 (rename-file USAGE.TMP USAGE.DAT)
118
119 # Apply current policy setting
120 ; ** Note that do-control exits
121 (when OVERRIDE (do-control "open" "override"))
122 (case MODE
123   (closed (do-control "close" "closed"))
124   (opened (do-control "open" "open"))
125   (timed ;; Close host outside start-end times
126    (when (< HM START) (do-control "close" "early"))
127    (when (>= HM END) (do-control "close" "late"))
128    (when (> SUM LIMIT) (do-control "close" "usage"))
129    (do-control "open" "usage")
130    )
131   (true (die "Unknown control mode " MODE))
132   )