utils.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "crypto/rand"
  6. "encoding/hex"
  7. "fmt"
  8. "image"
  9. "io"
  10. "io/ioutil"
  11. "net"
  12. "net/http"
  13. "os"
  14. "os/exec"
  15. "path/filepath"
  16. "regexp"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "github.com/codeskyblue/goreq"
  21. shellquote "github.com/kballard/go-shellquote"
  22. "github.com/openatx/androidutils"
  23. "github.com/pkg/errors"
  24. "github.com/prometheus/procfs"
  25. "github.com/shogo82148/androidbinary/apk"
  26. )
  27. // TempFileName generates a temporary filename for use in testing or whatever
  28. func TempFileName(dir, suffix string) string {
  29. randBytes := make([]byte, 16)
  30. rand.Read(randBytes)
  31. return filepath.Join(dir, hex.EncodeToString(randBytes)+suffix)
  32. }
  33. func fileExists(path string) bool {
  34. _, err := os.Stat(path)
  35. return err == nil
  36. }
  37. // Command add timeout support for os/exec
  38. type Command struct {
  39. Args []string
  40. Timeout time.Duration
  41. Shell bool
  42. ShellQuote bool
  43. Stdout io.Writer
  44. Stderr io.Writer
  45. }
  46. func NewCommand(args ...string) *Command {
  47. return &Command{
  48. Args: args,
  49. }
  50. }
  51. func (c *Command) shellPath() string {
  52. sh := os.Getenv("SHELL")
  53. if sh == "" {
  54. sh, err := exec.LookPath("sh")
  55. if err == nil {
  56. return sh
  57. }
  58. sh = "/system/bin/sh"
  59. }
  60. return sh
  61. }
  62. func (c *Command) computedArgs() (name string, args []string) {
  63. if c.Shell {
  64. var cmdline string
  65. if c.ShellQuote {
  66. cmdline = shellquote.Join(c.Args...)
  67. } else {
  68. cmdline = strings.Join(c.Args, " ") // simple, but works well with ">". eg Args("echo", "hello", ">output.txt")
  69. }
  70. args = append(args, "-c", cmdline)
  71. return c.shellPath(), args
  72. }
  73. return c.Args[0], c.Args[1:]
  74. }
  75. func (c Command) newCommand() *exec.Cmd {
  76. name, args := c.computedArgs()
  77. cmd := exec.Command(name, args...)
  78. if c.Stdout != nil {
  79. cmd.Stdout = c.Stdout
  80. }
  81. if c.Stderr != nil {
  82. cmd.Stderr = c.Stderr
  83. }
  84. return cmd
  85. }
  86. func (c Command) Run() error {
  87. cmd := c.newCommand()
  88. if c.Timeout > 0 {
  89. timer := time.AfterFunc(c.Timeout, func() {
  90. if cmd.Process != nil {
  91. cmd.Process.Kill()
  92. }
  93. })
  94. defer timer.Stop()
  95. }
  96. return cmd.Run()
  97. }
  98. func (c Command) StartBackground() (pid int, err error) {
  99. cmd := c.newCommand()
  100. err = cmd.Start()
  101. if err != nil {
  102. return
  103. }
  104. pid = cmd.Process.Pid
  105. return
  106. }
  107. func (c Command) Output() (output []byte, err error) {
  108. var b bytes.Buffer
  109. c.Stdout = &b
  110. c.Stderr = nil
  111. err = c.Run()
  112. return b.Bytes(), err
  113. }
  114. func (c Command) CombinedOutput() (output []byte, err error) {
  115. var b bytes.Buffer
  116. c.Stdout = &b
  117. c.Stderr = &b
  118. err = c.Run()
  119. return b.Bytes(), err
  120. }
  121. func (c Command) CombinedOutputString() (output string, err error) {
  122. bytesOutput, err := c.CombinedOutput()
  123. return string(bytesOutput), err
  124. }
  125. // need add timeout
  126. func runShell(args ...string) (output []byte, err error) {
  127. return Command{
  128. Args: args,
  129. Shell: true,
  130. ShellQuote: false,
  131. Timeout: 10 * time.Minute,
  132. }.CombinedOutput()
  133. }
  134. func runShellOutput(args ...string) (output []byte, err error) {
  135. return Command{
  136. Args: args,
  137. Shell: true,
  138. ShellQuote: false,
  139. Timeout: 10 * time.Minute,
  140. }.Output()
  141. }
  142. func runShellTimeout(duration time.Duration, args ...string) (output []byte, err error) {
  143. return Command{
  144. Args: args,
  145. Shell: true,
  146. Timeout: duration,
  147. }.CombinedOutput()
  148. }
  149. type fakeWriter struct {
  150. writeFunc func([]byte) (int, error)
  151. Err chan error
  152. }
  153. func (w *fakeWriter) Write(data []byte) (int, error) {
  154. n, err := w.writeFunc(data)
  155. if err != nil {
  156. select {
  157. case w.Err <- err:
  158. default:
  159. }
  160. }
  161. return n, err
  162. }
  163. func newFakeWriter(f func([]byte) (int, error)) *fakeWriter {
  164. return &fakeWriter{
  165. writeFunc: f,
  166. Err: make(chan error, 1),
  167. }
  168. }
  169. type ProcInfo struct {
  170. Pid int `json:"pid"`
  171. PPid int `json:"ppid"`
  172. NumThreads int `json:"threadCount"`
  173. Cmdline []string `json:"cmdline"`
  174. Name string `json:"name"`
  175. }
  176. // Kill by Pid
  177. func (p ProcInfo) Kill() error {
  178. process, err := os.FindProcess(p.Pid)
  179. if err != nil {
  180. return err
  181. }
  182. return process.Kill()
  183. }
  184. func listAllProcs() (ps []ProcInfo, err error) {
  185. fs, err := procfs.NewFS(procfs.DefaultMountPoint)
  186. if err != nil {
  187. return
  188. }
  189. procs, err := fs.AllProcs()
  190. if err != nil {
  191. return
  192. }
  193. for _, p := range procs {
  194. cmdline, _ := p.CmdLine()
  195. var name string
  196. if len(cmdline) == 1 {
  197. name = cmdline[0] // get package name
  198. } else {
  199. name, _ = p.Comm()
  200. }
  201. stat, _ := p.Stat()
  202. ps = append(ps, ProcInfo{
  203. Pid: p.PID,
  204. PPid: stat.PPID,
  205. Cmdline: cmdline,
  206. Name: name,
  207. NumThreads: stat.NumThreads,
  208. })
  209. }
  210. return
  211. }
  212. func findProcAll(packageName string) (procList []procfs.Proc, err error) {
  213. procs, err := procfs.AllProcs()
  214. for _, proc := range procs {
  215. cmdline, _ := proc.CmdLine()
  216. if len(cmdline) != 1 {
  217. continue
  218. }
  219. if cmdline[0] == packageName || strings.HasPrefix(cmdline[0], packageName+":") {
  220. procList = append(procList, proc)
  221. }
  222. }
  223. return
  224. }
  225. // pidof
  226. func pidOf(packageName string) (pid int, err error) {
  227. fs, err := procfs.NewFS(procfs.DefaultMountPoint)
  228. if err != nil {
  229. return
  230. }
  231. // when packageName is int
  232. pid, er := strconv.Atoi(packageName)
  233. if er == nil {
  234. _, err = fs.NewProc(pid)
  235. return
  236. }
  237. procs, err := fs.AllProcs()
  238. if err != nil {
  239. return
  240. }
  241. for _, proc := range procs {
  242. cmdline, _ := proc.CmdLine()
  243. if len(cmdline) == 1 && cmdline[0] == packageName {
  244. return proc.PID, nil
  245. }
  246. }
  247. return 0, errors.New("package not found")
  248. }
  249. type PackageInfo struct {
  250. PackageName string `json:"packageName"`
  251. MainActivity string `json:"mainActivity"`
  252. Label string `json:"label"`
  253. VersionName string `json:"versionName"`
  254. VersionCode int `json:"versionCode"`
  255. Size int64 `json:"size"`
  256. Icon image.Image `json:"-"`
  257. }
  258. func readPackageInfo(packageName string) (info PackageInfo, err error) {
  259. outbyte, err := runShell("pm", "path", packageName)
  260. lines := strings.Split(string(outbyte), "\n")
  261. if len(lines) == 0 {
  262. err = errors.New("no output received")
  263. return
  264. }
  265. output := strings.TrimSpace(lines[0])
  266. if !strings.HasPrefix(output, "package:") {
  267. err = errors.New("package " + strconv.Quote(packageName) + " not found")
  268. return
  269. }
  270. apkpath := output[len("package:"):]
  271. return readPackageInfoFromPath(apkpath)
  272. }
  273. func readPackageInfoFromPath(apkpath string) (info PackageInfo, err error) {
  274. finfo, err := os.Stat(apkpath)
  275. if err != nil {
  276. return
  277. }
  278. info.Size = finfo.Size()
  279. pkg, err := apk.OpenFile(apkpath)
  280. if err != nil {
  281. err = errors.Wrap(err, apkpath)
  282. return
  283. }
  284. defer pkg.Close()
  285. info.PackageName = pkg.PackageName()
  286. info.Label, _ = pkg.Label(nil)
  287. info.MainActivity, _ = pkg.MainActivity()
  288. info.Icon, _ = pkg.Icon(nil)
  289. info.VersionCode = int(pkg.Manifest().VersionCode.MustInt32())
  290. info.VersionName = pkg.Manifest().VersionName.MustString()
  291. return
  292. }
  293. func procWalk(fn func(p procfs.Proc)) error {
  294. fs, err := procfs.NewFS(procfs.DefaultMountPoint)
  295. if err != nil {
  296. return err
  297. }
  298. procs, err := fs.AllProcs()
  299. for _, proc := range procs {
  300. fn(proc)
  301. }
  302. return nil
  303. }
  304. // get main activity with packageName
  305. func mainActivityOf(packageName string) (activity string, err error) {
  306. output, err := runShellOutput("pm", "list", "packages", "-f", packageName)
  307. if err != nil {
  308. log.Println("pm list err:", err)
  309. return
  310. }
  311. matches := regexp.MustCompile(`package:(.+)=([.\w]+)`).FindAllStringSubmatch(string(output), -1)
  312. for _, match := range matches {
  313. if match[2] != packageName {
  314. continue
  315. }
  316. pkg, err := apk.OpenFile(match[1])
  317. if err != nil {
  318. return "", err
  319. }
  320. return pkg.MainActivity()
  321. }
  322. return "", errors.New("package not found")
  323. }
  324. // download minicap or minitouch apk, etc...
  325. func httpDownload(path string, urlStr string, perms os.FileMode) (written int64, err error) {
  326. resp, err := goreq.Request{
  327. Uri: urlStr,
  328. RedirectHeaders: true,
  329. MaxRedirects: 10,
  330. }.Do()
  331. if err != nil {
  332. return
  333. }
  334. defer resp.Body.Close()
  335. if resp.StatusCode != http.StatusOK {
  336. err = fmt.Errorf("http download <%s> status %v", urlStr, resp.Status)
  337. return
  338. }
  339. file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, perms)
  340. if err != nil {
  341. return
  342. }
  343. defer file.Close()
  344. written, err = io.Copy(file, resp.Body)
  345. log.Println("http download:", written)
  346. return
  347. }
  348. func hijackHTTPRequest(w http.ResponseWriter) (conn net.Conn, err error) {
  349. hj, ok := w.(http.Hijacker)
  350. if !ok {
  351. err = errors.New("webserver don't support hijacking")
  352. return
  353. }
  354. hjconn, bufrw, err := hj.Hijack()
  355. if err != nil {
  356. return nil, err
  357. }
  358. conn = newHijackReadWriteCloser(hjconn.(*net.TCPConn), bufrw)
  359. return
  360. }
  361. type hijactRW struct {
  362. *net.TCPConn
  363. bufrw *bufio.ReadWriter
  364. }
  365. func (this *hijactRW) Write(data []byte) (int, error) {
  366. nn, err := this.bufrw.Write(data)
  367. this.bufrw.Flush()
  368. return nn, err
  369. }
  370. func (this *hijactRW) Read(p []byte) (int, error) {
  371. return this.bufrw.Read(p)
  372. }
  373. func newHijackReadWriteCloser(conn *net.TCPConn, bufrw *bufio.ReadWriter) net.Conn {
  374. return &hijactRW{
  375. bufrw: bufrw,
  376. TCPConn: conn,
  377. }
  378. }
  379. func getCachedProperty(name string) string {
  380. return androidutils.CachedProperty(name)
  381. }
  382. func getProperty(name string) string {
  383. return androidutils.Property(name)
  384. }
  385. func copyToFile(rd io.Reader, dst string) error {
  386. fd, err := os.Create(dst)
  387. if err != nil {
  388. return err
  389. }
  390. defer fd.Close()
  391. _, err = io.Copy(fd, rd)
  392. return err
  393. }
  394. // parse output: dumpsys meminfo --local ${pkgname}
  395. // If everything is going, returns json, unit KB
  396. //
  397. // {
  398. // "code": 58548,
  399. // "graphics": 73068,
  400. // "java heap": 160332,
  401. // "native heap": 67708,
  402. // "private Other": 34976,
  403. // "stack": 4728,
  404. // "system": 8288,
  405. // "total": 407648
  406. // }
  407. func parseMemoryInfo(nameOrPid string) (info map[string]int, err error) {
  408. output, err := Command{
  409. Args: []string{"dumpsys", "meminfo", "--local", nameOrPid},
  410. Timeout: 10 * time.Second,
  411. }.CombinedOutputString()
  412. if err != nil {
  413. return
  414. }
  415. index := strings.Index(output, "App Summary")
  416. if index == -1 {
  417. err = errors.New("dumpsys meminfo has no [App Summary]")
  418. return
  419. }
  420. re := regexp.MustCompile(`(\w[\w ]+):\s*(\d+)`)
  421. matches := re.FindAllStringSubmatch(output[index:], -1)
  422. if len(matches) == 0 {
  423. err = errors.New("Invalid dumpsys meminfo output")
  424. return
  425. }
  426. info = make(map[string]int, len(matches))
  427. for _, m := range matches {
  428. key := strings.ToLower(m[1])
  429. val, _ := strconv.Atoi(m[2])
  430. info[key] = val
  431. }
  432. return
  433. }
  434. type CPUStat struct {
  435. Pid int
  436. SystemTotal uint
  437. SystemIdle uint
  438. ProcUser uint
  439. ProcSystem uint
  440. UpdateTime time.Time
  441. }
  442. func NewCPUStat(pid int) (stat *CPUStat, err error) {
  443. stat = &CPUStat{Pid: pid}
  444. err = stat.Update()
  445. return
  446. }
  447. func (c *CPUStat) String() string {
  448. return fmt.Sprintf("CPUStat(pid:%d, systotal:%d, sysidle:%d, user:%d, system:%d",
  449. c.Pid, c.SystemTotal, c.SystemIdle, c.ProcUser, c.ProcSystem)
  450. }
  451. // CPUPercent may > 100.0 if process have multi thread on multi cores
  452. func (c *CPUStat) CPUPercent(last *CPUStat) float64 {
  453. pjiff1 := last.ProcUser + last.ProcSystem
  454. pjiff2 := c.ProcUser + c.ProcSystem
  455. duration := c.SystemTotal - last.SystemTotal
  456. if duration <= 0 {
  457. return 0.0
  458. }
  459. percent := 100.0 * float64(pjiff2-pjiff1) / float64(duration)
  460. if percent < 0.0 {
  461. log.Println("Warning: cpu percent < 0", percent)
  462. percent = 0
  463. }
  464. return percent
  465. }
  466. func (c *CPUStat) SystemCPUPercent(last *CPUStat) float64 {
  467. idle := c.SystemIdle - last.SystemIdle
  468. jiff := c.SystemTotal - last.SystemTotal
  469. percent := 100.0 * float64(idle) / float64(jiff)
  470. return percent
  471. }
  472. // Update proc jiffies data
  473. func (c *CPUStat) Update() error {
  474. // retrive /proc/<pid>/stat
  475. fs, err := procfs.NewFS(procfs.DefaultMountPoint)
  476. if err != nil {
  477. return err
  478. }
  479. proc, err := fs.NewProc(c.Pid)
  480. if err != nil {
  481. return err
  482. }
  483. stat, err := proc.NewStat()
  484. if err != nil {
  485. return errors.Wrap(err, "read /proc/<pid>/stat")
  486. }
  487. // retrive /proc/stst
  488. statData, err := ioutil.ReadFile("/proc/stat")
  489. if err != nil {
  490. return errors.Wrap(err, "read /proc/stat")
  491. }
  492. procStat := string(statData)
  493. idx := strings.Index(procStat, "\n")
  494. // cpuName, user, nice, system, idle, iowait, irq, softIrq, steal, guest, guestNice
  495. fields := strings.Fields(procStat[:idx])
  496. if fields[0] != "cpu" {
  497. return errors.New("/proc/stat not startswith cpu")
  498. }
  499. var total, idle uint
  500. for i, raw := range fields[1:] {
  501. var v uint
  502. fmt.Sscanf(raw, "%d", &v)
  503. if i == 3 { // idle
  504. idle = v
  505. }
  506. total += v
  507. }
  508. c.ProcSystem = stat.STime
  509. c.ProcUser = stat.UTime
  510. c.SystemTotal = total
  511. c.SystemIdle = idle
  512. c.UpdateTime = time.Now()
  513. return nil
  514. }
  515. var cpuStats = make(map[int]*CPUStat)
  516. var _cpuCoreCount int
  517. // CPUCoreCount return 0 if retrive failed
  518. func CPUCoreCount() int {
  519. if _cpuCoreCount != 0 {
  520. return _cpuCoreCount
  521. }
  522. fs, err := procfs.NewFS(procfs.DefaultMountPoint)
  523. if err != nil {
  524. return 0
  525. }
  526. stat, err := fs.NewStat()
  527. if err != nil {
  528. return 0
  529. }
  530. _cpuCoreCount = len(stat.CPU)
  531. return _cpuCoreCount
  532. }
  533. type CPUInfo struct {
  534. Pid int `json:"pid"`
  535. User uint `json:"user"`
  536. System uint `json:"system"`
  537. Percent float64 `json:"percent"`
  538. SystemPercent float64 `json:"systemPercent"`
  539. CoreCount int `json:"coreCount"`
  540. }
  541. func readCPUInfo(pid int) (info CPUInfo, err error) {
  542. last, ok := cpuStats[pid]
  543. if !ok || // need fresh history data
  544. last.UpdateTime.Add(5*time.Second).Before(time.Now()) {
  545. last, err = NewCPUStat(pid)
  546. if err != nil {
  547. return
  548. }
  549. time.Sleep(100 * time.Millisecond)
  550. log.Println("Update data")
  551. }
  552. stat, err := NewCPUStat(pid)
  553. if err != nil {
  554. return
  555. }
  556. cpuStats[pid] = stat
  557. info.Pid = pid
  558. info.User = stat.ProcUser
  559. info.System = stat.ProcSystem
  560. info.Percent = stat.CPUPercent(last)
  561. info.SystemPercent = stat.SystemCPUPercent(last)
  562. info.CoreCount = CPUCoreCount()
  563. return
  564. }
  565. func dumpHierarchy() (xmlContent string, err error) {
  566. const targetPath = "/sdcard/window_dump.xml"
  567. c := &Command{
  568. Args: []string{"uiautomator", "dump", targetPath},
  569. Shell: true,
  570. }
  571. if err = c.Run(); err != nil {
  572. return
  573. }
  574. data, err := ioutil.ReadFile(targetPath)
  575. xmlContent = string(data)
  576. return
  577. }
  578. func listPackages() (pkgs []PackageInfo, err error) {
  579. c := NewCommand("pm", "list", "packages", "-f", "-3")
  580. c.Shell = true
  581. output, err := c.CombinedOutputString()
  582. if err != nil {
  583. return
  584. }
  585. for _, line := range strings.Split(output, "\n") {
  586. if !strings.HasPrefix(line, "package:") {
  587. continue
  588. }
  589. matches := regexp.MustCompile(`^package:(/.+)=([^=]+)$`).FindStringSubmatch(line)
  590. if len(matches) == 0 {
  591. continue
  592. }
  593. pkgPath := matches[1]
  594. pkgName := matches[2]
  595. pkgInfo, er := readPackageInfoFromPath(pkgPath)
  596. if er != nil {
  597. log.Printf("Read package %s error %v", pkgName, er)
  598. continue
  599. }
  600. pkgs = append(pkgs, pkgInfo)
  601. }
  602. return
  603. }
  604. func killProcessByName(processName string) bool {
  605. procs, err := procfs.AllProcs()
  606. if err != nil {
  607. return false
  608. }
  609. killed := false
  610. for _, p := range procs {
  611. cmdline, _ := p.CmdLine()
  612. var name string
  613. if len(cmdline) >= 1 {
  614. name = filepath.Base(cmdline[0])
  615. } else {
  616. name, _ = p.Comm()
  617. }
  618. if name == processName {
  619. process, err := os.FindProcess(p.PID)
  620. if err == nil {
  621. process.Kill()
  622. killed = true
  623. }
  624. }
  625. }
  626. return killed
  627. }
  628. func getPackagePath(packageName string) (string, error) {
  629. pmPathOutput, err := Command{
  630. Args: []string{"pm", "path", "com.github.uiautomator"},
  631. Shell: true,
  632. }.CombinedOutputString()
  633. if err != nil {
  634. return "", err
  635. }
  636. if !strings.HasPrefix(pmPathOutput, "package:") {
  637. return "", errors.New("invalid pm path output: " + pmPathOutput)
  638. }
  639. packagePath := strings.TrimSpace(pmPathOutput[len("package:"):])
  640. return packagePath, nil
  641. }