Loading core/java/android/app/job/JobInfo.java +14 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,20 @@ public class JobInfo implements Parcelable { */ public static final int PRIORITY_TOP_APP = 40; /** * Adjustment of {@link #getPriority} if the app has often (50% or more of the time) * been running jobs. * @hide */ public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40; /** * Adjustment of {@link #getPriority} if the app has always (90% or more of the time) * been running jobs. * @hide */ public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80; private final int jobId; private final PersistableBundle extras; private final ComponentName service; Loading services/core/java/com/android/server/job/JobPackageTracker.java 0 → 100644 +361 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.job; import android.app.job.JobInfo; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.SparseArray; import android.util.TimeUtils; import com.android.server.job.controllers.JobStatus; import java.io.PrintWriter; public final class JobPackageTracker { // We batch every 30 minutes. static final long BATCHING_TIME = 30*60*1000; // Number of historical data sets we keep. static final int NUM_HISTORY = 5; DataSet mCurDataSet = new DataSet(); DataSet[] mLastDataSets = new DataSet[NUM_HISTORY]; final static class PackageEntry { long pastActiveTime; long activeStartTime; int activeCount; boolean hadActive; long pastActiveTopTime; long activeTopStartTime; int activeTopCount; boolean hadActiveTop; long pastPendingTime; long pendingStartTime; int pendingCount; boolean hadPending; public long getActiveTime(long now) { long time = pastActiveTime; if (activeCount > 0) { time += now - activeStartTime; } return time; } public long getActiveTopTime(long now) { long time = pastActiveTopTime; if (activeTopCount > 0) { time += now - activeTopStartTime; } return time; } public long getPendingTime(long now) { long time = pastPendingTime; if (pendingCount > 0) { time += now - pendingStartTime; } return time; } } final static class DataSet { final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>(); final long mStartUptimeTime; final long mStartElapsedTime; final long mStartClockTime; long mSummedTime; public DataSet(DataSet otherTimes) { mStartUptimeTime = otherTimes.mStartUptimeTime; mStartElapsedTime = otherTimes.mStartElapsedTime; mStartClockTime = otherTimes.mStartClockTime; } public DataSet() { mStartUptimeTime = SystemClock.uptimeMillis(); mStartElapsedTime = SystemClock.elapsedRealtime(); mStartClockTime = System.currentTimeMillis(); } private PackageEntry getOrCreateEntry(int uid, String pkg) { ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); if (uidMap == null) { uidMap = new ArrayMap<>(); mEntries.put(uid, uidMap); } PackageEntry entry = uidMap.get(pkg); if (entry == null) { entry = new PackageEntry(); uidMap.put(pkg, entry); } return entry; } public PackageEntry getEntry(int uid, String pkg) { ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); if (uidMap == null) { return null; } return uidMap.get(pkg); } long getTotalTime(long now) { if (mSummedTime > 0) { return mSummedTime; } return now - mStartUptimeTime; } void incPending(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.pendingCount == 0) { pe.pendingStartTime = now; } pe.pendingCount++; } void decPending(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.pendingCount == 1) { pe.pastPendingTime += now - pe.pendingStartTime; } pe.pendingCount--; } void incActive(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeCount == 0) { pe.activeStartTime = now; } pe.activeCount++; } void decActive(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeCount == 1) { pe.pastActiveTime += now - pe.activeStartTime; } pe.activeCount--; } void incActiveTop(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeTopCount == 0) { pe.activeTopStartTime = now; } pe.activeTopCount++; } void decActiveTop(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeTopCount == 1) { pe.pastActiveTopTime += now - pe.activeTopStartTime; } pe.activeTopCount--; } void finish(DataSet next, long now) { for (int i = mEntries.size() - 1; i >= 0; i--) { ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); for (int j = uidMap.size() - 1; j >= 0; j--) { PackageEntry pe = uidMap.valueAt(j); if (pe.activeCount > 0 || pe.activeTopCount > 0 || pe.pendingCount > 0) { // Propagate existing activity in to next data set. PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); nextPe.activeStartTime = now; nextPe.activeCount = pe.activeCount; nextPe.activeTopStartTime = now; nextPe.activeTopCount = pe.activeTopCount; nextPe.pendingStartTime = now; nextPe.pendingCount = pe.pendingCount; // Finish it off. if (pe.activeCount > 0) { pe.pastActiveTime += now - pe.activeStartTime; pe.activeCount = 0; } if (pe.activeTopCount > 0) { pe.pastActiveTopTime += now - pe.activeTopStartTime; pe.activeTopCount = 0; } if (pe.pendingCount > 0) { pe.pastPendingTime += now - pe.pendingStartTime; pe.pendingCount = 0; } } } } } void addTo(DataSet out, long now) { out.mSummedTime += getTotalTime(now); for (int i = mEntries.size() - 1; i >= 0; i--) { ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); for (int j = uidMap.size() - 1; j >= 0; j--) { PackageEntry pe = uidMap.valueAt(j); PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); outPe.pastActiveTime += pe.pastActiveTime; outPe.pastActiveTopTime += pe.pastActiveTopTime; outPe.pastPendingTime += pe.pastPendingTime; if (pe.activeCount > 0) { outPe.pastActiveTime += now - pe.activeStartTime; outPe.hadActive = true; } if (pe.activeTopCount > 0) { outPe.pastActiveTopTime += now - pe.activeTopStartTime; outPe.hadActiveTop = true; } if (pe.pendingCount > 0) { outPe.pastPendingTime += now - pe.pendingStartTime; outPe.hadPending = true; } } } } void printDuration(PrintWriter pw, long period, long duration, String suffix) { float fraction = duration / (float) period; int percent = (int) ((fraction * 100) + .5f); if (percent > 0) { pw.print(" "); pw.print(percent); pw.print("% "); pw.print(suffix); } } void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed) { final long period = getTotalTime(now); pw.print(prefix); pw.print(header); pw.print(" at "); pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString()); pw.print(" ("); TimeUtils.formatDuration(mStartElapsedTime, nowEllapsed, pw); pw.print(") over "); TimeUtils.formatDuration(period, pw); pw.println(":"); final int NE = mEntries.size(); for (int i = 0; i < NE; i++) { ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); final int NP = uidMap.size(); for (int j = 0; j < NP; j++) { PackageEntry pe = uidMap.valueAt(j); pw.print(prefix); pw.print(" "); UserHandle.formatUid(pw, mEntries.keyAt(i)); pw.print(" / "); pw.print(uidMap.keyAt(j)); pw.print(":"); printDuration(pw, period, pe.getPendingTime(now), "pending"); printDuration(pw, period, pe.getActiveTime(now), "active"); printDuration(pw, period, pe.getActiveTopTime(now), "active-top"); if (pe.pendingCount > 0 || pe.hadPending) { pw.print(" (pending)"); } if (pe.activeCount > 0 || pe.hadActive) { pw.print(" (active)"); } if (pe.activeTopCount > 0 || pe.hadActiveTop) { pw.print(" (active-top)"); } pw.println(); } } } } void rebatchIfNeeded(long now) { long totalTime = mCurDataSet.getTotalTime(now); if (totalTime > BATCHING_TIME) { DataSet last = mCurDataSet; last.mSummedTime = totalTime; mCurDataSet = new DataSet(); last.finish(mCurDataSet, now); System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1); mLastDataSets[0] = last; } } public void notePending(JobStatus job) { final long now = SystemClock.uptimeMillis(); rebatchIfNeeded(now); mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now); } public void noteNonpending(JobStatus job) { final long now = SystemClock.uptimeMillis(); mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now); rebatchIfNeeded(now); } public void noteActive(JobStatus job) { final long now = SystemClock.uptimeMillis(); rebatchIfNeeded(now); if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); } else { mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); } } public void noteInactive(JobStatus job) { final long now = SystemClock.uptimeMillis(); if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); } else { mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now); } rebatchIfNeeded(now); } public float getLoadFactor(JobStatus job) { final int uid = job.getSourceUid(); final String pkg = job.getSourcePackageName(); PackageEntry cur = mCurDataSet.getEntry(uid, pkg); PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null; if (cur == null && last == null) { return 0; } final long now = SystemClock.uptimeMillis(); long time = cur.getActiveTime(now) + cur.getPendingTime(now); long period = mCurDataSet.getTotalTime(now); if (last != null) { time += last.getActiveTime(now) + last.getPendingTime(now); period += mLastDataSets[0].getTotalTime(now); } return time / (float)period; } public void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); final long nowEllapsed = SystemClock.elapsedRealtime(); final DataSet total; if (mLastDataSets[0] != null) { total = new DataSet(mLastDataSets[0]); mLastDataSets[0].addTo(total, now); } else { total = new DataSet(mCurDataSet); } mCurDataSet.addTo(total, now); for (int i = 1; i < mLastDataSets.length; i++) { if (mLastDataSets[i] != null) { mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed); pw.println(); } } total.dump(pw, "Current stats", prefix, now, nowEllapsed); } } services/core/java/com/android/server/job/JobSchedulerService.java +56 −11 Original line number Diff line number Diff line Loading @@ -93,16 +93,24 @@ public final class JobSchedulerService extends com.android.server.SystemService public static final boolean DEBUG = false; /** The maximum number of concurrent jobs we run at one time. */ private static final int MAX_JOB_CONTEXTS_COUNT = 8; private static final int MAX_JOB_CONTEXTS_COUNT = 12; /** The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. */ private static final int FG_JOB_CONTEXTS_COUNT = 4; /** Enforce a per-app limit on scheduled jobs? */ private static final boolean ENFORCE_MAX_JOBS = true; /** The maximum number of jobs that we allow an unprivileged app to schedule */ private static final int MAX_JOBS_PER_APP = 100; /** This is the job execution factor that is considered to be heavy use of the system. */ private static final float HEAVY_USE_FACTOR = .9f; /** This is the job execution factor that is considered to be moderate use of the system. */ private static final float MODERATE_USE_FACTOR = .5f; /** Global local for all job scheduler state. */ final Object mLock = new Object(); /** Master list of jobs. */ final JobStore mJobs; /** Tracking amount of time each package runs for. */ final JobPackageTracker mJobPackageTracker = new JobPackageTracker(); static final int MSG_JOB_EXPIRED = 0; static final int MSG_CHECK_JOB = 1; Loading Loading @@ -173,7 +181,7 @@ public final class JobSchedulerService extends com.android.server.SystemService * Current limit on the number of concurrent JobServiceContext entries we want to * keep actively running a job. */ int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2; int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; /** * Which uids are currently in the foreground. Loading Loading @@ -386,7 +394,9 @@ public final class JobSchedulerService extends com.android.server.SystemService stopTrackingJob(cancelled, incomingJob, true /* writeBack */); synchronized (mLock) { // Remove from pending queue. mPendingJobs.remove(cancelled); if (mPendingJobs.remove(cancelled)) { mJobPackageTracker.noteNonpending(cancelled); } // Cancel if running. stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED); reportActive(); Loading Loading @@ -518,7 +528,7 @@ public final class JobSchedulerService extends com.android.server.SystemService // Create the "runners". for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { mActiveServices.add( new JobServiceContext(this, mBatteryStats, new JobServiceContext(this, mBatteryStats, mJobPackageTracker, getContext().getMainLooper())); } // Attach jobs to their controllers. Loading Loading @@ -604,6 +614,20 @@ public final class JobSchedulerService extends com.android.server.SystemService return false; } void noteJobsPending(List<JobStatus> jobs) { for (int i = jobs.size() - 1; i >= 0; i--) { JobStatus job = jobs.get(i); mJobPackageTracker.notePending(job); } } void noteJobsNonpending(List<JobStatus> jobs) { for (int i = jobs.size() - 1; i >= 0; i--) { JobStatus job = jobs.get(i); mJobPackageTracker.noteNonpending(job); } } /** * Reschedules the given job based on the job's backoff policy. It doesn't make sense to * specify an override deadline on a failed job (the failed job will run even though it's not Loading Loading @@ -759,6 +783,7 @@ public final class JobSchedulerService extends com.android.server.SystemService // state is such that all ready jobs should be run immediately. if (runNow != null && !mPendingJobs.contains(runNow) && mJobs.containsJob(runNow)) { mJobPackageTracker.notePending(runNow); mPendingJobs.add(runNow); } queueReadyJobsForExecutionLockedH(); Loading Loading @@ -797,6 +822,7 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "queuing all ready jobs for execution:"); } noteJobsNonpending(mPendingJobs); mPendingJobs.clear(); mJobs.forEachJob(mReadyQueueFunctor); mReadyQueueFunctor.postProcess(); Loading Loading @@ -832,6 +858,7 @@ public final class JobSchedulerService extends com.android.server.SystemService public void postProcess() { if (newReadyJobs != null) { noteJobsPending(newReadyJobs); mPendingJobs.addAll(newReadyJobs); } newReadyJobs = null; Loading Loading @@ -910,6 +937,7 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); } noteJobsPending(runnableJobs); mPendingJobs.addAll(runnableJobs); } else { if (DEBUG) { Loading @@ -935,6 +963,7 @@ public final class JobSchedulerService extends com.android.server.SystemService private void maybeQueueReadyJobsForExecutionLockedH() { if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs..."); noteJobsNonpending(mPendingJobs); mPendingJobs.clear(); mJobs.forEachJob(mMaybeQueueFunctor); mMaybeQueueFunctor.postProcess(); Loading Loading @@ -998,16 +1027,28 @@ public final class JobSchedulerService extends com.android.server.SystemService } } private int adjustJobPriority(int curPriority, JobStatus job) { if (curPriority < JobInfo.PRIORITY_TOP_APP) { float factor = mJobPackageTracker.getLoadFactor(job); if (factor >= HEAVY_USE_FACTOR) { curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING; } else if (factor >= MODERATE_USE_FACTOR) { curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING; } } return curPriority; } private int evaluateJobPriorityLocked(JobStatus job) { int priority = job.getPriority(); if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) { return priority; return adjustJobPriority(priority, job); } int override = mUidPriorityOverride.get(job.getSourceUid(), 0); if (override != 0) { return override; return adjustJobPriority(override, job); } return priority; return adjustJobPriority(priority, job); } /** Loading @@ -1029,16 +1070,16 @@ public final class JobSchedulerService extends com.android.server.SystemService } switch (memLevel) { case ProcessStats.ADJ_MEM_FACTOR_MODERATE: mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - 2) * 2) / 3; mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) * 2) / 3; break; case ProcessStats.ADJ_MEM_FACTOR_LOW: mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - 2) / 3; mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) / 3; break; case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: mMaxActiveJobs = 1; break; default: mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2; mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; break; } Loading Loading @@ -1134,7 +1175,9 @@ public final class JobSchedulerService extends com.android.server.SystemService if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) { Slog.d(TAG, "Error executing " + pendingJob); } mPendingJobs.remove(pendingJob); if (mPendingJobs.remove(pendingJob)) { mJobPackageTracker.noteNonpending(pendingJob); } } } if (!preservePreferredUid) { Loading Loading @@ -1444,6 +1487,8 @@ public final class JobSchedulerService extends com.android.server.SystemService pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i)); } pw.println(); mJobPackageTracker.dump(pw, ""); pw.println(); pw.println("Pending queue:"); for (int i=0; i<mPendingJobs.size(); i++) { JobStatus job = mPendingJobs.get(i); Loading services/core/java/com/android/server/job/JobServiceContext.java +8 −3 Original line number Diff line number Diff line Loading @@ -105,6 +105,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne private final Context mContext; private final Object mLock; private final IBatteryStats mBatteryStats; private final JobPackageTracker mJobPackageTracker; private PowerManager.WakeLock mWakeLock; // Execution state. Loading Loading @@ -136,16 +137,18 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne /** Track when job will timeout. */ private long mTimeoutElapsed; JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) { this(service.getContext(), service.getLock(), batteryStats, service, looper); JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper); } @VisibleForTesting JobServiceContext(Context context, Object lock, IBatteryStats batteryStats, JobCompletedListener completedListener, Looper looper) { JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) { mContext = context; mLock = lock; mBatteryStats = batteryStats; mJobPackageTracker = tracker; mCallbackHandler = new JobServiceHandler(looper); mCompletedListener = completedListener; mAvailable = true; Loading Loading @@ -208,6 +211,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } catch (RemoteException e) { // Whatever. } mJobPackageTracker.noteActive(job); mAvailable = false; return true; } Loading Loading @@ -580,6 +584,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne return; } completedJob = mRunningJob; mJobPackageTracker.noteInactive(completedJob); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid()); Loading Loading
core/java/android/app/job/JobInfo.java +14 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,20 @@ public class JobInfo implements Parcelable { */ public static final int PRIORITY_TOP_APP = 40; /** * Adjustment of {@link #getPriority} if the app has often (50% or more of the time) * been running jobs. * @hide */ public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40; /** * Adjustment of {@link #getPriority} if the app has always (90% or more of the time) * been running jobs. * @hide */ public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80; private final int jobId; private final PersistableBundle extras; private final ComponentName service; Loading
services/core/java/com/android/server/job/JobPackageTracker.java 0 → 100644 +361 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.job; import android.app.job.JobInfo; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.SparseArray; import android.util.TimeUtils; import com.android.server.job.controllers.JobStatus; import java.io.PrintWriter; public final class JobPackageTracker { // We batch every 30 minutes. static final long BATCHING_TIME = 30*60*1000; // Number of historical data sets we keep. static final int NUM_HISTORY = 5; DataSet mCurDataSet = new DataSet(); DataSet[] mLastDataSets = new DataSet[NUM_HISTORY]; final static class PackageEntry { long pastActiveTime; long activeStartTime; int activeCount; boolean hadActive; long pastActiveTopTime; long activeTopStartTime; int activeTopCount; boolean hadActiveTop; long pastPendingTime; long pendingStartTime; int pendingCount; boolean hadPending; public long getActiveTime(long now) { long time = pastActiveTime; if (activeCount > 0) { time += now - activeStartTime; } return time; } public long getActiveTopTime(long now) { long time = pastActiveTopTime; if (activeTopCount > 0) { time += now - activeTopStartTime; } return time; } public long getPendingTime(long now) { long time = pastPendingTime; if (pendingCount > 0) { time += now - pendingStartTime; } return time; } } final static class DataSet { final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>(); final long mStartUptimeTime; final long mStartElapsedTime; final long mStartClockTime; long mSummedTime; public DataSet(DataSet otherTimes) { mStartUptimeTime = otherTimes.mStartUptimeTime; mStartElapsedTime = otherTimes.mStartElapsedTime; mStartClockTime = otherTimes.mStartClockTime; } public DataSet() { mStartUptimeTime = SystemClock.uptimeMillis(); mStartElapsedTime = SystemClock.elapsedRealtime(); mStartClockTime = System.currentTimeMillis(); } private PackageEntry getOrCreateEntry(int uid, String pkg) { ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); if (uidMap == null) { uidMap = new ArrayMap<>(); mEntries.put(uid, uidMap); } PackageEntry entry = uidMap.get(pkg); if (entry == null) { entry = new PackageEntry(); uidMap.put(pkg, entry); } return entry; } public PackageEntry getEntry(int uid, String pkg) { ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid); if (uidMap == null) { return null; } return uidMap.get(pkg); } long getTotalTime(long now) { if (mSummedTime > 0) { return mSummedTime; } return now - mStartUptimeTime; } void incPending(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.pendingCount == 0) { pe.pendingStartTime = now; } pe.pendingCount++; } void decPending(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.pendingCount == 1) { pe.pastPendingTime += now - pe.pendingStartTime; } pe.pendingCount--; } void incActive(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeCount == 0) { pe.activeStartTime = now; } pe.activeCount++; } void decActive(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeCount == 1) { pe.pastActiveTime += now - pe.activeStartTime; } pe.activeCount--; } void incActiveTop(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeTopCount == 0) { pe.activeTopStartTime = now; } pe.activeTopCount++; } void decActiveTop(int uid, String pkg, long now) { PackageEntry pe = getOrCreateEntry(uid, pkg); if (pe.activeTopCount == 1) { pe.pastActiveTopTime += now - pe.activeTopStartTime; } pe.activeTopCount--; } void finish(DataSet next, long now) { for (int i = mEntries.size() - 1; i >= 0; i--) { ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); for (int j = uidMap.size() - 1; j >= 0; j--) { PackageEntry pe = uidMap.valueAt(j); if (pe.activeCount > 0 || pe.activeTopCount > 0 || pe.pendingCount > 0) { // Propagate existing activity in to next data set. PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); nextPe.activeStartTime = now; nextPe.activeCount = pe.activeCount; nextPe.activeTopStartTime = now; nextPe.activeTopCount = pe.activeTopCount; nextPe.pendingStartTime = now; nextPe.pendingCount = pe.pendingCount; // Finish it off. if (pe.activeCount > 0) { pe.pastActiveTime += now - pe.activeStartTime; pe.activeCount = 0; } if (pe.activeTopCount > 0) { pe.pastActiveTopTime += now - pe.activeTopStartTime; pe.activeTopCount = 0; } if (pe.pendingCount > 0) { pe.pastPendingTime += now - pe.pendingStartTime; pe.pendingCount = 0; } } } } } void addTo(DataSet out, long now) { out.mSummedTime += getTotalTime(now); for (int i = mEntries.size() - 1; i >= 0; i--) { ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); for (int j = uidMap.size() - 1; j >= 0; j--) { PackageEntry pe = uidMap.valueAt(j); PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j)); outPe.pastActiveTime += pe.pastActiveTime; outPe.pastActiveTopTime += pe.pastActiveTopTime; outPe.pastPendingTime += pe.pastPendingTime; if (pe.activeCount > 0) { outPe.pastActiveTime += now - pe.activeStartTime; outPe.hadActive = true; } if (pe.activeTopCount > 0) { outPe.pastActiveTopTime += now - pe.activeTopStartTime; outPe.hadActiveTop = true; } if (pe.pendingCount > 0) { outPe.pastPendingTime += now - pe.pendingStartTime; outPe.hadPending = true; } } } } void printDuration(PrintWriter pw, long period, long duration, String suffix) { float fraction = duration / (float) period; int percent = (int) ((fraction * 100) + .5f); if (percent > 0) { pw.print(" "); pw.print(percent); pw.print("% "); pw.print(suffix); } } void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed) { final long period = getTotalTime(now); pw.print(prefix); pw.print(header); pw.print(" at "); pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString()); pw.print(" ("); TimeUtils.formatDuration(mStartElapsedTime, nowEllapsed, pw); pw.print(") over "); TimeUtils.formatDuration(period, pw); pw.println(":"); final int NE = mEntries.size(); for (int i = 0; i < NE; i++) { ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i); final int NP = uidMap.size(); for (int j = 0; j < NP; j++) { PackageEntry pe = uidMap.valueAt(j); pw.print(prefix); pw.print(" "); UserHandle.formatUid(pw, mEntries.keyAt(i)); pw.print(" / "); pw.print(uidMap.keyAt(j)); pw.print(":"); printDuration(pw, period, pe.getPendingTime(now), "pending"); printDuration(pw, period, pe.getActiveTime(now), "active"); printDuration(pw, period, pe.getActiveTopTime(now), "active-top"); if (pe.pendingCount > 0 || pe.hadPending) { pw.print(" (pending)"); } if (pe.activeCount > 0 || pe.hadActive) { pw.print(" (active)"); } if (pe.activeTopCount > 0 || pe.hadActiveTop) { pw.print(" (active-top)"); } pw.println(); } } } } void rebatchIfNeeded(long now) { long totalTime = mCurDataSet.getTotalTime(now); if (totalTime > BATCHING_TIME) { DataSet last = mCurDataSet; last.mSummedTime = totalTime; mCurDataSet = new DataSet(); last.finish(mCurDataSet, now); System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1); mLastDataSets[0] = last; } } public void notePending(JobStatus job) { final long now = SystemClock.uptimeMillis(); rebatchIfNeeded(now); mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now); } public void noteNonpending(JobStatus job) { final long now = SystemClock.uptimeMillis(); mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now); rebatchIfNeeded(now); } public void noteActive(JobStatus job) { final long now = SystemClock.uptimeMillis(); rebatchIfNeeded(now); if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); } else { mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); } } public void noteInactive(JobStatus job) { final long now = SystemClock.uptimeMillis(); if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now); } else { mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now); } rebatchIfNeeded(now); } public float getLoadFactor(JobStatus job) { final int uid = job.getSourceUid(); final String pkg = job.getSourcePackageName(); PackageEntry cur = mCurDataSet.getEntry(uid, pkg); PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null; if (cur == null && last == null) { return 0; } final long now = SystemClock.uptimeMillis(); long time = cur.getActiveTime(now) + cur.getPendingTime(now); long period = mCurDataSet.getTotalTime(now); if (last != null) { time += last.getActiveTime(now) + last.getPendingTime(now); period += mLastDataSets[0].getTotalTime(now); } return time / (float)period; } public void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); final long nowEllapsed = SystemClock.elapsedRealtime(); final DataSet total; if (mLastDataSets[0] != null) { total = new DataSet(mLastDataSets[0]); mLastDataSets[0].addTo(total, now); } else { total = new DataSet(mCurDataSet); } mCurDataSet.addTo(total, now); for (int i = 1; i < mLastDataSets.length; i++) { if (mLastDataSets[i] != null) { mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed); pw.println(); } } total.dump(pw, "Current stats", prefix, now, nowEllapsed); } }
services/core/java/com/android/server/job/JobSchedulerService.java +56 −11 Original line number Diff line number Diff line Loading @@ -93,16 +93,24 @@ public final class JobSchedulerService extends com.android.server.SystemService public static final boolean DEBUG = false; /** The maximum number of concurrent jobs we run at one time. */ private static final int MAX_JOB_CONTEXTS_COUNT = 8; private static final int MAX_JOB_CONTEXTS_COUNT = 12; /** The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. */ private static final int FG_JOB_CONTEXTS_COUNT = 4; /** Enforce a per-app limit on scheduled jobs? */ private static final boolean ENFORCE_MAX_JOBS = true; /** The maximum number of jobs that we allow an unprivileged app to schedule */ private static final int MAX_JOBS_PER_APP = 100; /** This is the job execution factor that is considered to be heavy use of the system. */ private static final float HEAVY_USE_FACTOR = .9f; /** This is the job execution factor that is considered to be moderate use of the system. */ private static final float MODERATE_USE_FACTOR = .5f; /** Global local for all job scheduler state. */ final Object mLock = new Object(); /** Master list of jobs. */ final JobStore mJobs; /** Tracking amount of time each package runs for. */ final JobPackageTracker mJobPackageTracker = new JobPackageTracker(); static final int MSG_JOB_EXPIRED = 0; static final int MSG_CHECK_JOB = 1; Loading Loading @@ -173,7 +181,7 @@ public final class JobSchedulerService extends com.android.server.SystemService * Current limit on the number of concurrent JobServiceContext entries we want to * keep actively running a job. */ int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2; int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; /** * Which uids are currently in the foreground. Loading Loading @@ -386,7 +394,9 @@ public final class JobSchedulerService extends com.android.server.SystemService stopTrackingJob(cancelled, incomingJob, true /* writeBack */); synchronized (mLock) { // Remove from pending queue. mPendingJobs.remove(cancelled); if (mPendingJobs.remove(cancelled)) { mJobPackageTracker.noteNonpending(cancelled); } // Cancel if running. stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED); reportActive(); Loading Loading @@ -518,7 +528,7 @@ public final class JobSchedulerService extends com.android.server.SystemService // Create the "runners". for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { mActiveServices.add( new JobServiceContext(this, mBatteryStats, new JobServiceContext(this, mBatteryStats, mJobPackageTracker, getContext().getMainLooper())); } // Attach jobs to their controllers. Loading Loading @@ -604,6 +614,20 @@ public final class JobSchedulerService extends com.android.server.SystemService return false; } void noteJobsPending(List<JobStatus> jobs) { for (int i = jobs.size() - 1; i >= 0; i--) { JobStatus job = jobs.get(i); mJobPackageTracker.notePending(job); } } void noteJobsNonpending(List<JobStatus> jobs) { for (int i = jobs.size() - 1; i >= 0; i--) { JobStatus job = jobs.get(i); mJobPackageTracker.noteNonpending(job); } } /** * Reschedules the given job based on the job's backoff policy. It doesn't make sense to * specify an override deadline on a failed job (the failed job will run even though it's not Loading Loading @@ -759,6 +783,7 @@ public final class JobSchedulerService extends com.android.server.SystemService // state is such that all ready jobs should be run immediately. if (runNow != null && !mPendingJobs.contains(runNow) && mJobs.containsJob(runNow)) { mJobPackageTracker.notePending(runNow); mPendingJobs.add(runNow); } queueReadyJobsForExecutionLockedH(); Loading Loading @@ -797,6 +822,7 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "queuing all ready jobs for execution:"); } noteJobsNonpending(mPendingJobs); mPendingJobs.clear(); mJobs.forEachJob(mReadyQueueFunctor); mReadyQueueFunctor.postProcess(); Loading Loading @@ -832,6 +858,7 @@ public final class JobSchedulerService extends com.android.server.SystemService public void postProcess() { if (newReadyJobs != null) { noteJobsPending(newReadyJobs); mPendingJobs.addAll(newReadyJobs); } newReadyJobs = null; Loading Loading @@ -910,6 +937,7 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); } noteJobsPending(runnableJobs); mPendingJobs.addAll(runnableJobs); } else { if (DEBUG) { Loading @@ -935,6 +963,7 @@ public final class JobSchedulerService extends com.android.server.SystemService private void maybeQueueReadyJobsForExecutionLockedH() { if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs..."); noteJobsNonpending(mPendingJobs); mPendingJobs.clear(); mJobs.forEachJob(mMaybeQueueFunctor); mMaybeQueueFunctor.postProcess(); Loading Loading @@ -998,16 +1027,28 @@ public final class JobSchedulerService extends com.android.server.SystemService } } private int adjustJobPriority(int curPriority, JobStatus job) { if (curPriority < JobInfo.PRIORITY_TOP_APP) { float factor = mJobPackageTracker.getLoadFactor(job); if (factor >= HEAVY_USE_FACTOR) { curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING; } else if (factor >= MODERATE_USE_FACTOR) { curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING; } } return curPriority; } private int evaluateJobPriorityLocked(JobStatus job) { int priority = job.getPriority(); if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) { return priority; return adjustJobPriority(priority, job); } int override = mUidPriorityOverride.get(job.getSourceUid(), 0); if (override != 0) { return override; return adjustJobPriority(override, job); } return priority; return adjustJobPriority(priority, job); } /** Loading @@ -1029,16 +1070,16 @@ public final class JobSchedulerService extends com.android.server.SystemService } switch (memLevel) { case ProcessStats.ADJ_MEM_FACTOR_MODERATE: mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - 2) * 2) / 3; mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) * 2) / 3; break; case ProcessStats.ADJ_MEM_FACTOR_LOW: mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - 2) / 3; mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) / 3; break; case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: mMaxActiveJobs = 1; break; default: mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2; mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; break; } Loading Loading @@ -1134,7 +1175,9 @@ public final class JobSchedulerService extends com.android.server.SystemService if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) { Slog.d(TAG, "Error executing " + pendingJob); } mPendingJobs.remove(pendingJob); if (mPendingJobs.remove(pendingJob)) { mJobPackageTracker.noteNonpending(pendingJob); } } } if (!preservePreferredUid) { Loading Loading @@ -1444,6 +1487,8 @@ public final class JobSchedulerService extends com.android.server.SystemService pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i)); } pw.println(); mJobPackageTracker.dump(pw, ""); pw.println(); pw.println("Pending queue:"); for (int i=0; i<mPendingJobs.size(); i++) { JobStatus job = mPendingJobs.get(i); Loading
services/core/java/com/android/server/job/JobServiceContext.java +8 −3 Original line number Diff line number Diff line Loading @@ -105,6 +105,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne private final Context mContext; private final Object mLock; private final IBatteryStats mBatteryStats; private final JobPackageTracker mJobPackageTracker; private PowerManager.WakeLock mWakeLock; // Execution state. Loading Loading @@ -136,16 +137,18 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne /** Track when job will timeout. */ private long mTimeoutElapsed; JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) { this(service.getContext(), service.getLock(), batteryStats, service, looper); JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) { this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper); } @VisibleForTesting JobServiceContext(Context context, Object lock, IBatteryStats batteryStats, JobCompletedListener completedListener, Looper looper) { JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) { mContext = context; mLock = lock; mBatteryStats = batteryStats; mJobPackageTracker = tracker; mCallbackHandler = new JobServiceHandler(looper); mCompletedListener = completedListener; mAvailable = true; Loading Loading @@ -208,6 +211,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } catch (RemoteException e) { // Whatever. } mJobPackageTracker.noteActive(job); mAvailable = false; return true; } Loading Loading @@ -580,6 +584,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne return; } completedJob = mRunningJob; mJobPackageTracker.noteInactive(completedJob); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid()); Loading