File size: 2,431 Bytes
3b70664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
(defpackage #:nekod.selfmod
  (:use #:cl #:nekod)
  (:export
   #:generation-loop
   #:mutate-spec
   #:evolve
   #:fitness
   #:*generation*
   #:*population*))

(in-package #:nekod.selfmod)

;; Generation loop: rewrite container definitions based on telemetry

(defvar *generation* 0)
(defvar *population* nil) ; list of container-specs

(defun mutate-spec (spec mutation-rate)
  "Randomly mutate a container spec for evolutionary optimization."
  (when (< (random 1.0) mutation-rate)
    (let ((new-spec (copy-structure spec)))
      ;; Mutate restart policy
      (setf (container-spec-restart-policy new-spec)
            (nth (random 3) '(:always :on-failure :never)))
      new-spec)))

(defun fitness (spec telemetry)
  "Score a container spec based on runtime telemetry.

   High uptime + low cpu + low memory = high fitness."
  (declare (ignore spec))
  (let ((uptime (getf telemetry :uptime 0.0))
        (cpu    (getf telemetry :cpu    0.5))
        (memory (getf telemetry :memory 0.5)))
    (- (* 0.6 uptime)
       (* 0.2 cpu)
       (* 0.2 memory))))

(defun evolve (population telemetry &optional (mutation-rate 0.1))
  "One generation of evolutionary optimization.

   Returns new population: survivors + mutants."
  (incf *generation*)
  (let* ((scored   (mapcar (lambda (s) (cons s (fitness s telemetry))) population))
         (sorted   (sort scored #'> :key #'cdr))
         (survivors (mapcar #'car (subseq sorted 0 (ceiling (length sorted) 2))))
         (mutants   (remove nil (mapcar (lambda (s) (mutate-spec s mutation-rate)) survivors))))
    (append survivors mutants)))

(defun generation-loop (population telemetry-fn
                        &key (generations 10) (mutation-rate 0.1) (verbose t))
  "Run N generations of evolutionary container optimization.



   POPULATION: initial list of container-specs

   TELEMETRY-FN: called each generation, returns plist (:uptime f :cpu f :memory f)

   Returns the final optimized population."
  (loop repeat generations
        for pop = population then (evolve pop telemetry mutation-rate)
        for telemetry = (funcall telemetry-fn)
        do (when verbose
             (format t "Gen ~a: ~a containers, best fitness ~,3f~%"
                     *generation*
                     (length pop)
                     (if pop (fitness (first pop) telemetry) 0.0)))
        finally (return pop)))