コード例 #1
0
// 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
}
コード例 #2
0
// 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
}
コード例 #3
0
// 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")
}
コード例 #4
0
// 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")
}
コード例 #5
0
// 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
}
コード例 #6
0
// 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
}
コード例 #7
0
// 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
}
コード例 #8
0
// 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")
}
コード例 #9
0
// 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
}
コード例 #10
0
// 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
}
コード例 #11
0
ファイル: activity.go プロジェクト: mike69d/battery-historian
// 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
}
コード例 #12
0
// 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
}