Created
June 23, 2021 21:15
-
-
Save stgleb/029ea7e0efa7c2b24477782677eddcc3 to your computer and use it in GitHub Desktop.
stats
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/csv" | |
"fmt" | |
"io" | |
"log" | |
"os" | |
"sort" | |
"strconv" | |
"time" | |
) | |
// Record represents single line in csv report. | |
type Record struct { | |
ID string | |
UserID string | |
Received time.Time | |
Begin time.Time | |
End time.Time | |
Delete bool | |
ExitCode uint8 | |
Size int64 | |
} | |
type userPair struct { | |
key string | |
value int64 | |
} | |
type codePair struct { | |
code uint8 | |
count int64 | |
} | |
func parseRecord(record []string) (*Record, error) { | |
id := record[0] | |
uid := record[1] | |
received, err := time.Parse(time.RFC3339, record[2]) | |
if err != nil { | |
return nil, fmt.Errorf("parse received time: %v", err) | |
} | |
begin, err := time.Parse(time.RFC3339, record[3]) | |
if err != nil { | |
return nil, fmt.Errorf("parse begin time: %v", err) | |
} | |
end, err := time.Parse(time.RFC3339, record[4]) | |
if err != nil { | |
return nil, fmt.Errorf("parse end time: %v", err) | |
} | |
deleted, err := strconv.ParseBool(record[5]) | |
if err != nil { | |
return nil, fmt.Errorf("parse deleted: %v", err) | |
} | |
exitCode, err := strconv.ParseInt(record[6], 10, 16) | |
if err != nil { | |
return nil, fmt.Errorf("parse exit code: %v", err) | |
} | |
size, err := strconv.ParseInt(record[7], 10, 64) | |
if err != nil { | |
return nil, fmt.Errorf("parse size: %v", err) | |
} | |
return &Record{ | |
ID: id, | |
UserID: uid, | |
Received: received, | |
Begin: begin, | |
End: end, | |
Delete: deleted, | |
ExitCode: uint8(exitCode), | |
Size: size, | |
}, nil | |
} | |
func readFile(fileName string) (records []*Record, err error) { | |
csvFile, err := os.Open(fileName) | |
if err != nil { | |
fmt.Println(err) | |
} | |
defer func() { | |
if err = csvFile.Close(); err != nil { | |
return | |
} | |
}() | |
reader := csv.NewReader(csvFile) | |
if err != nil { | |
fmt.Println(err) | |
} | |
for { | |
record, err := reader.Read() | |
if err == io.EOF { | |
break | |
} | |
if err != nil { | |
return nil, err | |
} | |
if len(record) < 8 { | |
return nil, fmt.Errorf("not enough data in the line") | |
} | |
parsedRecord, err := parseRecord(record) | |
if err != nil { | |
return nil, err | |
} | |
records = append(records, parsedRecord) | |
} | |
return | |
} | |
// Return all records whose builds happened in specific time frame [from, begin]. | |
func timeFrame(from, to time.Time, records []*Record) []*Record { | |
result := make([]*Record, 0, 0) | |
for _, r := range records { | |
if (r.Begin.After(from) && r.Begin.Before(to)) || (r.End.Before(to) && r.End.After(from)) { | |
result = append(result, r) | |
} | |
} | |
return result | |
} | |
// Returns ids of top n users of system. | |
func topNUsers(n int, records []*Record) []string { | |
hitMap := map[string]int64{} | |
for _, r := range records { | |
hitMap[r.UserID] += 1 | |
} | |
pairs := make([]userPair, 0, len(hitMap)) | |
for user, count := range hitMap { | |
pairs = append(pairs, userPair{ | |
user, | |
count, | |
}) | |
} | |
sort.Slice(pairs, func(i, j int) bool { | |
return pairs[i].value < pairs[j].value | |
}) | |
if n < len(pairs) { | |
pairs = pairs[:n] | |
} | |
users := make([]string, 0, len(pairs)) | |
for _, p := range pairs { | |
users = append(users, p.key) | |
} | |
return users | |
} | |
func buildAnalysis(records []*Record) (float64, []codePair) { | |
// Assume exit codes numbers are limited to a single unsigned byte. | |
exitCodes := make([]int64, 256) | |
for _, r := range records { | |
exitCodes[r.ExitCode] += 1 | |
} | |
rate := float64(exitCodes[0]) / float64(len(records)) | |
codePairs := make([]codePair, 0, 0) | |
for code, count := range exitCodes { | |
if count > 0 && code > 0 { | |
codePairs = append(codePairs, codePair{ | |
code: uint8(code), | |
count: count, | |
}) | |
} | |
} | |
sort.Slice(codePairs, func(i, j int) bool { | |
return codePairs[i].count < codePairs[j].count | |
}) | |
return rate, codePairs | |
} | |
func main() { | |
records, err := readFile("stats.csv") | |
if err != nil { | |
log.Fatalf("read file %v", err) | |
} | |
from, _ := time.Parse(time.RFC3339, "2018-10-31T13:23:25-04:00") | |
to, _ := time.Parse(time.RFC3339, "2018-11-29T10:51:24-05:00") | |
filtered := timeFrame(from, to, records) | |
log.Printf("records from %v to %v\n", from, to) | |
for _, r := range filtered { | |
log.Println(r) | |
} | |
log.Println("-----------------------") | |
users := topNUsers(5, records) | |
log.Println("top 5 users:") | |
for _, user := range users { | |
log.Printf("user: %s\n", user) | |
} | |
log.Println("-----------------------") | |
rate, codeStats := buildAnalysis(records) | |
log.Printf("success rate:= %v\n", rate) | |
log.Println("error code in ascending order") | |
for _, codeStat := range codeStats { | |
log.Printf("code: %v count: %v", codeStat.code, codeStat.count) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment