github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/dom/herd/html.go (about) 1 package herd 2 3 import ( 4 "fmt" 5 "io" 6 "sort" 7 "time" 8 9 "github.com/Cloud-Foundations/Dominator/lib/format" 10 ) 11 12 var timeFormat string = "02 Jan 2006 15:04:05.99 MST" 13 14 type rDuration time.Duration 15 16 func (d rDuration) selector(sub *Sub) bool { 17 if time.Since(sub.lastReachableTime) <= time.Duration(d) { 18 return true 19 } 20 return false 21 } 22 23 func (herd *Herd) writeHtml(writer io.Writer) { 24 if herd.updatesDisabledReason != "" { 25 herd.writeDisableStatus(writer) 26 fmt.Fprintln(writer, "<br>") 27 } 28 numSubs := herd.countSelectedSubs(nil) 29 fmt.Fprintf(writer, "Time since current cycle start: %s<br>\n", 30 time.Since(herd.currentScanStartTime)) 31 if numSubs < 1 { 32 fmt.Fprintf(writer, "Duration of previous cycle: %s<br>\n", 33 herd.previousScanDuration) 34 } else { 35 fmt.Fprintf(writer, "Duration of previous cycle: %s (%s/sub)<br>\n", 36 herd.previousScanDuration, 37 herd.previousScanDuration/time.Duration(numSubs)) 38 } 39 fmt.Fprintf(writer, "Image server: <a href=\"http://%s/\">%s</a><br>\n", 40 herd.imageManager, herd.imageManager) 41 if herd.defaultImageName != "" { 42 fmt.Fprintf(writer, 43 "Default image: <a href=\"http://%s/showImage?%s\">%s</a><br>\n", 44 herd.imageManager, herd.defaultImageName, herd.defaultImageName) 45 } 46 fmt.Fprintf(writer, 47 "Number of <a href=\"listSubs\">subs</a>: <a href=\"showAllSubs\">%d</a><br>\n", 48 numSubs) 49 numSubs = herd.countSelectedSubs(selectAliveSub) 50 fmt.Fprintf(writer, 51 "Number of alive subs: <a href=\"showAliveSubs\">%d</a><br>\n", 52 numSubs) 53 fmt.Fprint(writer, "Number of reachable subs in last: ") 54 herd.writeReachableSubsLink(writer, time.Minute, "1 min", "1m", true) 55 herd.writeReachableSubsLink(writer, time.Minute*10, "10 min", "10m", true) 56 herd.writeReachableSubsLink(writer, time.Hour, "1 hour", "1h", true) 57 herd.writeReachableSubsLink(writer, time.Hour*24, "1 day", "1d", true) 58 herd.writeReachableSubsLink(writer, time.Hour*24*7, "1 week", "1w", false) 59 numSubs = herd.countSelectedSubs(selectDeviantSub) 60 fmt.Fprintf(writer, 61 "Number of deviant subs: <a href=\"showDeviantSubs\">%d</a><br>\n", 62 numSubs) 63 numSubs = herd.countSelectedSubs(selectCompliantSub) 64 fmt.Fprintf(writer, 65 "Number of compliant subs: <a href=\"showCompliantSubs\">%d</a><br>\n", 66 numSubs) 67 subs := herd.getSelectedSubs(nil) 68 connectDurations := getConnectDurations(subs) 69 shortPollDurations := getPollDurations(subs, false) 70 fullPollDurations := getPollDurations(subs, true) 71 showDurationStats(writer, connectDurations, "Connect") 72 showDurationStats(writer, shortPollDurations, "Short poll") 73 showDurationStats(writer, fullPollDurations, "Full poll") 74 // TODO(rgooch): Figure out a way of restoring this information. 75 //fmt.Fprintf(writer, "Connection slots: %d out of %d<br>\n", 76 //len(herd.connectionSemaphore), cap(herd.connectionSemaphore)) 77 fmt.Fprintf(writer, "Poll slots: %d out of %d<br>\n", 78 len(herd.pollSemaphore), cap(herd.pollSemaphore)) 79 stats := herd.cpuSharer.GetStatistics() 80 timeSinceLastIdleEvent := time.Since(stats.LastIdleEvent) 81 fmt.Fprintf(writer, 82 "CPU slots: %d out of %d; idle events: %d, last: %s, time since: %s, last acquire: %s, last yield: %s<br>\n", 83 stats.NumCpuRunning, stats.NumCpu, stats.NumIdleEvents, 84 stats.LastIdleEvent.Format(timeFormat), 85 format.Duration(timeSinceLastIdleEvent), 86 format.Duration(time.Since(stats.LastAcquireEvent)), 87 format.Duration(time.Since(stats.LastYieldEvent))) 88 } 89 90 func (herd *Herd) writeDisableStatus(writer io.Writer) { 91 timeString := "" 92 if !herd.updatesDisabledTime.IsZero() { 93 timeString = " at " + herd.updatesDisabledTime.Format(timeFormat) 94 } 95 if herd.updatesDisabledBy == "" { 96 fmt.Fprintf(writer, 97 "<font color=\"red\">Updates disabled %s</font>%s\n", 98 herd.updatesDisabledReason, timeString) 99 } else { 100 fmt.Fprintf(writer, 101 "<font color=\"red\">Updates disabled by: %s %s</font>%s", 102 herd.updatesDisabledBy, herd.updatesDisabledReason, timeString) 103 } 104 } 105 106 func (herd *Herd) writeReachableSubsLink(writer io.Writer, 107 duration time.Duration, durationString string, query string, 108 moreToCome bool) { 109 numSubs := herd.countSelectedSubs(rDuration(duration).selector) 110 fmt.Fprintf(writer, "<a href=\"showReachableSubs?last=%s\">%s</a>", 111 query, durationString) 112 fmt.Fprintf(writer, "(<a href=\"listReachableSubs?last=%s\">%d</a>)", 113 query, numSubs) 114 if moreToCome { 115 fmt.Fprint(writer, ", ") 116 } else { 117 fmt.Fprintln(writer, "<br>") 118 } 119 } 120 121 func selectAliveSub(sub *Sub) bool { 122 switch sub.publishedStatus { 123 case statusUnknown: 124 return false 125 case statusConnecting: 126 return false 127 case statusDNSError: 128 return false 129 case statusConnectionRefused: 130 return false 131 case statusNoRouteToHost: 132 return false 133 case statusConnectTimeout: 134 return false 135 case statusFailedToConnect: 136 return false 137 case statusFailedToPoll: 138 return false 139 } 140 return true 141 } 142 143 func selectDeviantSub(sub *Sub) bool { 144 switch sub.publishedStatus { 145 case statusComputingUpdate: 146 return true 147 case statusSendingUpdate: 148 return true 149 case statusUpdatesDisabled: 150 return true 151 case statusUpdating: 152 return true 153 case statusUpdateDenied: 154 return true 155 case statusFailedToUpdate: 156 return true 157 } 158 return false 159 } 160 161 func selectCompliantSub(sub *Sub) bool { 162 if sub.publishedStatus == statusSynced { 163 return true 164 } 165 return false 166 } 167 168 func getConnectDurations(subs []*Sub) []time.Duration { 169 durations := make([]time.Duration, 0, len(subs)) 170 for _, sub := range subs { 171 if sub.lastConnectDuration > 0 { 172 durations = append(durations, sub.lastConnectDuration) 173 } 174 } 175 sort.Sort(durationList(durations)) 176 return durations 177 } 178 179 func getPollDurations(subs []*Sub, full bool) []time.Duration { 180 durations := make([]time.Duration, 0, len(subs)) 181 for _, sub := range subs { 182 var duration time.Duration 183 if full { 184 duration = sub.lastFullPollDuration 185 } else { 186 duration = sub.lastShortPollDuration 187 } 188 if duration > 0 { 189 durations = append(durations, duration) 190 } 191 } 192 sort.Sort(durationList(durations)) 193 return durations 194 } 195 196 type durationList []time.Duration 197 198 func (dl durationList) Len() int { 199 return len(dl) 200 } 201 202 func (dl durationList) Less(i, j int) bool { 203 return dl[i] < dl[j] 204 } 205 206 func (dl durationList) Swap(i, j int) { 207 dl[i], dl[j] = dl[j], dl[i] 208 } 209 210 func showDurationStats(writer io.Writer, durations []time.Duration, 211 durationType string) { 212 if len(durations) < 1 { 213 return 214 } 215 var avgDuration time.Duration 216 for _, duration := range durations { 217 avgDuration += duration 218 } 219 avgDuration /= time.Duration(len(durations)) 220 medDuration := durations[len(durations)/2] 221 unit := "ns" 222 scale := 1.0 223 switch { 224 case medDuration > 1e9: 225 unit = "s" 226 scale = 1e-9 227 case medDuration > 1e6: 228 unit = "ms" 229 scale = 1e-6 230 case medDuration > 1e3: 231 unit = "µs" 232 scale = 1e-3 233 } 234 fmt.Fprintf(writer, 235 "%s durations: %.3f/%.3f/%.3f/%.3f %s (avg/med/min/max)<br>\n", 236 durationType, 237 float64(avgDuration)*scale, float64(medDuration)*scale, 238 float64(durations[0])*scale, float64(durations[len(durations)-1])*scale, 239 unit) 240 }