Skip to content

Instantly share code, notes, and snippets.

@stgleb
Created June 23, 2021 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stgleb/029ea7e0efa7c2b24477782677eddcc3 to your computer and use it in GitHub Desktop.
Save stgleb/029ea7e0efa7c2b24477782677eddcc3 to your computer and use it in GitHub Desktop.
stats
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