Skip to content

Commit

Permalink
Add uastats, a tool to summarise user agents. (avct#31)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
ryanslade authored Sep 27, 2018
1 parent dc0ec4f commit 2ce2c8f
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 1 deletion.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ language: go
go:
- 1.9.x
- 1.8.x
- 1.7.x

script:
- go test
101 changes: 101 additions & 0 deletions cmd/uastats/main.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 2ce2c8f

Please sign in to comment.