logs.go
1 package main 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "sort" 10 "strings" 11 "time" 12 ) 13 14 // Color returns a HTML color code between green and yellow based on the 15 // number of days that passed since ds. 16 func (ds DateString) Color() string { 17 date, _ := time.Parse("2006-01-02T15:04:05Z", string(ds)) 18 days := int(time.Since(date).Hours() / 24) 19 if days > 255 { 20 days = 255 21 } 22 return fmt.Sprintf("#%02xff00", days) 23 } 24 25 func fetchLogs(dirs chan<- NamedFS) { 26 err := fs.WalkDir(bsdirFS, ".", func(path string, d fs.DirEntry, err error) error { 27 if err != nil { 28 return err 29 } 30 if path[0] == '.' { 31 return nil 32 } 33 if d.IsDir() && len(strings.Split(path, string(filepath.Separator))) == 4 { 34 dirs <- NamedFS{ 35 FS: bsdirFS, 36 Name: path, 37 } 38 } 39 return nil 40 }) 41 if err != nil { 42 fmt.Fprintf(os.Stderr, "Reading logs failed: %v\n", err) 43 } 44 close(dirs) 45 } 46 47 func collectLogs(dirs <-chan NamedFS) { 48 data.Logs = make(map[Timeframe][]Log) 49 data.VendorBoardDate = make(map[string]DateString) 50 data.VendorBoardReferenced = make(map[string]bool) 51 timeframes := make(map[Timeframe]bool) 52 gitcache := make(map[string]string) 53 for dir := range dirs { 54 upstream := "" 55 revB, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "revision.txt")) 56 if err != nil { 57 continue 58 } 59 rev := string(revB) 60 skipDir := false 61 for _, line := range strings.Split(rev, "\n") { 62 item := strings.SplitN(line, ":", 2) 63 if len(item) != 2 { 64 // This is an error, but let's try to extract 65 // as much value out of revision.txt files as 66 // possible, even if some lines are erroneous. 67 continue 68 } 69 if item[0] == "Upstream revision" { 70 upstream = strings.TrimSpace(item[1]) 71 // tried using go-git, but its resolver 72 // couldn't expand short hashes despite the 73 // docs claiming that it can. 74 if val, ok := gitcache[upstream]; ok { 75 upstream = val 76 } else { 77 res, err := exec.Command("/usr/bin/git", "-C", cbdir, "log", "-n1", "--format=%H", upstream).Output() 78 if err != nil { 79 fmt.Fprintf(os.Stderr, "revision %s not found \n", upstream) 80 skipDir = true 81 break 82 } 83 gitcache[upstream] = strings.TrimSpace(string(res)) 84 upstream = gitcache[upstream] 85 } 86 } 87 } 88 if skipDir { 89 continue 90 } 91 92 rawFiles, err := fs.Glob(dir.FS, filepath.Join(dir.Name, "*")) 93 if err != nil { 94 fmt.Fprintf(os.Stderr, "Could not fetch log data, skipping: %v\n", err) 95 continue 96 } 97 98 pieces := strings.Split(dir.Name, string(filepath.Separator)) 99 if len(pieces) < 4 { 100 fmt.Fprintf(os.Stderr, "log directory %s is malformed, skipping\n", dir.Name) 101 continue 102 } 103 vendorBoard := strings.Join(pieces[:2], "/") 104 // TODO: these need to become "second to last" and "last" item 105 // but only after compatibility to the current system has been ensured. 106 dateTimePath := pieces[3] 107 dateTime, err := time.Parse(time.RFC3339, strings.ReplaceAll(dateTimePath, "_", ":")) 108 if err != nil { 109 fmt.Fprintf(os.Stderr, "Could not parse timestamp from %s: %v\n", dir.Name, err) 110 continue 111 } 112 dateTimeNormal := dateTime.UTC().Format("2006-01-02T15:04:05Z") 113 dateTimeHuman := dateTime.UTC().Format(time.UnixDate) 114 tfYear, tfWeek := dateTime.ISOWeek() 115 timeframe := Timeframe(fmt.Sprintf("%dW%02d", tfYear, tfWeek)) 116 117 if !timeframes[timeframe] { 118 timeframes[timeframe] = true 119 data.Timeframes = append(data.Timeframes, timeframe) 120 data.Logs[timeframe] = []Log{} 121 } 122 123 files := []Path{} 124 l := len(dir.Name) + 1 125 for _, file := range rawFiles { 126 if file[l:] == "revision.txt" { 127 continue 128 } 129 files = append(files, Path{ 130 Path: dir.Name + "/", 131 Basename: file[l:], 132 }) 133 } 134 135 data.Logs[timeframe] = append(data.Logs[timeframe], Log{ 136 VendorBoard: vendorBoard, 137 Time: dateTimeNormal, 138 TimeReadable: dateTimeHuman, 139 Upstream: upstream, 140 Files: files, 141 }) 142 } 143 sort.Slice(data.Timeframes, func(i, j int) bool { 144 // reverse sort 145 return data.Timeframes[i] > data.Timeframes[j] 146 }) 147 for bi := range data.Logs { 148 bucket := data.Logs[bi] 149 sort.Slice(data.Logs[bi], func(i, j int) bool { 150 if bucket[i].Time == bucket[j].Time { 151 return (bucket[i].VendorBoard > bucket[j].VendorBoard) 152 } 153 return (bucket[i].Time > bucket[j].Time) 154 }) 155 } 156 for _, ts := range data.Timeframes { 157 for li, l := range data.Logs[ts] { 158 if _, match := data.VendorBoardDate[l.VendorBoard]; match { 159 continue 160 } 161 data.VendorBoardDate[l.VendorBoard] = DateString(l.Time) 162 data.Logs[ts][li].Reference = true 163 } 164 } 165 }