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  }