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  }