// extractSensorInfo extracts device sensor information found in the sensorservice dump of a bug report. func extractSensorInfo(input string) (map[int32]SensorInfo, error) { inSSection := false sensors := make(map[int32]SensorInfo) Loop: for _, line := range strings.Split(input, "\n") { if m, result := historianutils.SubexpNames(historianutils.ServiceDumpRE, line); m { switch in := result["service"] == "sensorservice"; { case inSSection && !in: // Just exited the section break Loop case in: inSSection = true continue Loop default: // Random section continue Loop } } if !inSSection { continue } if m, result := historianutils.SubexpNames(sensorLineRE, line); m { n, err := strconv.ParseInt(result["sensorNumber"], 0, 32) if err != nil { return nil, err } v := 0 if x := result["versionNumber"]; x != "" { v, err = strconv.Atoi(x) if err != nil { return nil, err } } sensors[int32(n)] = SensorInfo{ Name: result["sensorName"], Number: int32(n), Type: result["sensorTypeString"], Version: int32(v), } } } sensors[GPSSensorNumber] = SensorInfo{ Name: "GPS", Number: GPSSensorNumber, } return sensors, nil }
// AppIDFromString returns the appID (or base uid) for a given uid, stripping out the user id from it. // (ie. "10001" -> 10001,nil; "u0a25" -> 10025,nil; "text" -> 0,error func AppIDFromString(uid string) (int32, error) { // The empty string is a valid/expected value to pass through here. if uid == "" { return 0, nil } if m, result := historianutils.SubexpNames(abrUIDRE, uid); m { i, err := strconv.Atoi(result["appId"]) if err != nil { return 0, fmt.Errorf("error getting appID from string: %v", err) } // These are types defined and formatted in frameworks/base/core/java/android/os/UserHandle.java switch result["aidType"] { case "i": // Isolated UID return int32(i) + firstIsolatedUID, nil case "a": // appId >= FirstApplicationUID return int32(i) + FirstApplicationUID, nil case "s": // Unmodified appID return int32(i), nil default: return int32(i), fmt.Errorf("unknown appIdType: %s", result["aidType"]) } } i, err := strconv.Atoi(uid) if err != nil { return 0, fmt.Errorf("error getting appID from string: %v", err) } return AppID(int32(i)), nil }
// ExtractBatterystatsCheckin extracts and returns only the lines in // input that are included in the "CHECKIN BATTERYSTATS" section. func ExtractBatterystatsCheckin(input string) string { inBsSection := false var bsCheckin []string Loop: for _, line := range strings.Split(input, "\n") { line = strings.TrimSpace(line) if m, result := historianutils.SubexpNames(bugReportSectionRE, line); m { switch in := strings.Contains(result["section"], "CHECKIN BATTERYSTATS"); { case inBsSection && !in: // Just exited the section break Loop case in: inBsSection = true continue Loop default: // Random section continue Loop } } if inBsSection { bsCheckin = append(bsCheckin, line) } } return strings.Join(bsCheckin, "\n") }
// TimeZone extracts the time zone from a bug report. func TimeZone(contents string) (*time.Location, error) { for _, line := range strings.Split(contents, "\n") { if m, result := historianutils.SubexpNames(TimeZoneRE, line); m { return time.LoadLocation(result["timezone"]) } } return nil, errors.New("missing time zone line in bug report") }
// Parse writes a csv entry for each line in the kernel log file, and returns whether the format was valid. func Parse(f string) (bool, string, []error) { var errs []error var buf bytes.Buffer csvState := csv.NewState(&buf, false) activeMap := make(map[string]*entry) curTime := int64(0) startTime := int64(0) matched := false for _, l := range strings.Split(f, "\n") { if matches, result := historianutils.SubexpNames(WakeSourceRE, l); matches { timestamp, err := bugreportutils.TimeStampToMs(result["timeStamp"], result["remainder"], time.UTC) if err != nil { errs = append(errs, err) continue } matched = true if startTime == 0 { startTime = timestamp } curTime = timestamp t := result["transitionType"] v := result["value"] e, alreadyActive := activeMap[v] switch t { case PositiveTransition: if !alreadyActive { e = &entry{timestamp, v} activeMap[v] = e } else { // Double positive transition. Ignore the event. errs = append(errs, fmt.Errorf("two positive transitions for %q, wakesource %q", KernelWakeSource, v)) continue } case NegativeTransition: if !alreadyActive { errs = append(errs, fmt.Errorf("negative transition without positive transition for %q, wakesource %q", KernelWakeSource, v)) continue } delete(activeMap, v) default: errs = append(errs, fmt.Errorf("unknown transition for %q %q", KernelWakeSource, t)) continue } csvState.AddEntry(KernelWakeSource, e, curTime) } } csvState.PrintAllReset(curTime) return matched, buf.String(), errs }
// ParseMetaInfo extracts the device ID, build fingerprint and model name from the bug report. func ParseMetaInfo(input string) (*MetaInfo, error) { var deviceID, buildFingerprint, modelName string sdkVersion := -1 for _, line := range strings.Split(input, "\n") { if match, result := historianutils.SubexpNames(deviceIDRE, line); match { deviceID = result["deviceID"] } else if match, result := historianutils.SubexpNames(sdkVersionRE, line); match { sdk, err := strconv.Atoi(result["sdkVersion"]) if err != nil { return nil, err } sdkVersion = sdk } else if match, result := historianutils.SubexpNames(buildFingerprintRE, line); match && buildFingerprint == "" { // Only the first instance of this line in the bug report is guaranteed to be correct. // All following instances may be wrong, so we ignore them. buildFingerprint = result["build"] } else if match, result := historianutils.SubexpNames(modelNameRE, line); match { modelName = result["modelName"] } if deviceID != "" && buildFingerprint != "" && sdkVersion != -1 && modelName != "" { break } } if sdkVersion == -1 { return nil, errors.New("unable to find device SDK version") } if deviceID == "" { deviceID = "not available" } if modelName == "" { modelName = "unknown device" } sensors, err := extractSensorInfo(input) return &MetaInfo{ DeviceID: deviceID, SdkVersion: sdkVersion, BuildFingerprint: buildFingerprint, ModelName: modelName, Sensors: sensors, }, err }
// extractAppsFromAppOpsDump looks at the app ops service dump from a bug report // and extracts package names and their UIDs from the dump. It returns a mapping of // the package name to the PackageInfo object. func extractAppsFromAppOpsDump(s string) (map[string]*usagepb.PackageInfo, []error) { pkgs := make(map[string]*usagepb.PackageInfo) var errs []error inAppOpsSection := false var curUID int32 var err error Loop: for _, line := range strings.Split(s, "\n") { line = strings.TrimSpace(line) if m, result := historianutils.SubexpNames(historianutils.ServiceDumpRE, line); m { switch in := result["service"] == "appops"; { case inAppOpsSection && !in: // Just exited the App Ops section break Loop case in: inAppOpsSection = true continue default: // Random section continue } } if !inAppOpsSection { continue } if m, result := historianutils.SubexpNames(uidRE, line); m { curUID, err = AppIDFromString(result["uid"]) if err != nil { errs = append(errs, err) } } if m, result := historianutils.SubexpNames(appOpsPackageRE, line); m { pkg := result["package"] pkgs[pkg] = &usagepb.PackageInfo{ PkgName: proto.String(pkg), Uid: proto.Int32(curUID), } } } return pkgs, errs }
// DumpState returns the parsed dumpstate information as a time object. func DumpState(contents string) (time.Time, error) { loc, err := TimeZone(contents) if err != nil { return time.Time{}, err } for _, line := range strings.Split(contents, "\n") { if m, result := historianutils.SubexpNames(DumpstateRE, line); m { d, err := time.ParseInLocation(TimeLayout, strings.TrimSpace(result["timestamp"]), loc) if err != nil { return time.Time{}, err } return d, nil } } return time.Time{}, errors.New("could not find dumpstate information in bugreport") }
// ExtractPIDMappings returns mappings from PID to app names and UIDs extracted from the bug report. func ExtractPIDMappings(contents string) (map[string][]AppInfo, []string) { var warnings []string mapping := make(map[string][]AppInfo) for _, line := range strings.Split(contents, "\n") { if m, result := historianutils.SubexpNames(pidRE, line); m { baseUID, err := packageutils.AppIDFromString(result["uid"]) uidStr := strconv.Itoa(int(baseUID)) if err != nil { uidStr = "" warnings = append(warnings, fmt.Sprintf("invalid uid: %s", result["uid"])) } mapping[result["pid"]] = append(mapping[result["pid"]], AppInfo{ Name: result["app"], UID: uidStr, }) } } return mapping, warnings }
// extractAppsFromPackageDump looks at the package service dump from a bug report // and extracts as much application info from the dump. It returns a mapping of // the package name to the PackageInfo object. func extractAppsFromPackageDump(s string) (map[string]*usagepb.PackageInfo, []error) { pkgs := make(map[string]*usagepb.PackageInfo) var errs []error var inPackageDumpSection, inCurrentSection bool var curPkg *usagepb.PackageInfo Loop: for _, line := range strings.Split(s, "\n") { line = strings.TrimSpace(line) if m, result := historianutils.SubexpNames(historianutils.ServiceDumpRE, line); m { switch in := result["service"] == "package"; { case inPackageDumpSection && !in: // Just exited package dump section break Loop case in: inPackageDumpSection = true continue default: // Random section continue } } if !inPackageDumpSection { continue } switch line { case "Packages:": inCurrentSection = true continue case "Hidden system packages:": inCurrentSection = false break Loop } if !inCurrentSection { continue } if m, result := historianutils.SubexpNames(packageDumpPackageRE, line); m { if curPkg != nil { pkgs[curPkg.GetPkgName()] = curPkg } curPkg = &usagepb.PackageInfo{ PkgName: proto.String(result["package"]), } } else if m, result := historianutils.SubexpNames(packageDumpCompatRE, line); m { if curPkg == nil { errs = append(errs, errors.New("found compat line before package line")) continue } curPkg.PkgName = proto.String(result["package"]) } else if m, result := historianutils.SubexpNames(userIDRE, line); m { if curPkg == nil { errs = append(errs, errors.New("found userId line before package line")) continue } uid, err := AppIDFromString(result["uid"]) if err != nil { errs = append(errs, err) } curPkg.Uid = proto.Int32(uid) } else if m, result := historianutils.SubexpNames(packageDumpVersionCodeRE, line); m { if curPkg == nil { errs = append(errs, errors.New("found versionCode line before package line")) continue } vc, err := strconv.Atoi(result["versionCode"]) if err != nil { errs = append(errs, fmt.Errorf("error getting version code from string: %v\n", err)) continue } curPkg.VersionCode = proto.Int32(int32(vc)) } else if m, result := historianutils.SubexpNames(packageDumpVersionNameRE, line); m { if curPkg == nil { errs = append(errs, errors.New("found versionName line before package line")) continue } curPkg.VersionName = proto.String(result["versionName"]) } else if m, result := historianutils.SubexpNames(firstInstallTimeRE, line); m { if curPkg == nil { errs = append(errs, errors.New("found firstInstallTime line before package line")) continue } t, err := time.Parse(timeFormat, result["time"]) if err != nil { errs = append(errs, err) } curPkg.FirstInstallTime = proto.Int64(t.UnixNano() / int64(time.Millisecond)) } else if m, result := historianutils.SubexpNames(lastUpdateTimeRE, line); m { if curPkg == nil { errs = append(errs, errors.New("found lastUpdateTime line before package line")) continue } t, err := time.Parse(timeFormat, result["time"]) if err != nil { errs = append(errs, err) } curPkg.LastUpdateTime = proto.Int64(t.UnixNano() / int64(time.Millisecond)) } else if m, result := historianutils.SubexpNames(packageDumpSharedUserRE, line); m { if curPkg == nil { errs = append(errs, errors.New("found sharedUser line before package line")) continue } uid, err := AppIDFromString(result["uid"]) if err != nil { errs = append(errs, err) } if curPkg.GetUid() != uid { errs = append(errs, errors.New("sharedUser uid is different from package uid")) continue } curPkg.SharedUserId = proto.String(result["label"]) } } if curPkg != nil { pkgs[curPkg.GetPkgName()] = curPkg } return pkgs, errs }
// Parse writes a CSV entry for each line matching activity manager proc start and died, ANR and low memory events. // Package info is used to match crash events to UIDs. Errors encountered during parsing will be collected into an errors slice and will continue parsing remaining events. func Parse(pkgs []*usagepb.PackageInfo, f string) (string, []string, []error) { p, warnings, err := newParser(f) if err != nil { return "", nil, []error{err} } var errs []error crashSource := "" for _, line := range strings.Split(f, "\n") { m, result := historianutils.SubexpNames(logEntryRE, line) if !m { continue } timestamp, err := p.fullTimestamp(result["month"], result["day"], result["timeStamp"], result["remainder"]) if err != nil { errs = append(errs, err) continue } details := result["details"] pid := result["pid"] if m, _ = historianutils.SubexpNames(bluetoothScanRE, details); m { p.parseBluetoothScan(timestamp, pid) continue } if m, result = historianutils.SubexpNames(crashStartRE, details); m { crashSource = result["source"] continue } if m, result = historianutils.SubexpNames(crashProcessRE, details); m && crashSource != "" { var uid string pkg, err := packageutils.GuessPackage(result["process"], "", pkgs) if err != nil { errs = append(errs, err) // Still want to show the crash event even if there was an error matching a package. } else if pkg != nil { uid = fmt.Sprintf("%d", pkg.GetUid()) } p.csvState.PrintInstantEvent(csv.Entry{ Desc: "Crashes", Start: timestamp, Type: "service", Value: fmt.Sprintf("%s: %s", result["process"], crashSource), Opt: uid, }) crashSource = "" continue } m, result = historianutils.SubexpNames(activityManagerRE, details) if !m { // Non matching lines are ignored but not considered errors. continue } t := result["transitionType"] // Format of the value is defined at frameworks/base/services/core/java/com/android/server/am/EventLogTags.logtags. v := result["value"] switch t { case LowMemoryEvent: p.parseLowMemory(timestamp, v) case ANREvent: warning, err := p.parseANR(pkgs, timestamp, v) if err != nil { errs = append(errs, err) } if warning != "" { warnings = append(warnings, warning) } case ProcStartEvent, ProcDiedEvent: warning, err := p.parseProc(timestamp, v, t) if err != nil { errs = append(errs, err) } if warning != "" { warnings = append(warnings, warning) } default: errs = append(errs, fmt.Errorf("unknown transition for %q: %q", AMProc, t)) } } // If there was no corresponding am_proc_died event, set the end time to 0. p.csvState.PrintAllReset(0) return p.buf.String(), warnings, errs }
// Parse writes a CSV entry for each line in the powermonitor file, and returns whether the format was valid. func Parse(f string) (bool, string, []error) { var errs []error var buf bytes.Buffer csvState := csv.NewState(&buf, false) var e entry // The powermonitor file can have multiple readings per second. // Timestamps are in unix time (in seconds), so we count the number of readings per second, // and then later on convert them to ms. timestampCount := 0 var readings []float64 var timestamp, prevTimestamp time.Duration matched := false fractionalMode := false for _, l := range strings.Split(f, "\n") { // Lines should be in the format: unix_timestamp amps // Ignore non matching lines. matches, result := historianutils.SubexpNames(powermonitorRE, l) if !matches { errs = append(errs, fmt.Errorf("line did not match format: unix_timestamp amps : %q", l)) continue } hasFractional := result["fractional"] != "" // If we've matched a line previously and one line is fractional and the other isn't, flag an error. if matched && fractionalMode != hasFractional { errs = append(errs, fmt.Errorf("timestamp %q does not match fractional mode %v", l, fractionalMode)) continue } fractionalMode = hasFractional d := fmt.Sprintf("%s%ss", result["timeStamp"], result["fractional"]) var err error timestamp, err = time.ParseDuration(d) if err != nil { errs = append(errs, fmt.Errorf("could not parse timestamp %q", l)) continue } f64, err := strconv.ParseFloat(result["amps"], 64) if err != nil { errs = append(errs, fmt.Errorf("could not parse amps %q", l)) continue } matched = true // If the timestamps are in fractional seconds, we can set the CSV state immediately. // Otherwise for duplicated seconds timestamps we need to accumulate all entries for the current second. if fractionalMode { e.set(csvState, timestamp, f64) continue } if timestamp == 0 || timestamp == prevTimestamp { // The same timestamp appeared, increment the count. timestampCount++ } else { // New timestamp. // Emit entries for the previous time interval. if err := emitEntriesForInterval(prevTimestamp, timestamp, timestampCount, readings, csvState, &e); err != nil { errs = append(errs, err) } prevTimestamp = timestamp timestampCount = 1 readings = nil } readings = append(readings, f64) } if fractionalMode { csvState.PrintAllReset(int64(timestamp / time.Millisecond)) } else { if err := emitEntriesForInterval(prevTimestamp, prevTimestamp+time.Second, timestampCount, readings, csvState, &e); err != nil { errs = append(errs, err) } csvState.PrintAllReset(int64((prevTimestamp + time.Second) / time.Millisecond)) } return matched, buf.String(), errs }