gitlab.com/SkynetLabs/skyd@v1.6.9/cmd/skyc/hostdbcmd.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "math/big" 6 "os" 7 "text/tabwriter" 8 9 "github.com/spf13/cobra" 10 11 "gitlab.com/NebulousLabs/errors" 12 "gitlab.com/SkynetLabs/skyd/node/api" 13 "gitlab.com/SkynetLabs/skyd/skymodules" 14 "go.sia.tech/siad/modules" 15 "go.sia.tech/siad/types" 16 ) 17 18 const scanHistoryLen = 30 19 20 var ( 21 hostdbNumHosts int 22 ) 23 24 var ( 25 hostdbCmd = &cobra.Command{ 26 Use: "hostdb", 27 Short: "Interact with the renter's host database.", 28 Long: "View the list of active hosts, the list of all hosts, or query specific hosts.\nIf the '-v' flag is set, a list of recent scans will be provided, with the most\nrecent scan on the right. a '0' indicates that the host was offline, and a '1'\nindicates that the host was online.", 29 Run: wrap(hostdbcmd), 30 } 31 32 hostdbBlockDomainCmd = &cobra.Command{ 33 Use: "block [domain] [domain]...", 34 Short: "Block a domain in the hostdb", 35 Long: "Block a domain or list of domains in the hostdb", 36 Run: hostdbblockdomaincmd, 37 } 38 39 hostdbBlockedDomainsCmd = &cobra.Command{ 40 Use: "blocked", 41 Short: "View blocked domains in the hostdb", 42 Long: "View blocked domains in the hostdb", 43 Run: wrap(hostdbblockeddomainscmd), 44 } 45 46 hostdbFiltermodeCmd = &cobra.Command{ 47 Use: "filtermode", 48 Short: "View hostDB filtermode.", 49 Long: "View the hostDB filtermode and the filtered hosts", 50 Run: wrap(hostdbfiltermodecmd), 51 } 52 53 hostdbSetFiltermodeCmd = &cobra.Command{ 54 Use: "setfiltermode [filtermode] [host] [host] [host]...", 55 Short: "Set the filtermode.", 56 Long: `Set the hostdb filtermode and specify hosts. 57 [filtermode] can be whitelist, blacklist, or disable. 58 [host] is the host public key.`, 59 Run: hostdbsetfiltermodecmd, 60 } 61 62 hostdbUnblockDomainCmd = &cobra.Command{ 63 Use: "unblock [domain] [domain]...", 64 Short: "Unblock a domain in the hostdb", 65 Long: "Unblock a domain or list of domains in the hostdb", 66 Run: hostdbunblockdomaincmd, 67 } 68 69 hostdbViewCmd = &cobra.Command{ 70 Use: "view [pubkey]", 71 Short: "View the full information for a host.", 72 Long: "View detailed information about a host, including things like a score breakdown.", 73 Run: wrap(hostdbviewcmd), 74 } 75 ) 76 77 // printScoreBreakdown prints the score breakdown of a host, provided the info. 78 func printScoreBreakdown(info *api.HostdbHostsGET) { 79 fmt.Println("\n Score Breakdown:") 80 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 81 fmt.Fprintf(w, "\t\tAge:\t %.3f\n", info.ScoreBreakdown.AgeAdjustment) 82 fmt.Fprintf(w, "\t\tBase Price:\t %.3f\n", info.ScoreBreakdown.BasePriceAdjustment) 83 fmt.Fprintf(w, "\t\tBurn:\t %.3f\n", info.ScoreBreakdown.BurnAdjustment) 84 fmt.Fprintf(w, "\t\tCollateral:\t %.3f\n", info.ScoreBreakdown.CollateralAdjustment/1e96) 85 fmt.Fprintf(w, "\t\tDuration:\t %.3f\n", info.ScoreBreakdown.DurationAdjustment) 86 fmt.Fprintf(w, "\t\tInteraction:\t %.3f\n", info.ScoreBreakdown.InteractionAdjustment) 87 fmt.Fprintf(w, "\t\tPrice:\t %.3f\n", info.ScoreBreakdown.PriceAdjustment*1e24) 88 fmt.Fprintf(w, "\t\tStorage:\t %.3f\n", info.ScoreBreakdown.StorageRemainingAdjustment) 89 fmt.Fprintf(w, "\t\tUptime:\t %.3f\n", info.ScoreBreakdown.UptimeAdjustment) 90 fmt.Fprintf(w, "\t\tVersion:\t %.3f\n", info.ScoreBreakdown.VersionAdjustment) 91 fmt.Fprintf(w, "\t\tConversion Rate:\t %.3f\n", info.ScoreBreakdown.ConversionRate) 92 if err := w.Flush(); err != nil { 93 die("failed to flush writer") 94 } 95 } 96 97 // hostdbcmd is the handler for the command `skyc hostdb`. 98 // Lists hosts known to the hostdb 99 func hostdbcmd() { 100 if !verbose { 101 info, err := httpClient.HostDbActiveGet() 102 if errors.Contains(err, api.ErrAPICallNotRecognized) { 103 // Assume module is not loaded if status command is not recognized. 104 fmt.Printf("HostDB:\n Status: %s\n\n", moduleNotReadyStatus) 105 return 106 } else if err != nil { 107 die("Could not fetch host list:", err) 108 } 109 110 if len(info.Hosts) == 0 { 111 fmt.Println("No known active hosts") 112 return 113 } 114 115 // Strip down to the number of requested hosts. 116 if hostdbNumHosts != 0 && hostdbNumHosts < len(info.Hosts) { 117 info.Hosts = info.Hosts[len(info.Hosts)-hostdbNumHosts:] 118 } 119 120 fmt.Println(len(info.Hosts), "Active Hosts:") 121 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 122 fmt.Fprintln(w, "\t\tAddress\tVersion\tPrice (per TB per Mo)") 123 for i, host := range info.Hosts { 124 price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte) 125 fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\n", len(info.Hosts)-i, host.NetAddress, host.Version, currencyUnits(price)) 126 } 127 if err := w.Flush(); err != nil { 128 die("failed to flush writer") 129 } 130 } else { 131 info, err := httpClient.HostDbAllGet() 132 if err != nil { 133 die("Could not fetch host list:", err) 134 } 135 if len(info.Hosts) == 0 { 136 fmt.Println("No known hosts") 137 return 138 } 139 140 // Iterate through the hosts and divide by category. 141 var activeHosts, inactiveHosts, offlineHosts []api.ExtendedHostDBEntry 142 for _, host := range info.Hosts { 143 if host.AcceptingContracts && len(host.ScanHistory) > 0 && host.ScanHistory[len(host.ScanHistory)-1].Success { 144 activeHosts = append(activeHosts, host) 145 continue 146 } 147 if len(host.ScanHistory) > 0 && host.ScanHistory[len(host.ScanHistory)-1].Success { 148 inactiveHosts = append(inactiveHosts, host) 149 continue 150 } 151 offlineHosts = append(offlineHosts, host) 152 } 153 154 if hostdbNumHosts > 0 && len(offlineHosts) > hostdbNumHosts { 155 offlineHosts = offlineHosts[len(offlineHosts)-hostdbNumHosts:] 156 } 157 if hostdbNumHosts > 0 && len(inactiveHosts) > hostdbNumHosts { 158 inactiveHosts = inactiveHosts[len(inactiveHosts)-hostdbNumHosts:] 159 } 160 if hostdbNumHosts > 0 && len(activeHosts) > hostdbNumHosts { 161 activeHosts = activeHosts[len(activeHosts)-hostdbNumHosts:] 162 } 163 164 fmt.Println() 165 fmt.Println(len(offlineHosts), "Offline Hosts:") 166 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 167 fmt.Fprintln(w, "\t\tPubkey\tAddress\tVersion\tRemaining Storage\tPrice (/ TB / Month)\tDownload Price (/ TB)\tUptime\tRecent Scans") 168 for i, host := range offlineHosts { 169 // Compute the total measured uptime and total measured downtime for this 170 // host. 171 uptimeRatio := float64(0) 172 if len(host.ScanHistory) > 1 { 173 downtime := host.HistoricDowntime 174 uptime := host.HistoricUptime 175 recentTime := host.ScanHistory[0].Timestamp 176 recentSuccess := host.ScanHistory[0].Success 177 for _, scan := range host.ScanHistory[1:] { 178 if recentSuccess { 179 uptime += scan.Timestamp.Sub(recentTime) 180 } else { 181 downtime += scan.Timestamp.Sub(recentTime) 182 } 183 recentTime = scan.Timestamp 184 recentSuccess = scan.Success 185 } 186 uptimeRatio = float64(uptime) / float64(uptime+downtime) 187 } 188 189 // Get the scan history string. 190 scanHistStr := "" 191 displayScans := host.ScanHistory 192 if len(host.ScanHistory) > scanHistoryLen { 193 displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:] 194 } 195 for _, scan := range displayScans { 196 if scan.Success { 197 scanHistStr += "1" 198 } else { 199 scanHistStr += "0" 200 } 201 } 202 203 // Get a string representation of the historic outcomes of the most 204 // recent scans. 205 price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte) 206 downloadBWPrice := host.StoragePrice.Mul(modules.BytesPerTerabyte) 207 fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\t%v\t%v\t%v\t%.3f\t%s\n", len(offlineHosts)-i, host.PublicKeyString, 208 host.NetAddress, host.Version, modules.FilesizeUnits(host.RemainingStorage), currencyUnits(price), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr) 209 } 210 if err := w.Flush(); err != nil { 211 die("failed to flush writer") 212 } 213 214 fmt.Println() 215 fmt.Println(len(inactiveHosts), "Inactive Hosts:") 216 w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 217 fmt.Fprintln(w, "\t\tPubkey\tAddress\tVersion\tRemaining Storage\tPrice (/ TB / Month)\tCollateral (/ TB / Month)\tDownload Price (/ TB)\tUptime\tRecent Scans") 218 for i, host := range inactiveHosts { 219 // Compute the total measured uptime and total measured downtime for this 220 // host. 221 uptimeRatio := float64(0) 222 if len(host.ScanHistory) > 1 { 223 downtime := host.HistoricDowntime 224 uptime := host.HistoricUptime 225 recentTime := host.ScanHistory[0].Timestamp 226 recentSuccess := host.ScanHistory[0].Success 227 for _, scan := range host.ScanHistory[1:] { 228 if recentSuccess { 229 uptime += scan.Timestamp.Sub(recentTime) 230 } else { 231 downtime += scan.Timestamp.Sub(recentTime) 232 } 233 recentTime = scan.Timestamp 234 recentSuccess = scan.Success 235 } 236 uptimeRatio = float64(uptime) / float64(uptime+downtime) 237 } 238 239 // Get a string representation of the historic outcomes of the most 240 // recent scans. 241 scanHistStr := "" 242 displayScans := host.ScanHistory 243 if len(host.ScanHistory) > scanHistoryLen { 244 displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:] 245 } 246 for _, scan := range displayScans { 247 if scan.Success { 248 scanHistStr += "1" 249 } else { 250 scanHistStr += "0" 251 } 252 } 253 254 price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte) 255 collateral := host.Collateral.Mul(modules.BlockBytesPerMonthTerabyte) 256 downloadBWPrice := host.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte) 257 fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%.3f\t%s\n", len(inactiveHosts)-i, host.PublicKeyString, host.NetAddress, host.Version, modules.FilesizeUnits(host.RemainingStorage), currencyUnits(price), currencyUnits(collateral), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr) 258 } 259 fmt.Fprintln(w, "\t\tPubkey\tAddress\tVersion\tRemaining Storage\tPrice (/ TB / Month)\tCollateral (/ TB / Month)\tDownload Price (/ TB)\tUptime\tRecent Scans") 260 if err := w.Flush(); err != nil { 261 die("failed to flush writer") 262 } 263 264 // Grab the host at the 3/5th point and use it as the reference. (it's 265 // like using the median, except at the 3/5th point instead of the 1/2 266 // point.) 267 referenceScore := big.NewRat(1, 1) 268 if len(activeHosts) > 0 { 269 referenceIndex := len(activeHosts) * 3 / 5 270 hostInfo, err := httpClient.HostDbHostsGet(activeHosts[referenceIndex].PublicKey) 271 if err != nil { 272 die("Could not fetch provided host:", err) 273 } 274 if !hostInfo.ScoreBreakdown.Score.IsZero() { 275 referenceScore = new(big.Rat).Inv(new(big.Rat).SetInt(hostInfo.ScoreBreakdown.Score.Big())) 276 } 277 } 278 279 fmt.Println() 280 fmt.Println(len(activeHosts), "Active Hosts:") 281 w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 282 fmt.Fprintln(w, "\t\tPubkey\tAddress\tVersion\tScore\tRemaining Storage\tContract Fee\tPrice (/ TB / Month)\tCollateral (/ TB / Month)\tDownload Price (/TB)\tUptime\tRecent Scans") 283 for i, host := range activeHosts { 284 // Compute the total measured uptime and total measured downtime for this 285 // host. 286 uptimeRatio := float64(0) 287 if len(host.ScanHistory) > 1 { 288 downtime := host.HistoricDowntime 289 uptime := host.HistoricUptime 290 recentTime := host.ScanHistory[0].Timestamp 291 recentSuccess := host.ScanHistory[0].Success 292 for _, scan := range host.ScanHistory[1:] { 293 if recentSuccess { 294 uptime += scan.Timestamp.Sub(recentTime) 295 } else { 296 downtime += scan.Timestamp.Sub(recentTime) 297 } 298 recentTime = scan.Timestamp 299 recentSuccess = scan.Success 300 } 301 uptimeRatio = float64(uptime) / float64(uptime+downtime) 302 } 303 304 // Get a string representation of the historic outcomes of the most 305 // recent scans. 306 scanHistStr := "" 307 displayScans := host.ScanHistory 308 if len(host.ScanHistory) > scanHistoryLen { 309 displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:] 310 } 311 for _, scan := range displayScans { 312 if scan.Success { 313 scanHistStr += "1" 314 } else { 315 scanHistStr += "0" 316 } 317 } 318 319 // Grab the score information for the active hosts. 320 hostInfo, err := httpClient.HostDbHostsGet(host.PublicKey) 321 if err != nil { 322 die("Could not fetch provided host:", err) 323 } 324 score, _ := new(big.Rat).Mul(referenceScore, new(big.Rat).SetInt(hostInfo.ScoreBreakdown.Score.Big())).Float64() 325 326 price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte) 327 collateral := host.Collateral.Mul(modules.BlockBytesPerMonthTerabyte) 328 downloadBWPrice := host.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte) 329 fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\t%12.6g\t%v\t%v\t%v\t%v\t%v\t%.3f\t%s\n", len(activeHosts)-i, host.PublicKeyString, host.NetAddress, host.Version, score, modules.FilesizeUnits(host.RemainingStorage), currencyUnits(host.ContractPrice), currencyUnits(price), currencyUnits(collateral), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr) 330 } 331 fmt.Fprintln(w, "\t\tPubkey\tAddress\tVersion\tScore\tRemaining Storage\tContract Fee\tPrice (/ TB / Month)\tCollateral (/ TB / Month)\tDownload Price (/TB)\tUptime\tRecent Scans") 332 if err := w.Flush(); err != nil { 333 die("failed to flush writer") 334 } 335 } 336 } 337 338 // hostdbblockdomaincmd is the handler for the command `skyc hostdb 339 // block`. 340 func hostdbblockdomaincmd(cmd *cobra.Command, domains []string) { 341 if len(domains) == 0 { 342 _ = cmd.UsageFunc()(cmd) 343 os.Exit(exitCodeUsage) 344 } 345 err := httpClient.HostDbBlockDomainsPost(domains) 346 if err != nil { 347 die("Could not block domain in hostdb:", err) 348 } 349 fmt.Println("Successfully Blocked Domain") 350 } 351 352 // hostdbblockeddomainscmd is the handler for the command `skyc hostdb 353 // blockeddomains`. 354 func hostdbblockeddomainscmd() { 355 hdbdg, err := httpClient.HostDbBlockedDomainsGet() 356 if err != nil { 357 die(err) 358 } 359 fmt.Println() 360 fmt.Println(" Blocked Domains:") 361 for _, domain := range hdbdg.Domains { 362 fmt.Println(" ", domain) 363 } 364 fmt.Println() 365 } 366 367 // hostdbfiltermodecmd is the handler for the command `skyc hostdb 368 // filtermode`. 369 func hostdbfiltermodecmd() { 370 hdfmg, err := httpClient.HostDbFilterModeGet() 371 if err != nil { 372 die(err) 373 } 374 fmt.Println() 375 fmt.Println(" HostDB Filter Mode:", hdfmg.FilterMode) 376 fmt.Println(" Hosts:") 377 for _, host := range hdfmg.Hosts { 378 fmt.Println(" ", host) 379 } 380 fmt.Println() 381 } 382 383 // hostdbsetfiltermodecmd is the handler for the command `skyc hostdb 384 // setfiltermode`. sets the hostdb filtermode (whitelist, blacklist, disable) 385 func hostdbsetfiltermodecmd(cmd *cobra.Command, args []string) { 386 var fm skymodules.FilterMode 387 var filterModeStr string 388 var host types.SiaPublicKey 389 var hosts []types.SiaPublicKey 390 switch len(args) { 391 case 0: 392 _ = cmd.UsageFunc()(cmd) 393 os.Exit(exitCodeUsage) 394 case 1: 395 filterModeStr = args[0] 396 if filterModeStr != "disable" { 397 die("if only submitting filtermode it should be `disable`") 398 } 399 default: 400 filterModeStr = args[0] 401 for i := 1; i < len(args); i++ { 402 host.LoadString(args[i]) 403 hosts = append(hosts, host) 404 } 405 } 406 err := fm.FromString(filterModeStr) 407 if err != nil { 408 fmt.Println("Could not parse filtermode: ", err) 409 die() 410 } 411 412 err = httpClient.HostDbFilterModePost(fm, hosts) 413 if err != nil { 414 fmt.Println("Could not set hostdb filtermode: ", err) 415 die() 416 } 417 fmt.Println("Successfully set the filter mode") 418 } 419 420 // hostdbunblockdomaincmd is the handler for the command `skyc hostdb 421 // unblock`. 422 func hostdbunblockdomaincmd(cmd *cobra.Command, domains []string) { 423 if len(domains) == 0 { 424 _ = cmd.UsageFunc()(cmd) 425 os.Exit(exitCodeUsage) 426 } 427 err := httpClient.HostDbUnblockDomainsPost(domains) 428 if err != nil { 429 die("Could not unblock domain in hostdb:", err) 430 } 431 fmt.Println("Successfully Unblocked Domain") 432 } 433 434 // hostdbviewcmd is the handler for the command `skyc hostdb view`. 435 // shows detailed information about a host in the hostdb. 436 func hostdbviewcmd(pubkey string) { 437 var publicKey types.SiaPublicKey 438 publicKey.LoadString(pubkey) 439 info, err := httpClient.HostDbHostsGet(publicKey) 440 if err != nil { 441 die("Could not fetch provided host:", err) 442 } 443 444 fmt.Println("Host information:") 445 446 fmt.Println(" Public Key: ", info.Entry.PublicKeyString) 447 fmt.Println(" Version: ", info.Entry.Version) 448 fmt.Println(" Block First Seen: ", info.Entry.FirstSeen) 449 fmt.Println(" Absolute Score: ", info.ScoreBreakdown.Score) 450 fmt.Println(" Filtered: ", info.Entry.Filtered) 451 fmt.Println(" NetAddress: ", info.Entry.NetAddress) 452 fmt.Println(" Last IP Net Change: ", info.Entry.LastIPNetChange) 453 fmt.Println(" Number of IP Net Changes: ", len(info.Entry.IPNets)) 454 455 fmt.Println("\n Host Settings:") 456 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 457 458 fmt.Fprintln(w, "\t\tAccepting Contracts:\t", info.Entry.AcceptingContracts) 459 fmt.Fprintln(w, "\t\tMax Duration:\t", info.Entry.MaxDuration) 460 fmt.Fprintln(w, "\t\tWindow Size:\t", info.Entry.WindowSize) 461 fmt.Fprintln(w, "\t\tTotal Storage:\t", modules.FilesizeUnits(info.Entry.TotalStorage)) 462 fmt.Fprintln(w, "\t\tRemaining Storage:\t", modules.FilesizeUnits(info.Entry.RemainingStorage)) 463 fmt.Fprintln(w, "\t\tMax Download Batch Size:\t", modules.FilesizeUnits(info.Entry.MaxDownloadBatchSize)) 464 fmt.Fprintln(w, "\t\tMax Revision Batch Size:\t", modules.FilesizeUnits(info.Entry.MaxReviseBatchSize)) 465 fmt.Fprintln(w, "\t\tSector Size:\t", modules.FilesizeUnits(info.Entry.SectorSize)) 466 fmt.Fprintln(w, "\n\t\tOffered Collateral (TB / Mo):\t", currencyUnits(info.Entry.Collateral.Mul(modules.BlockBytesPerMonthTerabyte))) 467 fmt.Fprintln(w, "\t\tMax Collateral:\t", currencyUnits(info.Entry.MaxCollateral)) 468 fmt.Fprintln(w, "\t\tContract Price:\t", currencyUnits(info.Entry.ContractPrice)) 469 fmt.Fprintln(w, "\t\tBase RPC Price:\t", currencyUnits(info.Entry.BaseRPCPrice)) 470 fmt.Fprintln(w, "\t\tSector Access Price:\t", currencyUnits(info.Entry.SectorAccessPrice)) 471 fmt.Fprintln(w, "\t\tStorage Price (TB / Mo):\t", currencyUnits(info.Entry.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte))) 472 fmt.Fprintln(w, "\t\tDownload Price (1 TB):\t", currencyUnits(info.Entry.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte))) 473 fmt.Fprintln(w, "\t\tUpload Price (1 TB):\t", currencyUnits(info.Entry.UploadBandwidthPrice.Mul(modules.BytesPerTerabyte))) 474 fmt.Fprintln(w, "\t\tUnlock Hash:\t", info.Entry.UnlockHash) 475 fmt.Fprintln(w, "\n\t\tVersion:\t", info.Entry.Version) 476 fmt.Fprintln(w, "\t\tRevision Number:\t", info.Entry.RevisionNumber) 477 if err := w.Flush(); err != nil { 478 die("failed to flush writer") 479 } 480 481 printScoreBreakdown(&info) 482 483 // Compute the total measured uptime and total measured downtime for this 484 // host. 485 uptimeRatio := float64(0) 486 if len(info.Entry.ScanHistory) > 1 { 487 downtime := info.Entry.HistoricDowntime 488 uptime := info.Entry.HistoricUptime 489 recentTime := info.Entry.ScanHistory[0].Timestamp 490 recentSuccess := info.Entry.ScanHistory[0].Success 491 for _, scan := range info.Entry.ScanHistory[1:] { 492 if recentSuccess { 493 uptime += scan.Timestamp.Sub(recentTime) 494 } else { 495 downtime += scan.Timestamp.Sub(recentTime) 496 } 497 recentTime = scan.Timestamp 498 recentSuccess = scan.Success 499 } 500 uptimeRatio = float64(uptime) / float64(uptime+downtime) 501 } 502 503 // Compute the uptime ratio, but shift by 0.02 to acknowledge fully that 504 // 98% uptime and 100% uptime is valued the same. 505 fmt.Println("\n Scan History Length: ", len(info.Entry.ScanHistory)) 506 fmt.Println(" Historic Downtime: ", info.Entry.HistoricDowntime) 507 fmt.Println(" Historic Uptime: ", info.Entry.HistoricUptime) 508 fmt.Printf(" Historic Failed Interactions: %.3f\n", info.Entry.HistoricFailedInteractions) 509 fmt.Printf(" Historic Successful Interactions: %.3f\n", info.Entry.HistoricSuccessfulInteractions) 510 fmt.Println(" Recent Failed Interactions: ", info.Entry.RecentFailedInteractions) 511 fmt.Println(" Recent Successful Interactions: ", info.Entry.RecentSuccessfulInteractions) 512 fmt.Printf(" Overall Uptime: %.3f\n", uptimeRatio) 513 514 fmt.Println() 515 }