initial commit
This commit is contained in:
commit
5f00e5efb6
|
@ -0,0 +1,4 @@
|
||||||
|
their
|
||||||
|
*.log
|
||||||
|
*.csv
|
||||||
|
lines.txt
|
|
@ -0,0 +1,175 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogLine struct {
|
||||||
|
Time time.Time
|
||||||
|
TimeTook time.Duration
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bucket struct {
|
||||||
|
Time time.Time // only resolved to the minute
|
||||||
|
Entries []*LogLine
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateFormat = "2006-01-02T15:04:05.999999Z07:00"
|
||||||
|
const outDateFormat = "2006-01-02T15:04:05"
|
||||||
|
const outDateFormatNoSeconds = "2006-01-02T15:04"
|
||||||
|
|
||||||
|
func ParseLogLine(line []byte) (*LogLine, error) {
|
||||||
|
sl := bytes.Split(line, []byte(" "))
|
||||||
|
|
||||||
|
date := sl[0]
|
||||||
|
lTime, err := time.Parse(dateFormat, string(date))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sdur time.Duration
|
||||||
|
var host string
|
||||||
|
|
||||||
|
for i, section := range sl {
|
||||||
|
switch i {
|
||||||
|
case 0, 1:
|
||||||
|
continue // not key->value pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
set := bytes.Split(section, []byte("="))
|
||||||
|
if len(set) != 2 {
|
||||||
|
log.Printf("invalid: %v", set)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
k := string(set[0])
|
||||||
|
v := set[1]
|
||||||
|
|
||||||
|
if k == "service" {
|
||||||
|
dur, err := time.ParseDuration(string(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sdur = dur
|
||||||
|
}
|
||||||
|
|
||||||
|
if k == "host" {
|
||||||
|
host = Shuck(string(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ll := &LogLine{
|
||||||
|
Time: lTime,
|
||||||
|
TimeTook: sdur,
|
||||||
|
Host: host,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ll, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuck removes the first and last character of a string, analogous to
|
||||||
|
// shucking off the husk of an ear of corn.
|
||||||
|
func Shuck(victim string) string {
|
||||||
|
return victim[1 : len(victim)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
|
var lastTime time.Time
|
||||||
|
|
||||||
|
// active buckets, short var name because it's going to be referenced a lot
|
||||||
|
ab := map[string]*Bucket{}
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
|
||||||
|
ll, err := ParseLogLine(line)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastTime.IsZero() {
|
||||||
|
year, month, day := ll.Time.Date()
|
||||||
|
hour, minute, _ := ll.Time.Clock()
|
||||||
|
lastTime = time.Date(year, month, day, hour, minute, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// last line minutes
|
||||||
|
_, llm, _ := ll.Time.Clock()
|
||||||
|
_, ltm, _ := lastTime.Clock()
|
||||||
|
|
||||||
|
if llm != ltm {
|
||||||
|
processBuckets(ab)
|
||||||
|
ab = map[string]*Bucket{}
|
||||||
|
|
||||||
|
lastTime = lastTime.Add(5 * time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ab[ll.Host]
|
||||||
|
if b == nil {
|
||||||
|
year, month, day := ll.Time.Date()
|
||||||
|
hour, minute, _ := ll.Time.Clock()
|
||||||
|
b = &Bucket{
|
||||||
|
Time: time.Date(year, month, day, hour, minute, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
ab[ll.Host] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Entries = append(b.Entries, ll)
|
||||||
|
|
||||||
|
ab[ll.Host] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
processBuckets(ab)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sees if the minute and hour field of a == the minute and hour field of b
|
||||||
|
func cmpTime(a, b time.Time) bool {
|
||||||
|
_, am, _ := a.Clock()
|
||||||
|
_, bm, _ := b.Clock()
|
||||||
|
|
||||||
|
return am == bm
|
||||||
|
}
|
||||||
|
|
||||||
|
func toMS(dur time.Duration) int64 {
|
||||||
|
return dur.Nanoseconds() / 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
func processBuckets(set map[string]*Bucket) {
|
||||||
|
hosts := []string{}
|
||||||
|
for host, _ := range set {
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sort.StringSlice(hosts))
|
||||||
|
|
||||||
|
for _, host := range hosts {
|
||||||
|
bucket := set[host]
|
||||||
|
var longest time.Duration
|
||||||
|
var shortest time.Duration
|
||||||
|
shortest = 9999 * time.Minute // make this absurdly large so everything is smaller
|
||||||
|
var total time.Duration
|
||||||
|
|
||||||
|
for _, entry := range bucket.Entries {
|
||||||
|
switch true {
|
||||||
|
case entry.TimeTook > longest:
|
||||||
|
longest = entry.TimeTook
|
||||||
|
case entry.TimeTook < shortest:
|
||||||
|
shortest = entry.TimeTook
|
||||||
|
}
|
||||||
|
|
||||||
|
total += entry.TimeTook
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s,%s,%d,%d,%d,%d\n", bucket.Time.Format(outDateFormat), host, len(bucket.Entries), toMS(total), toMS(shortest), toMS(longest))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kr/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseLogLine(t *testing.T) {
|
||||||
|
const line = `2016-05-07T09:07:00.001490+00:00 heroku[router]: at=info method=GET path="/blog" host="brs.org" request_id=fc693802-8851-484e-aab4-1d013714b68b fwd="10.29.10.29" dyno=web.3 connect=2ms service=994ms status=200 bytes=552`
|
||||||
|
|
||||||
|
ll, err := ParseLogLine([]byte(line))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ms := (ll.TimeTook.Nanoseconds() / 1000000) // ns -> ms == div 1e6
|
||||||
|
if ms != 996 {
|
||||||
|
t.Fatalf("invalid time took for this line")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ll.Host != "brs.org" {
|
||||||
|
pretty.Println(ll)
|
||||||
|
|
||||||
|
t.Fatal("invalid host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCmpTime(t *testing.T) {
|
||||||
|
now := time.Date(2016, time.January, 1, 13, 37, 0, 0, time.UTC)
|
||||||
|
then := now.Add(5 * time.Minute)
|
||||||
|
|
||||||
|
if cmpTime(then, now) {
|
||||||
|
t.Fatal("cmpTime error")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue