backend/analyzer/ThreadDumps.go (119 lines of code) (raw):

package analyzer import ( "fmt" "io/ioutil" "log" "os" "path" "path/filepath" "strings" "sync" "time" ) //List of files mapped to info from one file of ThreadDump type ThreadDump map[string]ThreadDumpFile type ThreadDumpFile struct { Content string DateAndTime time.Time } //List of ThreadDumps Folders type AggregatedThreadDumps map[string]ThreadDump func analyzeThreadDumpsFolder(path string, threadDumpsFolder string) ThreadDump { t := make(ThreadDump) visit := func(path string, file os.DirEntry, err error) error { if strings.Contains(path, threadDumpsFolder) { fileInfo, _ := os.Stat(path) if !fileInfo.IsDir() { content, _ := ioutil.ReadFile(path) t[path] = ThreadDumpFile{ Content: string(content), DateAndTime: GetTimeStampFromThreadDump(path), } } } return nil } _ = filepath.WalkDir(path, visit) return t } func (t *ThreadDumpFile) ConvertToHTML() (html string) { return t.Content } func (t *ThreadDump) GetFile(path string) *ThreadDumpFile { for threadDumpElementPath, file := range *t { if strings.Contains(threadDumpElementPath, path) { return &file } } return nil } func (t *ThreadDump) GetElementWithNumber(number int) *ThreadDumpFile { threadDump := *t for i, path := range sortedKeys(threadDump) { if i == number { file := threadDump[path] return &file } } return nil } func (t *ThreadDump) GetFiltersHTML() (html string) { for _, i := range sortedKeys(*t) { html = html + "<li filename='" + filepath.Base(i) + "'>" + GetThreadDumpDisplayName(filepath.Base(i)) + "</li>" } return html } func getTimeStampFromThreadDumpFilename(filename string) time.Time { filename = path.Base(filename) timeStamp := GetRegexNamedCapturedGroups(`(?P<Year>\d{4})(?P<Month>\d{2})(?P<Day>\d{2})-(?P<Hours>\d{2})(?P<Minutes>\d{2})(?P<Seconds>\d{2})`, filename) if len(timeStamp) == 0 { return time.Time{} } s := fmt.Sprintf("%s-%s-%sT%s:%s:%sZ", timeStamp["Year"], timeStamp["Month"], timeStamp["Day"], timeStamp["Hours"], timeStamp["Minutes"], timeStamp["Seconds"]) t, err := time.Parse(time.RFC3339, s) if err != nil { log.Printf("getTimeStampFromThreadDumpFilename failed. Initial string: '%s', error: %s", s, err) } return t } //GetTimeStampFromThreadDump returns zero time.Time{} for file/dir that does not contain timestamp // returns time.Time{} for file/dir that contains timestamp func GetTimeStampFromThreadDump(path string) (t time.Time) { fileinfo, _ := os.Stat(path) if getTimeStampFromThreadDumpFilename(filepath.Base(path)).IsZero() { if fileinfo == nil { return t } else if fileinfo.IsDir() { return getTimeStampFromFirstInnerFile(path) } return t } return getTimeStampFromThreadDumpFilename(filepath.Base(path)) } //GetThreadDumpDisplayName retuns formatted timestamp string if it is possible to convert the filename to timestamp // If it is not possible - returns filename func GetThreadDumpDisplayName(path string) string { t := time.Time{} if GetTimeStampFromThreadDump(path).IsZero() { return filepath.Base(path) } else { t = GetTimeStampFromThreadDump(path) } duration := GetRegexNamedCapturedGroups(`(?P<Duration>\d{2})sec$`, path)["Duration"] if len(duration) > 0 { duration = fmt.Sprintf("(%ss)", duration) } s := fmt.Sprintf("%s %s", t.Format("2006.01.02 15:04:05"), duration) return s } //scans all the filenames in ThreadDumps directory and returns the timestamp of the earliest func getTimeStampFromFirstInnerFile(path string) (threadDumpTime time.Time) { threadDumpTime = time.Now() var wg sync.WaitGroup visit := func(path string, file os.DirEntry, err error) error { wg.Add(1) go func() { defer wg.Done() if t := getTimeStampFromThreadDumpFilename(path); !t.IsZero() && t.Before(threadDumpTime) { threadDumpTime = t } }() return nil } _ = filepath.WalkDir(path, visit) wg.Wait() return threadDumpTime }