From 2ce2c8fcd544edb053678744334884120f60da20 Mon Sep 17 00:00:00 2001 From: Ryan Slade Date: Thu, 27 Sep 2018 18:07:13 +0200 Subject: [PATCH] Add uastats, a tool to summarise user agents. (#31) * Add uastats, a tool to summarise user agents. uastats reads from stdin expecting one useragent per line. When the input finishes it will print a summary similar to the following: Read 319356 useragents Browsers BrowserChrome 183682 (57.52%) BrowserSafari 64062 (20.06%) BrowserIE 38184 (11.96%) BrowserFirefox 25561 (8.00%) BrowserSilk 3541 (1.11%) BrowserOpera 2543 (0.80%) BrowserAndroid 954 (0.30%) BrowserUnknown 625 (0.20%) BrowserBlackberry 132 (0.04%) BrowserSpotify 69 (0.02%) BrowserUCBrowser 3 (0.00%) Operating Systems OSWindows 143500 (44.93%) OSAndroid 84930 (26.59%) OSiOS 56519 (17.70%) OSMacOSX 24116 (7.55%) OSKindle 3878 (1.21%) OSChromeOS 2804 (0.88%) OSLinux 1629 (0.51%) OSWindowsPhone 1206 (0.38%) OSPlaystation 262 (0.08%) OSUnknown 262 (0.08%) OSXbox 125 (0.04%) OSBlackberry 118 (0.04%) OSNintendo 7 (0.00%) Device Types DeviceComputer 170833 (53.49%) DevicePhone 114783 (35.94%) DeviceTablet 32994 (10.33%) DeviceConsole 393 (0.12%) DeviceUnknown 246 (0.08%) DeviceTV 107 (0.03%) * Remove Go 1.7 from Travis. --- .travis.yml | 1 - cmd/uastats/main.go | 101 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 cmd/uastats/main.go diff --git a/.travis.yml b/.travis.yml index 77b64e6..2aef432 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ language: go go: - 1.9.x - 1.8.x - - 1.7.x script: - go test diff --git a/cmd/uastats/main.go b/cmd/uastats/main.go new file mode 100644 index 0000000..da70234 --- /dev/null +++ b/cmd/uastats/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "sort" + "text/tabwriter" + + "code.avct.io/uasurfer" +) + +func main() { + var count int + ua := &uasurfer.UserAgent{} + stats := stats{ + BrowserNames: make(map[uasurfer.BrowserName]int), + OSNames: make(map[uasurfer.OSName]int), + DeviceTypes: make(map[uasurfer.DeviceType]int), + } + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + count++ + ua.Reset() + uasurfer.ParseUserAgent(scanner.Text(), ua) + stats.BrowserNames[ua.Browser.Name]++ + stats.OSNames[ua.OS.Name]++ + stats.DeviceTypes[ua.DeviceType]++ + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading standard input:", err) + } + fmt.Printf("Read %d useragents\n", count) + fmt.Println() + stats.Summary(count, os.Stdout) +} + +type stats struct { + OSNames map[uasurfer.OSName]int + BrowserNames map[uasurfer.BrowserName]int + DeviceTypes map[uasurfer.DeviceType]int +} + +func (s *stats) Summary(total int, dest io.Writer) { + browserCounts := make([]stringCount, 0, len(s.BrowserNames)) + for k, v := range s.BrowserNames { + browserCounts = append(browserCounts, stringCount{name: k.String(), count: v}) + } + sort.Slice(browserCounts, func(i, j int) bool { return browserCounts[j].count < browserCounts[i].count }) // by count reversed + fmt.Fprintf(dest, "Browsers\n") + err := writeTable(browserCounts, total, dest) + if err != nil { + fmt.Fprintf(os.Stderr, "writing summary: %v", err) + return + } + + fmt.Fprintln(dest) + osCounts := make([]stringCount, 0, len(s.OSNames)) + for k, v := range s.OSNames { + osCounts = append(osCounts, stringCount{name: k.String(), count: v}) + } + sort.Slice(osCounts, func(i, j int) bool { return osCounts[j].count < osCounts[i].count }) // by count reversed + fmt.Fprintf(dest, "Operating Systems\n") + err = writeTable(osCounts, total, dest) + if err != nil { + fmt.Fprintf(os.Stderr, "writing summary: %v", err) + return + } + + fmt.Fprintln(dest) + deviceCounts := make([]stringCount, 0, len(s.DeviceTypes)) + for k, v := range s.DeviceTypes { + deviceCounts = append(deviceCounts, stringCount{name: k.String(), count: v}) + } + sort.Slice(deviceCounts, func(i, j int) bool { return deviceCounts[j].count < deviceCounts[i].count }) // by count reversed + fmt.Fprintf(dest, "Device Types\n") + err = writeTable(deviceCounts, total, dest) + if err != nil { + fmt.Fprintf(os.Stderr, "writing summary: %v", err) + return + } +} + +func writeTable(counts []stringCount, total int, dest io.Writer) error { + tw := tabwriter.NewWriter(dest, 10, 1, 2, ' ', 0) + for i := range counts { + fmt.Fprintf(tw, "%s\t%d (%.2f%%)\n", counts[i].name, counts[i].count, percent(counts[i].count, total)) + } + return tw.Flush() +} + +type stringCount struct { + name string + count int +} + +func percent(num, den int) float64 { + return float64(num) / float64(den) * 100.0 +}