package main import ( "bufio" "bytes" "fmt" "log" "runtime" "strings" ) // reused by GoRuntinesStatus() var buffer []byte var output *bytes.Buffer // This uses the most memory. There has to be a better way. const DEBUG_THIS bool = false /* var GStatus map[string]string = map[string]string{ "idle": "I", "runnable": "r", "running": "R", "syscall": "s", "waiting": "W", "dead": "D", "copystack": "C", "preempted": "P", "sleep": "S", "select": "s", // missing "chan receive": "<", // missing "chan send": ">", // missing } */ // Map Status to Symbol. func GStatusMapper(status []byte) byte { switch status[0] { case 'c': if status[1] == 'o' { return 'C' } switch status[5] { case 'r': return '<' case 's': return '>' } return '?' case 'd': return 'D' case 'i': return 'I' case 'p': return 'P' case 'r': switch status[4] { case 'a': return 'r' case 'i': return 'R' } return '?' case 's': switch status[1] { case 'y': return 's' case 'l': return 'S' case 'e': return 's' } return '?' case 'w': return 'W' default: return '?' } } // Get Go Routines and map the Status. See GStatus. func GoRoutinesStatus() []byte { if output == nil { log.Println("new bytes.Buffer{}") output = &bytes.Buffer{} } output.Reset() // Map status to letter. /* if GStatus == nil { GStatus = make(map[string]string) GStatus = map[string]string{ "idle": "I", "runnable": "r", "running": "R", "syscall": "s", "waiting": "W", "dead": "D", "copystack": "C", "preempted": "P", "sleep": "S", "select": "s", // missing "chan receive": "<", // missing "chan send": ">", // missing } } */ // 2K works, we don't have to grow any more. if buffer == nil { buffer = make([]byte, 2048) } read := runtime.Stack(buffer, true) if read == cap(buffer) { log.Printf("Increasing buffer from (%d bytes)\n", cap(buffer)) buffer = make([]byte, cap(buffer)+1024) return GoRoutinesStatus() } var buff []byte = buffer[:read] // fmt.Println(string(buffer[0:read])) // This is horribly inefficient (memory allocation wise) var pos, lineend int var slice []byte var space, spaceend int var status []byte var count int for pos != -1 { if DEBUG_THIS { log.Printf("POS: %d (%d)\n", pos, len(buff)) } lineend = bytes.Index(buff[pos:], []byte("\n")) if DEBUG_THIS { log.Printf("LineEnd: %d\n", lineend) } if lineend != -1 { lineend += pos slice = buff[pos:lineend] pos = lineend + 1 } else { slice = buffer[pos:] pos = -1 } // Process line if bytes.HasPrefix(slice, []byte("goroutine ")) { if DEBUG_THIS { log.Printf("GoRoutine %d [%s]\n", count, slice) count++ } // Found a gorutine line space = bytes.Index(slice, []byte{'['}) spaceend = bytes.Index(slice[space+1:], []byte{']'}) if spaceend == -1 { status = slice[space+1:] } else { spaceend += space + 1 // log.Printf("space %d, spaceend %d\n", space, spaceend) status = slice[space+1 : spaceend] } // Ok, status looks like what we're looking for here! if DEBUG_THIS { log.Printf("Status: [%s]\n", status) } space = bytes.Index(status, []byte{','}) if space != -1 { status = status[:space] // log.Printf("Status now: [%s]\n", status) } rstatus := GStatusMapper(status) //rstatus, ok := GStatus[string(status)] output.WriteByte(rstatus) /* if ok { output.WriteString(rstatus) } else { log.Printf("** Status [%s] not found.\n", status) } */ } } if false { var reader = bytes.NewReader(buffer[0:read]) var scanner = bufio.NewScanner(reader) for scanner.Scan() { var text = scanner.Text() if strings.HasPrefix(text, "goroutine ") { // fmt.Println(text) // goroutine 20 [runnable]: // goroutine 1 [select, 1 minutes]: // goroutine 17 [chan receive]: // Get ID and get Status. parts := strings.SplitN(text, " ", 3) /* gid, err := strconv.Atoi(parts[1]) if err != nil { continue } */ status := parts[2][1 : len(parts[2])-2] if strings.Contains(status, ",") { status = strings.Split(status, ",")[0] } /* rstatus := GStatusMapper(status) output.WriteByte(rstatus) rstatus, ok := GStatus[status] if ok { output.WriteString(rstatus) } else { log.Printf("Status %s not found.\n[%s]\n", rstatus, text) } */ } } } return output.Bytes() } // Return a nicely formatted string representing memory usage. func ReprMem(size uint64, output *bytes.Buffer) { if output == nil { return } output.Reset() var value float64 = float64(size) var units byte = ' ' if value > 1024 { // In KB units = 'K' value /= 1024 } if value > 1024 { // In MB units = 'M' value /= 1024 } if value > 1024 { // Im GB units = 'G' value /= 1024 } fmt.Fprintf(output, "%0.2f", value) if output.Len() > 4 { output.Reset() fmt.Fprintf(output, "%0.1f", value) } if output.Len() > 4 { output.Reset() fmt.Fprintf(output, "%0.0f", value) } if units != ' ' { output.WriteByte(units) } } // Return array of memory usage information func Memory(result *map[string]*bytes.Buffer) { var memstats runtime.MemStats runtime.ReadMemStats(&memstats) if (*result)["Sys"] == nil { (*result)["Sys"] = &bytes.Buffer{} } ReprMem(memstats.Sys, (*result)["Sys"]) // ReprMem(memstats.HeapAlloc, (*result)["Heap"]) /* if (*result)["HeapSys"] == nil { (*result)["HeapSys"] = &bytes.Buffer{} } */ if (*result)["Heap"] == nil { (*result)["Heap"] = &bytes.Buffer{} } ReprMem(memstats.HeapAlloc, (*result)["Heap"]) if (*result)["HeapSys"] == nil { (*result)["HeapSys"] = &bytes.Buffer{} } ReprMem(memstats.HeapSys, (*result)["MeapSys"]) if (*result)["StackSys"] == nil { (*result)["StackSys"] = &bytes.Buffer{} } ReprMem(memstats.StackSys, (*result)["StackSys"]) // Don't scale the Number of GCs, it is good as-is. if (*result)["NumGC"] == nil { (*result)["NumGC"] = &bytes.Buffer{} } (*result)["NumGC"].Reset() fmt.Fprintf((*result)["NumGC"], "%d", memstats.NumGC) // ReprMem(uint64(memstats.NumGC), (*result)["NumGC"]) /* (*result)["HeapInUse"] = ReprMem(memstats.HeapInuse) (*result)["HeapSys"] = ReprMem(memstats.HeapSys) (*result)["Sys"] = ReprMem(memstats.Sys) (*result)["StackInUse"] = ReprMem(memstats.StackInuse) (*result)["StackSys"] = ReprMem(memstats.StackSys) (*result)["GCSys"] = ReprMem(memstats.GCSys) */ }