├── README.md ├── .gitignore ├── main.go ├── adb.go └── cpu.go /README.md: -------------------------------------------------------------------------------- 1 | # perfdroid -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | var packageName *string 10 | var timeInterval *int 11 | 12 | func main() { 13 | parseArgs() 14 | 15 | err := SetupAdb(*packageName) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | fmt.Println("Package:", *packageName) 21 | for { 22 | usage := CpuUsage(*packageName, *timeInterval) 23 | fmt.Println("User:", usage.user) 24 | fmt.Println("System:", usage.system) 25 | } 26 | 27 | } 28 | 29 | func parseArgs() { 30 | packageName = flag.String("package", "", "package to monitor") 31 | timeInterval = flag.Int("timeInterval", 1, "Time interval in which to gather data (in seconds)") 32 | flag.Parse() 33 | } 34 | -------------------------------------------------------------------------------- /adb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os/exec" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | var adbPath string 13 | 14 | func SetupAdb(packageName string) error { 15 | path, err := exec.LookPath("adb") 16 | if err != nil { 17 | return err 18 | } 19 | adbPath = path 20 | return nil 21 | } 22 | 23 | func AdbPath() string { 24 | return adbPath 25 | } 26 | 27 | func ExecAdb(arg ...string) string { 28 | cmd := exec.Command(AdbPath(), append([]string{"shell"}, arg...)...) 29 | stdout, err := cmd.StdoutPipe() 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | err = cmd.Start() 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | readBytes, err := ioutil.ReadAll(stdout) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | return string(readBytes) 42 | } 43 | 44 | func Pid(packageName string) (uint64, error) { 45 | out := ExecAdb("ps") 46 | lines := strings.Split(out, "\n") 47 | for _, line := range lines { 48 | if strings.Contains(line, packageName) { 49 | fields := strings.Fields(line) 50 | val, err := strconv.ParseUint(fields[1], 10, 64) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | return val, nil 55 | } 56 | } 57 | return 0, fmt.Errorf("Target app (%s) is not running", packageName) 58 | } 59 | -------------------------------------------------------------------------------- /cpu.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type cpuUsage struct { 12 | utime uint64 13 | stime uint64 14 | } 15 | 16 | type CpuMetric struct { 17 | user float64 18 | system float64 19 | } 20 | 21 | func totalCpuTime() uint64 { 22 | out := ExecAdb("cat", "/proc/stat") 23 | lines := strings.Split(out, "\n") 24 | for _, line := range lines { 25 | fields := strings.Fields(line) 26 | if fields[0] == "cpu" { 27 | var sum uint64 = 0 28 | arr := fields[1:] 29 | for _, num := range arr { 30 | val, err := strconv.ParseUint(num, 10, 64) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | sum += val 35 | } 36 | return sum 37 | } 38 | } 39 | log.Fatal("No CPU fields found") 40 | return 0 41 | } 42 | 43 | func appCpuUsage(pid uint64) cpuUsage { 44 | // TODO: App might be killed by the time this is called, which will cause a crash 45 | out := ExecAdb("cat", fmt.Sprintf("/proc/%v/stat", pid)) 46 | fields := strings.Fields(out) 47 | utime, err := strconv.ParseUint(fields[13], 10, 64) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | stime, err := strconv.ParseUint(fields[14], 10, 64) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | return cpuUsage{utime: utime, stime: stime} 56 | } 57 | 58 | func CpuUsage(packageName string, timeInterval int) CpuMetric { 59 | // PID is computed everytime because PID can change if app is killed or screen is locked 60 | pid, err := Pid(packageName) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | oldTotalTime := totalCpuTime() 65 | oldAppTime := appCpuUsage(pid) 66 | time.Sleep(timeInterval * time.Second) 67 | newTotalTime := totalCpuTime() 68 | newAppTime := appCpuUsage(pid) 69 | user := float64(newAppTime.utime-oldAppTime.utime) / float64(newTotalTime-oldTotalTime) * 100 70 | system := float64(newAppTime.stime-oldAppTime.stime) / float64(newTotalTime-oldTotalTime) * 100 71 | return CpuMetric{user: user, system: system} 72 | } 73 | --------------------------------------------------------------------------------