github.com/Cloud-Foundations/Dominator@v0.3.4/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 type uDuration time.Duration 16 17 func (d rDuration) selector(sub *Sub) bool { 18 if time.Since(sub.lastReachableTime) <= time.Duration(d) { 19 return true 20 } 21 return false 22 } 23 24 func (d uDuration) selector(sub *Sub) bool { 25 if time.Since(sub.lastReachableTime) > time.Duration(d) { 26 return true 27 } 28 return false 29 } 30 31 func (herd *Herd) writeHtml(writer io.Writer) { 32 if herd.updatesDisabledReason != "" { 33 herd.writeDisableStatus(writer) 34 fmt.Fprintln(writer, "<br>") 35 } 36 var numAliveSubs, numCompliantSubs, numDeviantSubs uint64 37 var numLikelyCompliantSubs, numDisruptionWaitingSubs uint64 38 var reachableMinuteSubs, reachable10MinuteSubs, reachableHourSubs uint64 39 var reachableDaySubs, reachableWeekSubs, reachableMonthSubs uint64 40 var unreachableMinuteSubs, unreachable10MinuteSubs uint64 41 var unreachableHourSubs, unreachableDaySubs, unreachableWeekSubs uint64 42 var unreachableMonthSubs uint64 43 subCounters := []subCounter{ 44 {&numAliveSubs, selectAliveSub}, 45 {&numCompliantSubs, selectCompliantSub}, 46 {&numDeviantSubs, selectDeviantSub}, 47 {&numLikelyCompliantSubs, selectLikelyCompliantSub}, 48 {&numDisruptionWaitingSubs, selectDisruptionWaitingSub}, 49 {&reachableMinuteSubs, rDuration(time.Minute).selector}, 50 {&reachable10MinuteSubs, rDuration(10 * time.Minute).selector}, 51 {&reachableHourSubs, rDuration(time.Hour).selector}, 52 {&reachableDaySubs, rDuration(24 * time.Hour).selector}, 53 {&reachableWeekSubs, rDuration(7 * 24 * time.Hour).selector}, 54 {&reachableMonthSubs, rDuration(730 * time.Hour).selector}, 55 {&unreachableMinuteSubs, uDuration(time.Minute).selector}, 56 {&unreachable10MinuteSubs, uDuration(10 * time.Minute).selector}, 57 {&unreachableHourSubs, uDuration(time.Hour).selector}, 58 {&unreachableDaySubs, uDuration(24 * time.Hour).selector}, 59 {&unreachableWeekSubs, uDuration(7 * 24 * time.Hour).selector}, 60 {&unreachableMonthSubs, uDuration(730 * time.Hour).selector}, 61 } 62 numSubs := herd.countSelectedSubs(subCounters) 63 fmt.Fprintf(writer, "Time since current cycle start: %s<br>\n", 64 time.Since(herd.currentScanStartTime)) 65 if numSubs < 1 { 66 fmt.Fprintf(writer, "Duration of previous cycle: %s<br>\n", 67 herd.previousScanDuration) 68 } else { 69 fmt.Fprintf(writer, "Duration of previous cycle: %s (%s/sub)<br>\n", 70 herd.previousScanDuration, 71 herd.previousScanDuration/time.Duration(numSubs)) 72 } 73 if herd.scanCounter > 0 { 74 fmt.Fprintf(writer, "Average cycle time: %s<br>\n", 75 format.Duration(herd.totalScanDuration/ 76 time.Duration(herd.scanCounter))) 77 } 78 fmt.Fprintf(writer, "Cycle count: %d<br>\n", herd.scanCounter) 79 fmt.Fprintf(writer, "Image server: <a href=\"http://%s/\">%s</a><br>\n", 80 herd.imageManager, herd.imageManager) 81 if herd.defaultImageName != "" { 82 fmt.Fprintf(writer, 83 "Default image: <a href=\"http://%s/showImage?%s\">%s</a><br>\n", 84 herd.imageManager, herd.defaultImageName, herd.defaultImageName) 85 } 86 fmt.Fprintf(writer, 87 "Number of <a href=\"listSubs\">subs</a>: <a href=\"showAllSubs\">%d</a>", 88 numSubs) 89 fmt.Fprintf(writer, 90 " (<a href=\"showAllSubs?output=json\">JSON</a>") 91 fmt.Fprintf(writer, 92 ", <a href=\"showAllSubs?output=csv\">CSV</a>)<br>\n") 93 fmt.Fprintf(writer, 94 "Number of alive subs: <a href=\"showAliveSubs\">%d</a>", 95 numAliveSubs) 96 fmt.Fprintf(writer, 97 " (<a href=\"showAliveSubs?output=json\">JSON</a>") 98 fmt.Fprintf(writer, 99 ", <a href=\"showAliveSubs?output=csv\">CSV</a>)<br>\n") 100 fmt.Fprint(writer, "Number of reachable subs in last: ") 101 herd.writeReachableSubsLink(writer, reachableMinuteSubs, "1 min", "1m", 102 true) 103 herd.writeReachableSubsLink(writer, reachable10MinuteSubs, "10 min", "10m", 104 true) 105 herd.writeReachableSubsLink(writer, reachableHourSubs, "1 hour", "1h", true) 106 herd.writeReachableSubsLink(writer, reachableDaySubs, "1 day", "1d", true) 107 herd.writeReachableSubsLink(writer, reachableWeekSubs, "1 week", "1w", 108 true) 109 herd.writeReachableSubsLink(writer, reachableMonthSubs, "1 month", "1M", 110 false) 111 fmt.Fprint(writer, "Number of unreachable subs in last: ") 112 herd.writeUnreachableSubsLink(writer, unreachableMinuteSubs, "1 min", "1m", 113 true) 114 herd.writeUnreachableSubsLink(writer, unreachable10MinuteSubs, "10 min", 115 "10m", true) 116 herd.writeUnreachableSubsLink(writer, unreachableHourSubs, "1 hour", "1h", 117 true) 118 herd.writeUnreachableSubsLink(writer, unreachableDaySubs, "1 day", "1d", 119 true) 120 herd.writeUnreachableSubsLink(writer, unreachableWeekSubs, "1 week", "1w", 121 true) 122 herd.writeUnreachableSubsLink(writer, unreachableMonthSubs, "1 month", "1M", 123 false) 124 fmt.Fprintf(writer, 125 "Number of deviant subs: <a href=\"showDeviantSubs\">%d</a>", 126 numDeviantSubs) 127 fmt.Fprintf(writer, 128 " (<a href=\"showDeviantSubs?output=json\">JSON</a>") 129 fmt.Fprintf(writer, 130 ", <a href=\"showDeviantSubs?output=csv\">CSV</a>)<br>\n") 131 fmt.Fprintf(writer, 132 "Number of compliant subs: <a href=\"showCompliantSubs\">%d</a>(verified)", 133 numCompliantSubs) 134 fmt.Fprintf(writer, 135 ", <a href=\"showLikelyCompliantSubs\">%d</a>(likely)<br>\n", 136 numLikelyCompliantSubs) 137 if numDisruptionWaitingSubs > 0 { 138 fmt.Fprintf(writer, 139 "Number of subs waiting to disrupt: <a href=\"showAllSubs?status=disruption%%20requested&status=disruption%%20denied\">%d</a>", 140 numDisruptionWaitingSubs) 141 fmt.Fprintf(writer, 142 " (<a href=\"showAllSubs?status=disruption%%20requested&status=disruption%%20denied&output=json\">JSON</a>") 143 fmt.Fprintf(writer, 144 ", <a href=\"showAllSubs?status=disruption%%20requested&status=disruption%%20denied&output=csv\">CSV</a>)<br>\n") 145 } 146 fmt.Fprintf(writer, 147 "Image status for subs: <a href=\"showImagesForSubs\">dashboard</a>") 148 fmt.Fprintf(writer, 149 " (<a href=\"listImagesForSubs?output=json\">JSON</a>") 150 fmt.Fprintf(writer, 151 ", <a href=\"listImagesForSubs?output=csv\">CSV</a>)<br>\n") 152 subs := herd.getSelectedSubs(nil) 153 connectDurations := getConnectDurations(subs) 154 shortPollDurations := getPollDurations(subs, false) 155 fullPollDurations := getPollDurations(subs, true) 156 showDurationStats(writer, connectDurations, "Connect") 157 showDurationStats(writer, shortPollDurations, "Short poll") 158 showDurationStats(writer, fullPollDurations, "Full poll") 159 // TODO(rgooch): Figure out a way of restoring this information. 160 //fmt.Fprintf(writer, "Connection slots: %d out of %d<br>\n", 161 //len(herd.connectionSemaphore), cap(herd.connectionSemaphore)) 162 fmt.Fprintf(writer, "Poll slots: %d out of %d<br>\n", 163 len(herd.pollSemaphore), cap(herd.pollSemaphore)) 164 stats := herd.cpuSharer.GetStatistics() 165 timeSinceLastIdleEvent := time.Since(stats.LastIdleEvent) 166 fmt.Fprintf(writer, 167 "CPU slots: %d out of %d; idle events: %d, last: %s, time since: %s, last acquire: %s, last yield: %s<br>\n", 168 stats.NumCpuRunning, stats.NumCpu, stats.NumIdleEvents, 169 stats.LastIdleEvent.Format(timeFormat), 170 format.Duration(timeSinceLastIdleEvent), 171 format.Duration(time.Since(stats.LastAcquireEvent)), 172 format.Duration(time.Since(stats.LastYieldEvent))) 173 } 174 175 func (herd *Herd) writeDisableStatus(writer io.Writer) { 176 timeString := "" 177 if !herd.updatesDisabledTime.IsZero() { 178 timeString = " at " + herd.updatesDisabledTime.Format(timeFormat) 179 } 180 if herd.updatesDisabledBy == "" { 181 fmt.Fprintf(writer, 182 "<font color=\"red\">Updates disabled %s</font>%s\n", 183 herd.updatesDisabledReason, timeString) 184 } else { 185 fmt.Fprintf(writer, 186 "<font color=\"red\">Updates disabled by: %s %s</font>%s", 187 herd.updatesDisabledBy, herd.updatesDisabledReason, timeString) 188 } 189 } 190 191 func (herd *Herd) writeReachableSubsLink(writer io.Writer, 192 numSubs uint64, durationString string, query string, moreToCome bool) { 193 fmt.Fprintf(writer, "<a href=\"showReachableSubs?last=%s\">%s</a>", 194 query, durationString) 195 fmt.Fprintf(writer, "(<a href=\"listReachableSubs?last=%s\">%d</a>)", 196 query, numSubs) 197 if moreToCome { 198 fmt.Fprint(writer, ", ") 199 } else { 200 fmt.Fprintln(writer, "<br>") 201 } 202 } 203 204 func (herd *Herd) writeUnreachableSubsLink(writer io.Writer, 205 numSubs uint64, durationString string, query string, moreToCome bool) { 206 fmt.Fprintf(writer, "<a href=\"showUnreachableSubs?last=%s\">%s</a>", 207 query, durationString) 208 fmt.Fprintf(writer, "(<a href=\"listUnreachableSubs?last=%s\">%d</a>)", 209 query, numSubs) 210 if moreToCome { 211 fmt.Fprint(writer, ", ") 212 } else { 213 fmt.Fprintln(writer, "<br>") 214 } 215 } 216 217 func selectAliveSub(sub *Sub) bool { 218 switch sub.publishedStatus { 219 case statusUnknown: 220 return false 221 case statusConnecting: 222 return false 223 case statusDNSError: 224 return false 225 case statusConnectionRefused: 226 return false 227 case statusNoRouteToHost: 228 return false 229 case statusConnectTimeout: 230 return false 231 case statusFailedToConnect: 232 return false 233 case statusFailedToPoll: 234 return false 235 } 236 return true 237 } 238 239 func selectDeviantSub(sub *Sub) bool { 240 switch sub.publishedStatus { 241 case statusWaitingToPoll: 242 return true 243 case statusNotEnoughFreeSpace: 244 return true 245 case statusFetching, statusFetchDenied, statusFailedToFetch: 246 return true 247 case statusPushing, statusPushDenied, statusFailedToPush: 248 return true 249 case statusFailedToGetObject: 250 return true 251 case statusComputingUpdate: 252 return sub.lastSuccessfulImageName != sub.mdb.RequiredImage 253 case statusSendingUpdate: 254 return true 255 case statusMissingComputedFile: 256 return true 257 case statusUpdatesDisabled: 258 return true 259 case statusUpdating: 260 return true 261 case statusUpdateDenied: 262 return true 263 case statusFailedToUpdate: 264 return true 265 } 266 return false 267 } 268 269 func selectCompliantSub(sub *Sub) bool { 270 if sub.publishedStatus == statusSynced { 271 return true 272 } 273 return false 274 } 275 276 func selectDisruptionWaitingSub(sub *Sub) bool { 277 switch sub.publishedStatus { 278 case statusDisruptionRequested: 279 return true 280 case statusDisruptionDenied: 281 return true 282 } 283 return false 284 } 285 286 func selectLikelyCompliantSub(sub *Sub) bool { 287 switch sub.publishedStatus { 288 case statusWaitingToPoll, statusPolling: 289 return sub.lastSuccessfulImageName == sub.mdb.RequiredImage 290 case statusWaitingForNextFullPoll: 291 return true 292 case statusSynced: 293 return true 294 } 295 return false 296 } 297 298 func getConnectDurations(subs []*Sub) []time.Duration { 299 durations := make([]time.Duration, 0, len(subs)) 300 for _, sub := range subs { 301 if sub.lastConnectDuration > 0 { 302 durations = append(durations, sub.lastConnectDuration) 303 } 304 } 305 sort.Sort(durationList(durations)) 306 return durations 307 } 308 309 func getPollDurations(subs []*Sub, full bool) []time.Duration { 310 durations := make([]time.Duration, 0, len(subs)) 311 for _, sub := range subs { 312 var duration time.Duration 313 if full { 314 duration = sub.lastFullPollDuration 315 } else { 316 duration = sub.lastShortPollDuration 317 } 318 if duration > 0 { 319 durations = append(durations, duration) 320 } 321 } 322 sort.Sort(durationList(durations)) 323 return durations 324 } 325 326 type durationList []time.Duration 327 328 func (dl durationList) Len() int { 329 return len(dl) 330 } 331 332 func (dl durationList) Less(i, j int) bool { 333 return dl[i] < dl[j] 334 } 335 336 func (dl durationList) Swap(i, j int) { 337 dl[i], dl[j] = dl[j], dl[i] 338 } 339 340 func showDurationStats(writer io.Writer, durations []time.Duration, 341 durationType string) { 342 if len(durations) < 1 { 343 return 344 } 345 var avgDuration time.Duration 346 for _, duration := range durations { 347 avgDuration += duration 348 } 349 avgDuration /= time.Duration(len(durations)) 350 medDuration := durations[len(durations)/2] 351 unit := "ns" 352 scale := 1.0 353 switch { 354 case medDuration > 1e9: 355 unit = "s" 356 scale = 1e-9 357 case medDuration > 1e6: 358 unit = "ms" 359 scale = 1e-6 360 case medDuration > 1e3: 361 unit = "µs" 362 scale = 1e-3 363 } 364 fmt.Fprintf(writer, 365 "%s durations: %.3f/%.3f/%.3f/%.3f %s (avg/med/min/max)<br>\n", 366 durationType, 367 float64(avgDuration)*scale, float64(medDuration)*scale, 368 float64(durations[0])*scale, float64(durations[len(durations)-1])*scale, 369 unit) 370 }