// A program to go through all of the CS Domain possible IPs and create information about // the current status of each public machine. // // The program operates in two phases. // Phase 1: identify all live public machines in the CS domain. // Phase 2: for each live, public machine, collect usage statistics about it. // Each phase is done in parallel so that the the querying of remote machines takes m // time where m is the time for the slowest machine to respond. // // GTowell // Created : July 2021 // Modified: August 2022 package main import ( "encoding/csv" "encoding/json" "fmt" "os" "os/exec" "sort" "strings" "sync" "time" ) // show extra stuff is debugging is true. // It will be true if there are ANY command line parameters. // Thsi is the ONLY use of command line parameters var debug bool = false func main() { // there are some IPs that are known to not be public. Put them here. ipskipList2:= [256]bool{} // defines machines that are available for student use // generally these are only the machines in the labs publicLocMap = map[string]bool{"230":true, "231":true, "232":true, "sunZone":false, "UNK":false, "david":false, "deepak":false} // locations from from csv file nameLocMap = make(map[string]string) // the CSV file has a bunch of useful information infile, _ := os.Open("/home/gtowell/bin/machine_nam_loc.csv") lines, _ := csv.NewReader(infile).ReadAll() infile.Close() for _,r := range lines { nameLocMap[strings.TrimSpace(r[0])]=strings.TrimSpace(r[1]) } if len(os.Args) > 1 { debug=true } if debug { fmt.Println("Getting\n") } getLiveIPs(ipskipList2) partTwo() } //=========================== // Phase 1 // Get machine names for live IPs //=========================== // A struct to hold some basic information about an IP. type basicdta struct { ip int // the IP, just the final number ipstring string // the full IP as a string name string // the name associated with that IP loc string // the location (or the last known location) of the machine } var nameLocMap map[string]string // A map from machine name to machine location. // IPs may change, the name and location are constant var publicLocMap map[string]bool // Map from machine name to its location // a printer for the basicdata structure func (d basicdta) String() string { return fmt.Sprintf("%d:%s:name=%s:%v", d.ip, d.ipstring, d.name,d.loc); } // given the last part of the IP address, // query the full IP address for its machine name // Seems kind of weird, but again, the IP address is not fixed. func getName(ippart int) basicdta { start := time.Now() rtn := basicdta{ip:ippart, ipstring: fmt.Sprintf("165.106.10.%d", ippart) }; a10 := fmt.Sprintf("gtowell@165.106.10.%d", ippart) aaa := "timeout" cmd := exec.Command(aaa, "15", "ssh", "-oBatchMode=yes", "-oConnectTimeout=5", "-oStrictHostKeyChecking=no", a10, "hostname") stdout, err := cmd.Output() if err != nil { rtn.name = "" rtn.loc="UNK" rtn.ip = -rtn.ip return rtn } rtn.name = strings.TrimSpace(string(stdout)) rtn.loc=nameLocMap[rtn.name] elap := time.Since(start) if debug { fmt.Printf("GOT %v %v\n", rtn, elap) } return rtn } // since the slice basicdata is acted upon by a set of parallel processes // associate it with a lock, so that changes to not interact poorly var ( mu sync.Mutex basicDataSlice []basicdta ) func getLiveIPs(ipSkipList [256]bool) { var wg sync.WaitGroup basicDataSlice=nil for rz:=1; rz<255; rz++ { //fmt.Printf("AAA %s\n", r); if !ipSkipList[rz] { wg.Add(1) go func(rr int) { defer wg.Done() //fmt.Printf("BBB %s\n", rr) rtn := getName(rr) mu.Lock() if rtn.ip>0 { basicDataSlice = append(basicDataSlice, rtn) } else { if debug { fmt.Printf("PRIVATE: %v\n", rtn) } } mu.Unlock() //fmt.Printf("%v %d\n", rtn, len(ch)) }(rz) } } wg.Wait() sort.Slice(basicDataSlice, func(i, j int) bool { return basicDataSlice[i].ip < basicDataSlice[j].ip }) gcount:=0; for _,v := range basicDataSlice { if (v.ip>0) { gcount++; } } //fmt.Printf("publicLocMap %v\n", publicLocMap) fmt.Printf("Data %v\n", basicDataSlice) if debug { fmt.Printf("%d\n", gcount) } } //================================ // Phase 2 // Get usage stats for live machines //================================ // struct for holding the info collect in phase 2 // the 'json:...' specifies how to name each element in // a JSON object type dta struct { Ip string `json:"ip"` Name string `json:"nickname"` Cores string `json:"cores"` Loc string `json:"loc"` Uptime string `json:"uptime"` Users string `json:"users"` Onemin string `json:"use 1min"` Fivemin string `json:"use 5min"` Tenmin string `json:"use 10min"` } // another paring of a lock with a variable to make // explicit that this is a shared variable across threads var ( muu sync.Mutex chh []dta ) // Controller for Phase 2. In this part, we collect status // information about the machines found live in phase 1. // That status information is written out to a JSON file // for formatting in Javascript by a web page. func partTwo() { if debug { fmt.Println("Part2") } if debug { fmt.Print("Getting\n\n") } doPartTwo() if debug { fmt.Println("Gotten") } } //start a bunch of threads that do the actual work // this function will not complete until all of the threads // it started are complete. func doPartTwo() { var wg sync.WaitGroup chh=nil for _,r := range basicDataSlice { //fmt.Printf("AAA %s\n", r); wg.Add(1) go func(rr basicdta) { defer wg.Done() //fmt.Printf("BBB %s\n", rr) rtn := getStatus(rr) muu.Lock() chh = append(chh, rtn) muu.Unlock() //fmt.Printf("%v %d\n", rtn, len(chh)) }(r) } wg.Wait() // with all of the information collected, sort it, // then write it into a JSON file sort.Slice(chh, func(i, j int) bool { return chh[i].Ip < chh[j].Ip }) aa := make(map[string]interface{}) aa["time"]=time.Now().Format("Jan-02-2006 15:04:05") aa["machines"] = chh if debug { fmt.Printf("QQQ %v\n", chh) } file,_ := os.Create("/home/gtowell/bin/jj.json") byt,_ := json.Marshal(aa) fmt.Fprintf(file, "%v", string(byt)) file.Close() } // Get the status of a machine, executed from a thread // Variable names are fairly poor func getStatus(info basicdta) dta { app := "ssh" rtn := dta{Ip:strings.TrimSpace(info.ipstring), Name:strings.TrimSpace(info.name), Cores:"8", Uptime:"", Loc:strings.TrimSpace(info.loc)}; a10 := fmt.Sprintf("gtowell@%s", rtn.Ip) cmd := exec.Command(app, "-oBatchMode=yes", "-oConnectTimeout=2", "-oStrictHostKeyChecking=no", a10, "uptime") stdout, err := cmd.Output() if err != nil { return rtn } r := csv.NewReader(strings.NewReader(string(stdout))) record, err := r.Read() if err != nil { return rtn } // parse the returned string to get the information I want. // there is probably a better way to get the info, but // this works. sli := strings.Split(record[0], " ") rtn.Uptime=fmt.Sprintf("%s %s", sli[len(sli)-2], sli[len(sli)-1]) ssli := strings.Split(strings.TrimSpace(record[len(record)-4]), " ") if debug { fmt.Printf("UUU %v\n", ssli) } rtn.Users= strings.TrimSpace(ssli[0]) rtn.Onemin=strings.Split(record[len(record)-3], ": ")[1] rtn.Fivemin=record[len(record)-2] rtn.Tenmin=record[len(record)-1] return rtn }