Skip to content

Commit 459516a

Browse files
committed
Publish failure report RSS feeds.
Also, clean up failure report to hide a bunch of noise by default.
1 parent 88cba3e commit 459516a

File tree

7 files changed

+235
-39
lines changed

7 files changed

+235
-39
lines changed

dist-cache.lisp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ their name does not match the system file name."
346346
directory component, pathname-name, or pathname-type."
347347
(substitute #\_ #\/ string))
348348

349+
(defun decode-string-from-filesystem (string)
350+
(substitute #\/ #\_ string))
351+
349352
(defun winfail-file (winfail source system-file system)
350353
(let ((name (format nil "~A_~A_~A_~A"
351354
winfail
@@ -386,6 +389,8 @@ are loadable for SOURCE and return a list of lists. Each list has the
386389
structure \(SYSTEM-FILE-NAME SYSTEM-NAME &REST DEPENDENCIES). "
387390
(ensure-system-file-index)
388391
(setf source (source-designator source))
392+
(when (fresh-cache-p source)
393+
(clear-fasl-cache))
389394
(let ((winners '())
390395
(timing-file (timing-file source))
391396
(start-time (get-universal-time)))

failure-report.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ span.source-location {
2626
color: #999;
2727
}
2828

29+
span.boring {
30+
display: none;
31+
}
32+
2933
a.source-link:hover {
3034
text-decoration: underline;
3135
color: black;

html-failure-report.lisp

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ the string is returned unchanged."
9090
(defun content-type (file)
9191
(cond ((equalp (pathname-type file) "css")
9292
"text/css")
93+
((equalp (pathname-type file) "rss")
94+
"application/rss+xml")
9395
(t
9496
"text/html")))
9597

@@ -119,6 +121,12 @@ the string is returned unchanged."
119121
(defgeneric system-name (object))
120122
(defgeneric system-file-name (object))
121123
(defgeneric failure-report-url (object))
124+
(defgeneric full-failure-report-url (object)
125+
(:method (object)
126+
(format nil "http://~A/~A~A"
127+
*failtail-bucket*
128+
(report-prefix)
129+
(failure-report-url object))))
122130
(defgeneric failure-report-html-file (base object))
123131

124132
(defgeneric stylesheet-path (object))
@@ -186,7 +194,7 @@ the string is returned unchanged."
186194
(defclass failing-source ()
187195
((failure-data
188196
:initarg :failure-data
189-
:reader failure-data)
197+
:accessor failure-data)
190198
(source
191199
:initarg :source
192200
:reader source)
@@ -223,7 +231,7 @@ the string is returned unchanged."
223231
(defclass failure-report ()
224232
((failure-data
225233
:initarg :failure-data
226-
:reader failure-data)
234+
:accessor failure-data)
227235
(stylesheet-path
228236
:initform "failure-report.css"
229237
:reader stylesheet-path)))
@@ -239,23 +247,70 @@ the string is returned unchanged."
239247
(defmethod name ((object failure-report))
240248
"Failure report")
241249

250+
(defun parse-failure-file-name (failure-file)
251+
"Parse FAILURE-FILE's namestring into a FAILING-SYSTEM object; if no
252+
source is found that matches the filename, return nil."
253+
;; Syntax is:
254+
;; fail_<project>_<system-file-name>_<failing-system-escaped-name>.txt
255+
(ppcre:register-groups-bind (source-name system-file-name failing-system)
256+
("^fail_(.*?)_(.*?)_(.*?)\\.txt"
257+
(file-namestring failure-file))
258+
;; It's possible that the logfile belongs to a source that is no
259+
;; longer part of Quicklisp (due to renaming, removal, or whatever
260+
;; reason), so don't try to make a failing-source in that case.
261+
(let ((source (find-source source-name)))
262+
(when source
263+
(make-instance 'failing-system
264+
:source (find-source source-name)
265+
:failure-log-file failure-file
266+
:system-file-name system-file-name
267+
:system-name (decode-string-from-filesystem
268+
failing-system))))))
269+
270+
(defun failing-source-log-files ()
271+
(let* ((base (translate-logical-pathname "quicklisp-controller:dist;build-cache;"))
272+
(fail-wild (merge-pathnames "**/fail_*_*_*.txt" base)))
273+
(directory fail-wild)))
274+
275+
(defun failing-systems ()
276+
(remove nil
277+
(mapcar #'parse-failure-file-name
278+
(failing-source-log-files))))
279+
280+
(defun failure-log-failure-report ()
281+
"Scan the failure log files of all projects to produce a failure report."
282+
(let ((systems (make-hash-table :test 'equal)))
283+
(flet ((fsource (source)
284+
(or (gethash (name source) systems)
285+
(setf (gethash (name source) systems)
286+
(make-instance 'failing-source
287+
:failure-data nil
288+
:source source)))))
289+
(let ((table (make-hash-table :test 'eq))
290+
(systems (failing-systems))
291+
(report (make-instance 'failure-report
292+
:failure-data '())))
293+
(dolist (system systems)
294+
(let ((key (fsource (source system))))
295+
(push system (gethash key table))))
296+
(maphash (lambda (failing-source failing-systems)
297+
(setf (failure-data failing-source) failing-systems)
298+
(push failing-source (failure-data report)))
299+
table)
300+
report))))
301+
242302
(defmethod failure-data ((object (eql t)))
243-
(let ((sources
244-
(mapcan (lambda (source)
245-
(let ((failing-source
246-
(find-failing-source source)))
247-
(when failing-source
248-
(list failing-source))))
249-
(all-of-type t))))
250-
(make-instance 'failure-report
251-
:failure-data (sort sources
252-
#'string<
253-
:key #'name))))
303+
(failure-log-failure-report))
304+
305+
(defparameter *log-lines-that-are-boring*
306+
(mapcar 'ppcre:create-scanner
307+
'("^WARNING:")))
254308

255309
(defparameter *log-lines-to-highlight*
256310
(mapcar 'ppcre:create-scanner
257311
'("^; caught (WARNING|ERROR):"
258312
" READ error during"
313+
"^Backtrace for"
259314
"^Unhandled")))
260315

261316
(defparameter *failure-log-reconstitution-patterns*
@@ -267,21 +322,12 @@ the string is returned unchanged."
267322

268323
(defun highlighted-log-line-p (line)
269324
(loop for scanner in *log-lines-to-highlight*
270-
thereis (ppcre:scan scanner line)))
325+
thereis (ppcre:scan scanner line)))
326+
327+
(defun boring-log-line-p (line)
328+
(loop for scanner in *log-lines-that-are-boring*
329+
thereis (ppcre:scan scanner line)))
271330

272-
(defun highlight-log-lines (input-stream output-stream)
273-
(loop for line = (read-line input-stream nil)
274-
while line
275-
do
276-
(setf line (cl-who:escape-string line))
277-
(setf line (failure-log-reconstitute-line line))
278-
(if (highlighted-log-line-p line)
279-
(progn
280-
(write-string "<strong>" output-stream)
281-
(write-string line output-stream)
282-
(write-string "</strong>" output-stream))
283-
(write-string line output-stream))
284-
(terpri output-stream)))
285331

286332
(defmethod write-html-failure-report-header (object stream)
287333
(format stream "<html><head><title>~A</title>~
@@ -317,14 +363,16 @@ the string is returned unchanged."
317363
(loop for line = (read-line log-stream nil)
318364
while line
319365
do
320-
(setf line (cl-who:escape-string line))
321-
(if (highlighted-log-line-p line)
322-
(progn
323-
(write-string "<strong>" stream)
324-
(write-string line stream)
325-
(write-string "</strong>" stream))
326-
(write-string line stream))
327-
(terpri stream)))
366+
(setf line (cl-who:escape-string line))
367+
(cond ((highlighted-log-line-p line)
368+
(write-string "<strong>" stream)
369+
(write-string line stream)
370+
(write-string "</strong>" stream)
371+
(terpri stream))
372+
((boring-log-line-p line)
373+
(format stream "<span class='boring'>~A~%</span>" line))
374+
(t
375+
(write-line line stream)))))
328376
(format stream "</pre>")
329377
(format stream "</div>~%"))
330378

package.lisp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
;;;; package.lisp
22

33
(defpackage #:quicklisp-controller
4-
(:use #:cl)
4+
(:use #:cl
5+
#:westbrook)
56
(:export #:setup-directories)
67
(:shadowing-import-from #:sb-ext
78
#:run-program

quicklisp-controller.asd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
#:cl-who
1919
#:ubiquitous
2020
#:githappy
21-
#:project-info)
21+
#:project-info
22+
#:westbrook)
2223
:serial t
2324
:components ((:file "tarhash")
2425
(:file "github-issues")
@@ -28,6 +29,7 @@
2829
(:file "commands")
2930
(:file "utils")
3031
(:file "setup")
32+
(:file "provenance")
3133
(:file "upstream")
3234
(:file "system-file-magic-cache")
3335
(:file "dist-cache")
@@ -53,6 +55,7 @@
5355
(:file "ng-indexes")
5456
(:file "git")
5557
(:file "html-failure-report")
58+
(:file "rss-failure-feeds")
5659
(:file "recrank")
5760
(:file "irepl")))
5861

recrank.lisp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
(in-package #:quicklisp-controller)
44

5-
(defun recrank (&key (update t) (report t) (publish-failure-report t)
5+
(defun recrank (&key (update t) (report t) (feeds t)
6+
(publish-failure-report t)
67
parallel
78
(file #p"quicklisp:tmp;update-failures.txt"))
89
(clear-fasl-cache)
@@ -23,7 +24,12 @@
2324
(with-skipping
2425
(mock-report :mail t))
2526
(when (and publish-failure-report (report-publishing-enabled-p))
26-
(let ((url (publish-failure-report)))
27+
(let* ((report (failure-data t))
28+
(url (publish-failure-report :failure-report report)))
29+
(when feeds
30+
(in-anonymous-directory
31+
(write-feeds report "feeds/")
32+
(publish-feeds "feeds/")))
2733
(write-line url)))))
2834

2935
(defun recrank-to-file (file &rest args &key &allow-other-keys )

rss-failure-feeds.lisp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
;;;; rss-failure-feeds.lisp
2+
;;;;
3+
;;;; Building on the html failure reports, produces a set of feeds so
4+
;;;; authors and other interested people can subscribe to failures.
5+
;;;;
6+
;;;; Each failing source becomes an item in a feed. There are many
7+
;;;; different feeds - one for each source, one for each author (as
8+
;;;; far as that can be determined from e.g. github username
9+
;;;; patterns), and one for everything.
10+
;;;;
11+
;;;; The feeds don't have any state. They don't preserve old
12+
;;;; items. They are created fresh for every run. (This will change if
13+
;;;; it works poorly in feed readers.)
14+
;;;;
15+
;;;; Feeds are written into a directory based on a failure report
16+
;;;; generated from a (failure-data t) call, then uploaded to the
17+
;;;; failure report bucket all at once.
18+
;;;;
19+
20+
(in-package #:quicklisp-controller)
21+
22+
;;; Grouping sources by author (or other criteria)
23+
24+
(defparameter *location-grouping-patterns*
25+
'("://github.com/(.*?)/"
26+
"://bitbucket.org/(.*?)/"
27+
"(kpe.io)"
28+
"(xach)"
29+
"(wcp).sdf-eu.org"
30+
"(nklein)"
31+
"gitlab.com/(.*?)/"
32+
"(hexstreamsoft)"
33+
"(marijnh)averbeke"
34+
"(maraist)"
35+
"(lichteblau)"
36+
"gitlab.common-lisp.net/(.*?)/"))
37+
38+
(defun scan-first-register (pattern target)
39+
"Scan TARGET with the regex PATTERN. If there is a match, return the
40+
first register grouping."
41+
(multiple-value-bind (start end starts ends)
42+
(ppcre:scan pattern target)
43+
(declare (ignore end))
44+
(when (and start (plusp (length starts)))
45+
(subseq target (aref starts 0) (aref ends 0)))))
46+
47+
(defun group-name (failing-source)
48+
(let ((location (location (source failing-source))))
49+
(dolist (pattern *location-grouping-patterns*)
50+
(let ((group (scan-first-register pattern location)))
51+
(when group
52+
(return group))))))
53+
54+
;;; Producing feeds
55+
56+
(defun item-contents (failing-source)
57+
(with-output-to-string (stream)
58+
(let ((failures (failure-data failing-source)))
59+
(format stream "<p>Project <b>~A</b><p>~%" (name failing-source))
60+
(format stream "<p>Taken from <code>~A</code></p>~%"
61+
(first-line-of (source-file (source failing-source))))
62+
(format stream "<p>~A</p>~%" (versions-and-such))
63+
(format stream "<p>~D failing system~:*~P: ~{~A~^, ~}</p>~%"
64+
(length failures)
65+
(mapcar 'system-name failures))
66+
(format stream "<p><a href='~A'>Full build log</a></p>~%"
67+
(full-failure-report-url failing-source)))))
68+
69+
(defun item-title (failing-source)
70+
(format nil "~A" (name (source failing-source))))
71+
72+
(defclass failing-source-item (item)
73+
((failing-source
74+
:reader failing-source
75+
:initarg :failing-source)))
76+
77+
(defun failing-source-item (failing-source)
78+
(make-instance 'failing-source-item
79+
:failing-source failing-source
80+
:title (item-title failing-source)
81+
:description (item-contents failing-source)
82+
:guid (full-failure-report-url failing-source)
83+
:link (full-failure-report-url failing-source)))
84+
85+
86+
(defun feed-link (group)
87+
(format nil "http://report.quicklisp.org/rss/~A.rss" group))
88+
89+
(defun generate-feeds (failure-report)
90+
(let ((feeds (make-string-table)))
91+
(labels ((ensure-feed (title link)
92+
(let ((feed (gethash title feeds)))
93+
(or feed
94+
(setf (gethash title feeds)
95+
(make-instance 'feed
96+
:pub-date (get-universal-time)
97+
:last-build-date (get-universal-time)
98+
:title title
99+
:link link
100+
:description
101+
(format nil "Quicklisp build failures for ~S"
102+
title)))))))
103+
(let ((all (ensure-feed "all" (feed-link "all"))))
104+
(dolist (source (failure-data failure-report) feeds)
105+
(let* ((item (failing-source-item source))
106+
(group-name (group-name source)))
107+
(push item (items all))
108+
(when group-name
109+
(push item (items (ensure-feed group-name (feed-link group-name)))))))))))
110+
111+
(defun write-feeds (failure-report output-directory)
112+
(let ((feeds (generate-feeds failure-report)))
113+
(let ((*default-pathname-defaults* (truename
114+
(ensure-directories-exist
115+
output-directory))))
116+
(maphash
117+
(lambda (name feed)
118+
(generate-to (make-pathname :type "rss" :name name) feed))
119+
feeds)
120+
*default-pathname-defaults*)))
121+
122+
(defun publish-feeds (feed-directory)
123+
(let ((files (directory (make-pathname :name :wild
124+
:type "rss"
125+
:defaults feed-directory))))
126+
(dolist (file files)
127+
(let ((key (format nil "feeds/~A.rss" (pathname-name file))))
128+
(upload-report-file file key)))))
129+

0 commit comments

Comments
 (0)