github.com/JBoudou/Itero@v0.1.7/tools/logstat.go (about) 1 // Itero - Online iterative vote application 2 // Copyright (C) 2021 Joseph Boudou 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as 6 // published by the Free Software Foundation, either version 3 of the 7 // License, or (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bufio" 21 "fmt" 22 "os" 23 "regexp" 24 "sort" 25 "time" 26 ) 27 28 type LogStat struct{} 29 30 func (self LogStat) Cmd() string { 31 return "logstat" 32 } 33 34 func (self LogStat) String() string { 35 return "Analyse Itero logs." 36 } 37 38 func init() { 39 AddCommand(LogStat{}) 40 } 41 42 func (self LogStat) Run(args []string) { 43 timedRegex := regexp.MustCompile(`^(\S{10} \S{15}) \S `) 44 fullRegex := regexp.MustCompile(`^\S{10} \S{15} H ([0-9.]{7,15}):\d+ (/\S*) .*(\d{3}) in (\S+)\s$`) 45 segmentRegex := regexp.MustCompile(`^((?:/[^/]*[^/\d][^/]*){2,}?)(?:/\d+)*/[a-zA-Z0-9\-_]{9}$`) 46 47 var firstTime time.Time 48 var lastTime time.Time 49 total := countTime{} 50 bySt := byStatus{} 51 byIp := byKeyAndStatus{} 52 byRq := byKeyAndStatus{} 53 var unparsedLines uint32 54 55 rd := bufio.NewReader(os.Stdin) 56 for { 57 line, err := rd.ReadString('\n') 58 if err != nil { 59 break 60 } 61 62 capture := timedRegex.FindStringSubmatch(line) 63 if capture == nil { 64 unparsedLines += 1 65 continue 66 } 67 t, err := time.ParseInLocation("2006/01/02 15:04:05.000000", capture[1], time.Local) 68 if err != nil { 69 unparsedLines += 1 70 continue 71 } 72 if firstTime.IsZero() { 73 firstTime = t 74 } else { 75 lastTime = t 76 } 77 78 capture = fullRegex.FindStringSubmatch(line) 79 if capture == nil { 80 continue 81 } 82 dur, err := time.ParseDuration(capture[4]) 83 if err != nil { 84 continue 85 } 86 total.Add(dur) 87 status := capture[3] 88 bySt.Add(status, dur) 89 byIp.Add(capture[1], status, dur) 90 91 request := capture[2] 92 capture = segmentRegex.FindStringSubmatch(request) 93 if capture != nil { 94 request = capture[1] 95 } 96 byRq.Add(request, status, dur) 97 } 98 99 fmt.Printf("Server run: %v.\n", lastTime.Sub(firstTime)) 100 total.Display("") 101 fmt.Printf("%d unparsed lines.\n", unparsedLines) 102 103 fmt.Println("Status:") 104 bySt.Display(" ") 105 106 fmt.Println("Visitors:") 107 byIp.Display(" ") 108 fmt.Println("Requests:") 109 byRq.Display(" ") 110 } 111 112 // 113 // countTime 114 // 115 116 type countTime struct { 117 count uint32 118 duration time.Duration 119 } 120 121 func (self *countTime) Add(dur time.Duration) { 122 self.count += 1 123 self.duration += dur 124 } 125 126 func (self countTime) Display(prefix string) { 127 fmt.Printf("%s%d requests in %v (%f req/sec)\n", prefix, self.count, self.duration, 128 float64(self.count)/self.duration.Seconds()) 129 } 130 131 // 132 // byStatus 133 // 134 135 type status = string 136 137 type byStatus map[status]*countTime 138 139 func (self byStatus) Add(status status, dur time.Duration) { 140 ct, ok := self[status] 141 if !ok { 142 ct = &countTime{} 143 self[status] = ct 144 } 145 ct.Add(dur) 146 } 147 148 func (self byStatus) Display(prefix string) { 149 sorted := make([]status, len(self)) 150 i := 0 151 for key := range self { 152 sorted[i] = key 153 i += 1 154 } 155 sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) 156 157 for _, key := range sorted { 158 val := self[key] 159 val.Display(prefix + key + ": ") 160 } 161 } 162 163 // 164 // byKeyAndStatus 165 // 166 167 type byKeyAndStatus_node struct { 168 total countTime 169 detail byStatus 170 } 171 type byKeyAndStatus map[string]*byKeyAndStatus_node 172 173 func (self byKeyAndStatus) Add(key string, status status, dur time.Duration) { 174 bk, ok := self[key] 175 if !ok { 176 bk = &byKeyAndStatus_node{detail: byStatus{}} 177 self[key] = bk 178 } 179 bk.total.Add(dur) 180 bk.detail.Add(status, dur) 181 } 182 183 func (self byKeyAndStatus) Display(prefix string) { 184 sorted := make([]string, len(self)) 185 i := 0 186 for key := range self { 187 sorted[i] = key 188 i += 1 189 } 190 sort.Slice(sorted, func(i, j int) bool { 191 return self[sorted[i]].total.duration > self[sorted[j]].total.duration 192 }) 193 194 for _, key := range sorted { 195 val := self[key] 196 val.total.Display(prefix + key + ": ") 197 val.detail.Display(prefix + " ") 198 } 199 }