blob: 8c2c2362f9941b0569abdece33a01b822f0000d6 [file] [log] [blame]
Joe Onorato4eb64fd2016-03-21 15:30:09 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.app.procstats;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.os.SystemClock;
22import android.os.SystemProperties;
23import android.os.UserHandle;
24import android.text.format.DateFormat;
25import android.util.ArrayMap;
26import android.util.ArraySet;
27import android.util.DebugUtils;
28import android.util.Log;
29import android.util.Slog;
30import android.util.SparseArray;
31import android.util.TimeUtils;
32
33import com.android.internal.app.procstats.ProcessStats;
34import com.android.internal.app.procstats.ProcessStats.PackageState;
35import com.android.internal.app.procstats.ProcessStats.ProcessStateHolder;
36import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection;
37import static com.android.internal.app.procstats.ProcessStats.PSS_SAMPLE_COUNT;
38import static com.android.internal.app.procstats.ProcessStats.PSS_MINIMUM;
39import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE;
40import static com.android.internal.app.procstats.ProcessStats.PSS_MAXIMUM;
41import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM;
42import static com.android.internal.app.procstats.ProcessStats.PSS_USS_AVERAGE;
43import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM;
44import static com.android.internal.app.procstats.ProcessStats.PSS_COUNT;
45import static com.android.internal.app.procstats.ProcessStats.STATE_NOTHING;
46import static com.android.internal.app.procstats.ProcessStats.STATE_PERSISTENT;
47import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
48import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_FOREGROUND;
49import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
50import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
51import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
52import static com.android.internal.app.procstats.ProcessStats.STATE_SERVICE;
53import static com.android.internal.app.procstats.ProcessStats.STATE_SERVICE_RESTARTING;
54import static com.android.internal.app.procstats.ProcessStats.STATE_RECEIVER;
55import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
56import static com.android.internal.app.procstats.ProcessStats.STATE_LAST_ACTIVITY;
57import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
58import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
59import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
60import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
61
62import dalvik.system.VMRuntime;
63import libcore.util.EmptyArray;
64
65import java.io.IOException;
66import java.io.InputStream;
67import java.io.PrintWriter;
68import java.util.ArrayList;
69import java.util.Arrays;
70import java.util.Collections;
71import java.util.Comparator;
72import java.util.Objects;
73
74public final class ProcessState {
75 private static final String TAG = "ProcessStats";
76 private static final boolean DEBUG = false;
77 private static final boolean DEBUG_PARCEL = false;
78
79 // Map from process states to the states we track.
80 private static final int[] PROCESS_STATE_TO_STATE = new int[] {
81 STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
82 STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
83 STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
84 STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
85 STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
86 STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
87 STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
88 STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
89 STATE_BACKUP, // ActivityManager.PROCESS_STATE_BACKUP
90 STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
91 STATE_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE
92 STATE_RECEIVER, // ActivityManager.PROCESS_STATE_RECEIVER
93 STATE_HOME, // ActivityManager.PROCESS_STATE_HOME
94 STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
95 STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
96 STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
97 STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
98 };
99
100 public static final Comparator<ProcessState> COMPARATOR = new Comparator<ProcessState>() {
101 @Override
102 public int compare(ProcessState lhs, ProcessState rhs) {
103 if (lhs.mTmpTotalTime < rhs.mTmpTotalTime) {
104 return -1;
105 } else if (lhs.mTmpTotalTime > rhs.mTmpTotalTime) {
106 return 1;
107 }
108 return 0;
109 }
110 };
111
112 static class PssAggr {
113 long pss = 0;
114 long samples = 0;
115
116 void add(long newPss, long newSamples) {
117 pss = (long)( (pss*(double)samples) + (newPss*(double)newSamples) )
118 / (samples+newSamples);
119 samples += newSamples;
120 }
121 }
122
123 // Used by reset to count rather than storing extra maps. Be careful.
124 public int tmpNumInUse;
125 public ProcessState tmpFoundSubProc;
126
127 private final ProcessStats mStats;
128 private final String mName;
129 private final String mPackage;
130 private final int mUid;
131 private final int mVersion;
132 private final DurationsTable mDurations;
133 private final PssTable mPssTable;
134
135 private ProcessState mCommonProcess;
136 private int mCurState = STATE_NOTHING;
137 private long mStartTime;
138
139 private int mLastPssState = STATE_NOTHING;
140 private long mLastPssTime;
141
142 private boolean mActive;
143 private int mNumActiveServices;
144 private int mNumStartedServices;
145
146 private int mNumExcessiveWake;
147 private int mNumExcessiveCpu;
148
149 private int mNumCachedKill;
150 private long mMinCachedKillPss;
151 private long mAvgCachedKillPss;
152 private long mMaxCachedKillPss;
153
154 private boolean mMultiPackage;
155 private boolean mDead;
156
157 // Set in computeProcessTimeLocked and used by COMPARATOR to sort. Be careful.
158 private long mTmpTotalTime;
159
160 /**
161 * Create a new top-level process state, for the initial case where there is only
162 * a single package running in a process. The initial state is not running.
163 */
164 public ProcessState(ProcessStats processStats, String pkg, int uid, int vers, String name) {
165 mStats = processStats;
166 mName = name;
167 mCommonProcess = this;
168 mPackage = pkg;
169 mUid = uid;
170 mVersion = vers;
171 mDurations = new DurationsTable(processStats.mTableData);
172 mPssTable = new PssTable(processStats.mTableData);
173 }
174
175 /**
176 * Create a new per-package process state for an existing top-level process
177 * state. The current running state of the top-level process is also copied,
178 * marked as started running at 'now'.
179 */
180 public ProcessState(ProcessState commonProcess, String pkg, int uid, int vers, String name,
181 long now) {
182 mStats = commonProcess.mStats;
183 mName = name;
184 mCommonProcess = commonProcess;
185 mPackage = pkg;
186 mUid = uid;
187 mVersion = vers;
188 mCurState = commonProcess.mCurState;
189 mStartTime = now;
190 mDurations = new DurationsTable(commonProcess.mStats.mTableData);
191 mPssTable = new PssTable(commonProcess.mStats.mTableData);
192 }
193
194 public ProcessState clone(long now) {
195 ProcessState pnew = new ProcessState(this, mPackage, mUid, mVersion, mName, now);
196 pnew.mDurations.addDurations(mDurations);
197 pnew.mPssTable.copyFrom(mPssTable, PSS_COUNT);
198 pnew.mNumExcessiveWake = mNumExcessiveWake;
199 pnew.mNumExcessiveCpu = mNumExcessiveCpu;
200 pnew.mNumCachedKill = mNumCachedKill;
201 pnew.mMinCachedKillPss = mMinCachedKillPss;
202 pnew.mAvgCachedKillPss = mAvgCachedKillPss;
203 pnew.mMaxCachedKillPss = mMaxCachedKillPss;
204 pnew.mActive = mActive;
205 pnew.mNumActiveServices = mNumActiveServices;
206 pnew.mNumStartedServices = mNumStartedServices;
207 return pnew;
208 }
209
210 public String getName() {
211 return mName;
212 }
213
214 public ProcessState getCommonProcess() {
215 return mCommonProcess;
216 }
217
218 /**
219 * Say that we are not part of a shared process, so mCommonProcess = this.
220 */
221 public void makeStandalone() {
222 mCommonProcess = this;
223 }
224
225 public String getPackage() {
226 return mPackage;
227 }
228
229 public int getUid() {
230 return mUid;
231 }
232
233 public int getVersion() {
234 return mVersion;
235 }
236
237 public boolean isMultiPackage() {
238 return mMultiPackage;
239 }
240
241 public void setMultiPackage(boolean val) {
242 mMultiPackage = val;
243 }
244
245 public int getDurationsBucketCount() {
246 return mDurations.getKeyCount();
247 }
248
249 public void add(ProcessState other) {
250 mDurations.addDurations(other.mDurations);
251 mPssTable.mergeStats(other.mPssTable);
252 mNumExcessiveWake += other.mNumExcessiveWake;
253 mNumExcessiveCpu += other.mNumExcessiveCpu;
254 if (other.mNumCachedKill > 0) {
255 addCachedKill(other.mNumCachedKill, other.mMinCachedKillPss,
256 other.mAvgCachedKillPss, other.mMaxCachedKillPss);
257 }
258 }
259
260 public void resetSafely(long now) {
261 mDurations.resetTable();
262 mPssTable.resetTable();
263 mStartTime = now;
264 mLastPssState = STATE_NOTHING;
265 mLastPssTime = 0;
266 mNumExcessiveWake = 0;
267 mNumExcessiveCpu = 0;
268 mNumCachedKill = 0;
269 mMinCachedKillPss = mAvgCachedKillPss = mMaxCachedKillPss = 0;
270 }
271
272 public void makeDead() {
273 mDead = true;
274 }
275
276 private void ensureNotDead() {
277 if (!mDead) {
278 return;
279 }
Joe Onorato1cc1d132016-05-17 15:31:38 -0700280 Slog.w(TAG, "ProcessState dead: name=" + mName
Joe Onorato4eb64fd2016-03-21 15:30:09 -0700281 + " pkg=" + mPackage + " uid=" + mUid + " common.name=" + mCommonProcess.mName);
282 }
283
284 public void writeToParcel(Parcel out, long now) {
285 out.writeInt(mMultiPackage ? 1 : 0);
286 mDurations.writeToParcel(out);
287 mPssTable.writeToParcel(out);
288 out.writeInt(mNumExcessiveWake);
289 out.writeInt(mNumExcessiveCpu);
290 out.writeInt(mNumCachedKill);
291 if (mNumCachedKill > 0) {
292 out.writeLong(mMinCachedKillPss);
293 out.writeLong(mAvgCachedKillPss);
294 out.writeLong(mMaxCachedKillPss);
295 }
296 }
297
298 public boolean readFromParcel(Parcel in, boolean fully) {
299 boolean multiPackage = in.readInt() != 0;
300 if (fully) {
301 mMultiPackage = multiPackage;
302 }
303 if (DEBUG_PARCEL) Slog.d(TAG, "Reading durations table...");
304 if (!mDurations.readFromParcel(in)) {
305 return false;
306 }
307 if (DEBUG_PARCEL) Slog.d(TAG, "Reading pss table...");
308 if (!mPssTable.readFromParcel(in)) {
309 return false;
310 }
311 mNumExcessiveWake = in.readInt();
312 mNumExcessiveCpu = in.readInt();
313 mNumCachedKill = in.readInt();
314 if (mNumCachedKill > 0) {
315 mMinCachedKillPss = in.readLong();
316 mAvgCachedKillPss = in.readLong();
317 mMaxCachedKillPss = in.readLong();
318 } else {
319 mMinCachedKillPss = mAvgCachedKillPss = mMaxCachedKillPss = 0;
320 }
321 return true;
322 }
323
324 public void makeActive() {
325 ensureNotDead();
326 mActive = true;
327 }
328
329 public void makeInactive() {
330 mActive = false;
331 }
332
333 public boolean isInUse() {
334 return mActive || mNumActiveServices > 0 || mNumStartedServices > 0
335 || mCurState != STATE_NOTHING;
336 }
337
338 public boolean isActive() {
339 return mActive;
340 }
341
342 public boolean hasAnyData() {
343 return !(mDurations.getKeyCount() == 0
344 && mCurState == STATE_NOTHING
345 && mPssTable.getKeyCount() == 0);
346 }
347
348 /**
349 * Update the current state of the given list of processes.
350 *
351 * @param state Current ActivityManager.PROCESS_STATE_*
352 * @param memFactor Current mem factor constant.
353 * @param now Current time.
354 * @param pkgList Processes to update.
355 */
356 public void setState(int state, int memFactor, long now,
357 ArrayMap<String, ProcessStateHolder> pkgList) {
358 if (state < 0) {
359 state = mNumStartedServices > 0
360 ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
361 } else {
362 state = PROCESS_STATE_TO_STATE[state] + (memFactor*STATE_COUNT);
363 }
364
365 // First update the common process.
366 mCommonProcess.setState(state, now);
367
368 // If the common process is not multi-package, there is nothing else to do.
369 if (!mCommonProcess.mMultiPackage) {
370 return;
371 }
372
373 if (pkgList != null) {
374 for (int ip=pkgList.size()-1; ip>=0; ip--) {
375 pullFixedProc(pkgList, ip).setState(state, now);
376 }
377 }
378 }
379
380 public void setState(int state, long now) {
381 ensureNotDead();
pengzhicai5c6740a2016-09-27 16:57:56 +0800382 if (!mDead && (mCurState != state)) {
Joe Onorato4eb64fd2016-03-21 15:30:09 -0700383 //Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
384 commitStateTime(now);
385 mCurState = state;
386 }
387 }
388
389 public void commitStateTime(long now) {
390 if (mCurState != STATE_NOTHING) {
391 long dur = now - mStartTime;
392 if (dur > 0) {
393 mDurations.addDuration(mCurState, dur);
394 }
395 }
396 mStartTime = now;
397 }
398
399 public void incActiveServices(String serviceName) {
400 if (DEBUG && "".equals(mName)) {
401 RuntimeException here = new RuntimeException("here");
402 here.fillInStackTrace();
403 Slog.d(TAG, "incActiveServices: " + this + " service=" + serviceName
404 + " to " + (mNumActiveServices+1), here);
405 }
406 if (mCommonProcess != this) {
407 mCommonProcess.incActiveServices(serviceName);
408 }
409 mNumActiveServices++;
410 }
411
412 public void decActiveServices(String serviceName) {
413 if (DEBUG && "".equals(mName)) {
414 RuntimeException here = new RuntimeException("here");
415 here.fillInStackTrace();
416 Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
417 + " to " + (mNumActiveServices-1), here);
418 }
419 if (mCommonProcess != this) {
420 mCommonProcess.decActiveServices(serviceName);
421 }
422 mNumActiveServices--;
423 if (mNumActiveServices < 0) {
424 Slog.wtfStack(TAG, "Proc active services underrun: pkg=" + mPackage
425 + " uid=" + mUid + " proc=" + mName + " service=" + serviceName);
426 mNumActiveServices = 0;
427 }
428 }
429
430 public void incStartedServices(int memFactor, long now, String serviceName) {
431 if (false) {
432 RuntimeException here = new RuntimeException("here");
433 here.fillInStackTrace();
434 Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName
435 + " to " + (mNumStartedServices+1), here);
436 }
437 if (mCommonProcess != this) {
438 mCommonProcess.incStartedServices(memFactor, now, serviceName);
439 }
440 mNumStartedServices++;
441 if (mNumStartedServices == 1 && mCurState == STATE_NOTHING) {
442 setState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
443 }
444 }
445
446 public void decStartedServices(int memFactor, long now, String serviceName) {
447 if (false) {
448 RuntimeException here = new RuntimeException("here");
449 here.fillInStackTrace();
450 Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
451 + " to " + (mNumStartedServices-1), here);
452 }
453 if (mCommonProcess != this) {
454 mCommonProcess.decStartedServices(memFactor, now, serviceName);
455 }
456 mNumStartedServices--;
457 if (mNumStartedServices == 0 && (mCurState%STATE_COUNT) == STATE_SERVICE_RESTARTING) {
458 setState(STATE_NOTHING, now);
459 } else if (mNumStartedServices < 0) {
460 Slog.wtfStack(TAG, "Proc started services underrun: pkg="
461 + mPackage + " uid=" + mUid + " name=" + mName);
462 mNumStartedServices = 0;
463 }
464 }
465
466 public void addPss(long pss, long uss, boolean always,
467 ArrayMap<String, ProcessStateHolder> pkgList) {
468 ensureNotDead();
469 if (!always) {
470 if (mLastPssState == mCurState && SystemClock.uptimeMillis()
471 < (mLastPssTime+(30*1000))) {
472 return;
473 }
474 }
475 mLastPssState = mCurState;
476 mLastPssTime = SystemClock.uptimeMillis();
477 if (mCurState != STATE_NOTHING) {
478 // First update the common process.
479 mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss);
480
481 // If the common process is not multi-package, there is nothing else to do.
482 if (!mCommonProcess.mMultiPackage) {
483 return;
484 }
485
486 if (pkgList != null) {
487 for (int ip=pkgList.size()-1; ip>=0; ip--) {
488 pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurState, 1,
489 pss, pss, pss, uss, uss, uss);
490 }
491 }
492 }
493 }
494
495 public void reportExcessiveWake(ArrayMap<String, ProcessStateHolder> pkgList) {
496 ensureNotDead();
497 mCommonProcess.mNumExcessiveWake++;
498 if (!mCommonProcess.mMultiPackage) {
499 return;
500 }
501
502 for (int ip=pkgList.size()-1; ip>=0; ip--) {
503 pullFixedProc(pkgList, ip).mNumExcessiveWake++;
504 }
505 }
506
507 public void reportExcessiveCpu(ArrayMap<String, ProcessStateHolder> pkgList) {
508 ensureNotDead();
509 mCommonProcess.mNumExcessiveCpu++;
510 if (!mCommonProcess.mMultiPackage) {
511 return;
512 }
513
514 for (int ip=pkgList.size()-1; ip>=0; ip--) {
515 pullFixedProc(pkgList, ip).mNumExcessiveCpu++;
516 }
517 }
518
519 private void addCachedKill(int num, long minPss, long avgPss, long maxPss) {
520 if (mNumCachedKill <= 0) {
521 mNumCachedKill = num;
522 mMinCachedKillPss = minPss;
523 mAvgCachedKillPss = avgPss;
524 mMaxCachedKillPss = maxPss;
525 } else {
526 if (minPss < mMinCachedKillPss) {
527 mMinCachedKillPss = minPss;
528 }
529 if (maxPss > mMaxCachedKillPss) {
530 mMaxCachedKillPss = maxPss;
531 }
532 mAvgCachedKillPss = (long)( ((mAvgCachedKillPss*(double)mNumCachedKill) + avgPss)
533 / (mNumCachedKill+num) );
534 mNumCachedKill += num;
535 }
536 }
537
538 public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) {
539 ensureNotDead();
540 mCommonProcess.addCachedKill(1, pss, pss, pss);
541 if (!mCommonProcess.mMultiPackage) {
542 return;
543 }
544
545 for (int ip=pkgList.size()-1; ip>=0; ip--) {
546 pullFixedProc(pkgList, ip).addCachedKill(1, pss, pss, pss);
547 }
548 }
549
550 public ProcessState pullFixedProc(String pkgName) {
551 if (mMultiPackage) {
552 // The array map is still pointing to a common process state
553 // that is now shared across packages. Update it to point to
554 // the new per-package state.
555 SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgName, mUid);
556 if (vpkg == null) {
557 throw new IllegalStateException("Didn't find package " + pkgName
558 + " / " + mUid);
559 }
560 PackageState pkg = vpkg.get(mVersion);
561 if (pkg == null) {
562 throw new IllegalStateException("Didn't find package " + pkgName
563 + " / " + mUid + " vers " + mVersion);
564 }
565 ProcessState proc = pkg.mProcesses.get(mName);
566 if (proc == null) {
567 throw new IllegalStateException("Didn't create per-package process "
568 + mName + " in pkg " + pkgName + " / " + mUid + " vers " + mVersion);
569 }
570 return proc;
571 }
572 return this;
573 }
574
575 private ProcessState pullFixedProc(ArrayMap<String, ProcessStateHolder> pkgList,
576 int index) {
577 ProcessStateHolder holder = pkgList.valueAt(index);
578 ProcessState proc = holder.state;
579 if (mDead && proc.mCommonProcess != proc) {
580 // Somehow we are contining to use a process state that is dead, because
581 // it was not being told it was active during the last commit. We can recover
582 // from this by generating a fresh new state, but this is bad because we
583 // are losing whatever data we had in the old process state.
584 Log.wtf(TAG, "Pulling dead proc: name=" + mName + " pkg=" + mPackage
585 + " uid=" + mUid + " common.name=" + mCommonProcess.mName);
586 proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mVersion,
587 proc.mName);
588 }
589 if (proc.mMultiPackage) {
590 // The array map is still pointing to a common process state
591 // that is now shared across packages. Update it to point to
592 // the new per-package state.
593 SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgList.keyAt(index),
594 proc.mUid);
595 if (vpkg == null) {
596 throw new IllegalStateException("No existing package "
597 + pkgList.keyAt(index) + "/" + proc.mUid
598 + " for multi-proc " + proc.mName);
599 }
600 PackageState pkg = vpkg.get(proc.mVersion);
601 if (pkg == null) {
602 throw new IllegalStateException("No existing package "
603 + pkgList.keyAt(index) + "/" + proc.mUid
604 + " for multi-proc " + proc.mName + " version " + proc.mVersion);
605 }
606 String savedName = proc.mName;
607 proc = pkg.mProcesses.get(proc.mName);
608 if (proc == null) {
609 throw new IllegalStateException("Didn't create per-package process "
610 + savedName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid);
611 }
612 holder.state = proc;
613 }
614 return proc;
615 }
616
617 public long getDuration(int state, long now) {
618 long time = mDurations.getValueForId((byte)state);
619 if (mCurState == state) {
620 time += now - mStartTime;
621 }
622 return time;
623 }
624
625 public long getPssSampleCount(int state) {
626 return mPssTable.getValueForId((byte)state, PSS_SAMPLE_COUNT);
627 }
628
629 public long getPssMinimum(int state) {
630 return mPssTable.getValueForId((byte)state, PSS_MINIMUM);
631 }
632
633 public long getPssAverage(int state) {
634 return mPssTable.getValueForId((byte)state, PSS_AVERAGE);
635 }
636
637 public long getPssMaximum(int state) {
638 return mPssTable.getValueForId((byte)state, PSS_MAXIMUM);
639 }
640
641 public long getPssUssMinimum(int state) {
642 return mPssTable.getValueForId((byte)state, PSS_USS_MINIMUM);
643 }
644
645 public long getPssUssAverage(int state) {
646 return mPssTable.getValueForId((byte)state, PSS_USS_AVERAGE);
647 }
648
649 public long getPssUssMaximum(int state) {
650 return mPssTable.getValueForId((byte)state, PSS_USS_MAXIMUM);
651 }
652
653 /**
654 * Sums up the PSS data and adds it to 'data'.
655 *
656 * @param data The aggregate data is added here.
657 * @param now SystemClock.uptimeMillis()
658 */
659 public void aggregatePss(TotalMemoryUseCollection data, long now) {
660 final PssAggr fgPss = new PssAggr();
661 final PssAggr bgPss = new PssAggr();
662 final PssAggr cachedPss = new PssAggr();
663 boolean havePss = false;
664 for (int i=0; i<mDurations.getKeyCount(); i++) {
665 final int key = mDurations.getKeyAt(i);
666 int type = SparseMappingTable.getIdFromKey(key);
667 int procState = type % STATE_COUNT;
668 long samples = getPssSampleCount(type);
669 if (samples > 0) {
670 long avg = getPssAverage(type);
671 havePss = true;
672 if (procState <= STATE_IMPORTANT_FOREGROUND) {
673 fgPss.add(avg, samples);
674 } else if (procState <= STATE_RECEIVER) {
675 bgPss.add(avg, samples);
676 } else {
677 cachedPss.add(avg, samples);
678 }
679 }
680 }
681 if (!havePss) {
682 return;
683 }
684 boolean fgHasBg = false;
685 boolean fgHasCached = false;
686 boolean bgHasCached = false;
687 if (fgPss.samples < 3 && bgPss.samples > 0) {
688 fgHasBg = true;
689 fgPss.add(bgPss.pss, bgPss.samples);
690 }
691 if (fgPss.samples < 3 && cachedPss.samples > 0) {
692 fgHasCached = true;
693 fgPss.add(cachedPss.pss, cachedPss.samples);
694 }
695 if (bgPss.samples < 3 && cachedPss.samples > 0) {
696 bgHasCached = true;
697 bgPss.add(cachedPss.pss, cachedPss.samples);
698 }
699 if (bgPss.samples < 3 && !fgHasBg && fgPss.samples > 0) {
700 bgPss.add(fgPss.pss, fgPss.samples);
701 }
702 if (cachedPss.samples < 3 && !bgHasCached && bgPss.samples > 0) {
703 cachedPss.add(bgPss.pss, bgPss.samples);
704 }
705 if (cachedPss.samples < 3 && !fgHasCached && fgPss.samples > 0) {
706 cachedPss.add(fgPss.pss, fgPss.samples);
707 }
708 for (int i=0; i<mDurations.getKeyCount(); i++) {
709 final int key = mDurations.getKeyAt(i);
710 final int type = SparseMappingTable.getIdFromKey(key);
711 long time = mDurations.getValue(key);
712 if (mCurState == type) {
713 time += now - mStartTime;
714 }
715 final int procState = type % STATE_COUNT;
716 data.processStateTime[procState] += time;
717 long samples = getPssSampleCount(type);
718 long avg;
719 if (samples > 0) {
720 avg = getPssAverage(type);
721 } else if (procState <= STATE_IMPORTANT_FOREGROUND) {
722 samples = fgPss.samples;
723 avg = fgPss.pss;
724 } else if (procState <= STATE_RECEIVER) {
725 samples = bgPss.samples;
726 avg = bgPss.pss;
727 } else {
728 samples = cachedPss.samples;
729 avg = cachedPss.pss;
730 }
731 double newAvg = ( (data.processStatePss[procState]
732 * (double)data.processStateSamples[procState])
733 + (avg*(double)samples)
734 ) / (data.processStateSamples[procState]+samples);
735 data.processStatePss[procState] = (long)newAvg;
736 data.processStateSamples[procState] += samples;
737 data.processStateWeight[procState] += avg * (double)time;
738 }
739 }
740
741 public long computeProcessTimeLocked(int[] screenStates, int[] memStates,
742 int[] procStates, long now) {
743 long totalTime = 0;
744 for (int is=0; is<screenStates.length; is++) {
745 for (int im=0; im<memStates.length; im++) {
746 for (int ip=0; ip<procStates.length; ip++) {
747 int bucket = ((screenStates[is] + memStates[im]) * STATE_COUNT)
748 + procStates[ip];
749 totalTime += getDuration(bucket, now);
750 }
751 }
752 }
753 mTmpTotalTime = totalTime;
754 return totalTime;
755 }
756
757 public void dumpSummary(PrintWriter pw, String prefix,
758 int[] screenStates, int[] memStates, int[] procStates,
759 long now, long totalTime) {
760 pw.print(prefix);
761 pw.print("* ");
762 pw.print(mName);
763 pw.print(" / ");
764 UserHandle.formatUid(pw, mUid);
765 pw.print(" / v");
766 pw.print(mVersion);
767 pw.println(":");
768 dumpProcessSummaryDetails(pw, prefix, " TOTAL: ", screenStates, memStates,
769 procStates, now, totalTime, true);
770 dumpProcessSummaryDetails(pw, prefix, " Persistent: ", screenStates, memStates,
771 new int[] { STATE_PERSISTENT }, now, totalTime, true);
772 dumpProcessSummaryDetails(pw, prefix, " Top: ", screenStates, memStates,
773 new int[] {STATE_TOP}, now, totalTime, true);
774 dumpProcessSummaryDetails(pw, prefix, " Imp Fg: ", screenStates, memStates,
775 new int[] { STATE_IMPORTANT_FOREGROUND }, now, totalTime, true);
776 dumpProcessSummaryDetails(pw, prefix, " Imp Bg: ", screenStates, memStates,
777 new int[] {STATE_IMPORTANT_BACKGROUND}, now, totalTime, true);
778 dumpProcessSummaryDetails(pw, prefix, " Backup: ", screenStates, memStates,
779 new int[] {STATE_BACKUP}, now, totalTime, true);
780 dumpProcessSummaryDetails(pw, prefix, " Heavy Wgt: ", screenStates, memStates,
781 new int[] {STATE_HEAVY_WEIGHT}, now, totalTime, true);
782 dumpProcessSummaryDetails(pw, prefix, " Service: ", screenStates, memStates,
783 new int[] {STATE_SERVICE}, now, totalTime, true);
784 dumpProcessSummaryDetails(pw, prefix, " Service Rs: ", screenStates, memStates,
785 new int[] {STATE_SERVICE_RESTARTING}, now, totalTime, true);
786 dumpProcessSummaryDetails(pw, prefix, " Receiver: ", screenStates, memStates,
787 new int[] {STATE_RECEIVER}, now, totalTime, true);
788 dumpProcessSummaryDetails(pw, prefix, " (Home): ", screenStates, memStates,
789 new int[] {STATE_HOME}, now, totalTime, true);
790 dumpProcessSummaryDetails(pw, prefix, " (Last Act): ", screenStates, memStates,
791 new int[] {STATE_LAST_ACTIVITY}, now, totalTime, true);
792 dumpProcessSummaryDetails(pw, prefix, " (Cached): ", screenStates, memStates,
793 new int[] {STATE_CACHED_ACTIVITY, STATE_CACHED_ACTIVITY_CLIENT,
794 STATE_CACHED_EMPTY}, now, totalTime, true);
795 }
796
797 public void dumpProcessState(PrintWriter pw, String prefix,
798 int[] screenStates, int[] memStates, int[] procStates, long now) {
799 long totalTime = 0;
800 int printedScreen = -1;
801 for (int is=0; is<screenStates.length; is++) {
802 int printedMem = -1;
803 for (int im=0; im<memStates.length; im++) {
804 for (int ip=0; ip<procStates.length; ip++) {
805 final int iscreen = screenStates[is];
806 final int imem = memStates[im];
807 final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
808 long time = mDurations.getValueForId((byte)bucket);
809 String running = "";
810 if (mCurState == bucket) {
811 running = " (running)";
812 }
813 if (time != 0) {
814 pw.print(prefix);
815 if (screenStates.length > 1) {
816 DumpUtils.printScreenLabel(pw, printedScreen != iscreen
817 ? iscreen : STATE_NOTHING);
818 printedScreen = iscreen;
819 }
820 if (memStates.length > 1) {
821 DumpUtils.printMemLabel(pw,
822 printedMem != imem ? imem : STATE_NOTHING, '/');
823 printedMem = imem;
824 }
825 pw.print(DumpUtils.STATE_NAMES[procStates[ip]]); pw.print(": ");
826 TimeUtils.formatDuration(time, pw); pw.println(running);
827 totalTime += time;
828 }
829 }
830 }
831 }
832 if (totalTime != 0) {
833 pw.print(prefix);
834 if (screenStates.length > 1) {
835 DumpUtils.printScreenLabel(pw, STATE_NOTHING);
836 }
837 if (memStates.length > 1) {
838 DumpUtils.printMemLabel(pw, STATE_NOTHING, '/');
839 }
840 pw.print("TOTAL : ");
841 TimeUtils.formatDuration(totalTime, pw);
842 pw.println();
843 }
844 }
845
846 public void dumpPss(PrintWriter pw, String prefix,
847 int[] screenStates, int[] memStates, int[] procStates) {
848 boolean printedHeader = false;
849 int printedScreen = -1;
850 for (int is=0; is<screenStates.length; is++) {
851 int printedMem = -1;
852 for (int im=0; im<memStates.length; im++) {
853 for (int ip=0; ip<procStates.length; ip++) {
854 final int iscreen = screenStates[is];
855 final int imem = memStates[im];
856 final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
857 long count = getPssSampleCount(bucket);
858 if (count > 0) {
859 if (!printedHeader) {
860 pw.print(prefix);
861 pw.print("PSS/USS (");
862 pw.print(mPssTable.getKeyCount());
863 pw.println(" entries):");
864 printedHeader = true;
865 }
866 pw.print(prefix);
867 pw.print(" ");
868 if (screenStates.length > 1) {
869 DumpUtils.printScreenLabel(pw,
870 printedScreen != iscreen ? iscreen : STATE_NOTHING);
871 printedScreen = iscreen;
872 }
873 if (memStates.length > 1) {
874 DumpUtils.printMemLabel(pw,
875 printedMem != imem ? imem : STATE_NOTHING, '/');
876 printedMem = imem;
877 }
878 pw.print(DumpUtils.STATE_NAMES[procStates[ip]]); pw.print(": ");
879 pw.print(count);
880 pw.print(" samples ");
881 DebugUtils.printSizeValue(pw, getPssMinimum(bucket) * 1024);
882 pw.print(" ");
883 DebugUtils.printSizeValue(pw, getPssAverage(bucket) * 1024);
884 pw.print(" ");
885 DebugUtils.printSizeValue(pw, getPssMaximum(bucket) * 1024);
886 pw.print(" / ");
887 DebugUtils.printSizeValue(pw, getPssUssMinimum(bucket) * 1024);
888 pw.print(" ");
889 DebugUtils.printSizeValue(pw, getPssUssAverage(bucket) * 1024);
890 pw.print(" ");
891 DebugUtils.printSizeValue(pw, getPssUssMaximum(bucket) * 1024);
892 pw.println();
893 }
894 }
895 }
896 }
897 if (mNumExcessiveWake != 0) {
898 pw.print(prefix); pw.print("Killed for excessive wake locks: ");
899 pw.print(mNumExcessiveWake); pw.println(" times");
900 }
901 if (mNumExcessiveCpu != 0) {
902 pw.print(prefix); pw.print("Killed for excessive CPU use: ");
903 pw.print(mNumExcessiveCpu); pw.println(" times");
904 }
905 if (mNumCachedKill != 0) {
906 pw.print(prefix); pw.print("Killed from cached state: ");
907 pw.print(mNumCachedKill); pw.print(" times from pss ");
908 DebugUtils.printSizeValue(pw, mMinCachedKillPss * 1024); pw.print("-");
909 DebugUtils.printSizeValue(pw, mAvgCachedKillPss * 1024); pw.print("-");
910 DebugUtils.printSizeValue(pw, mMaxCachedKillPss * 1024); pw.println();
911 }
912 }
913
914 private void dumpProcessSummaryDetails(PrintWriter pw, String prefix,
915 String label, int[] screenStates, int[] memStates, int[] procStates,
916 long now, long totalTime, boolean full) {
917 ProcessStats.ProcessDataCollection totals = new ProcessStats.ProcessDataCollection(
918 screenStates, memStates, procStates);
919 computeProcessData(totals, now);
920 final double percentage = (double) totals.totalTime / (double) totalTime * 100;
921 // We don't print percentages < .01, so just drop those.
922 if (percentage >= 0.005 || totals.numPss != 0) {
923 if (prefix != null) {
924 pw.print(prefix);
925 }
926 if (label != null) {
927 pw.print(label);
928 }
929 totals.print(pw, totalTime, full);
930 if (prefix != null) {
931 pw.println();
932 }
933 }
934 }
935
936 public void dumpInternalLocked(PrintWriter pw, String prefix, boolean dumpAll) {
937 if (dumpAll) {
938 pw.print(prefix); pw.print("myID=");
939 pw.print(Integer.toHexString(System.identityHashCode(this)));
940 pw.print(" mCommonProcess=");
941 pw.print(Integer.toHexString(System.identityHashCode(mCommonProcess)));
942 pw.print(" mPackage="); pw.println(mPackage);
943 if (mMultiPackage) {
944 pw.print(prefix); pw.print("mMultiPackage="); pw.println(mMultiPackage);
945 }
946 if (this != mCommonProcess) {
947 pw.print(prefix); pw.print("Common Proc: "); pw.print(mCommonProcess.mName);
948 pw.print("/"); pw.print(mCommonProcess.mUid);
949 pw.print(" pkg="); pw.println(mCommonProcess.mPackage);
950 }
951 }
952 if (mActive) {
953 pw.print(prefix); pw.print("mActive="); pw.println(mActive);
954 }
955 if (mDead) {
956 pw.print(prefix); pw.print("mDead="); pw.println(mDead);
957 }
958 if (mNumActiveServices != 0 || mNumStartedServices != 0) {
959 pw.print(prefix); pw.print("mNumActiveServices="); pw.print(mNumActiveServices);
960 pw.print(" mNumStartedServices=");
961 pw.println(mNumStartedServices);
962 }
963 }
964
965 public void computeProcessData(ProcessStats.ProcessDataCollection data, long now) {
966 data.totalTime = 0;
967 data.numPss = data.minPss = data.avgPss = data.maxPss =
968 data.minUss = data.avgUss = data.maxUss = 0;
969 for (int is=0; is<data.screenStates.length; is++) {
970 for (int im=0; im<data.memStates.length; im++) {
971 for (int ip=0; ip<data.procStates.length; ip++) {
972 int bucket = ((data.screenStates[is] + data.memStates[im]) * STATE_COUNT)
973 + data.procStates[ip];
974 data.totalTime += getDuration(bucket, now);
975 long samples = getPssSampleCount(bucket);
976 if (samples > 0) {
977 long minPss = getPssMinimum(bucket);
978 long avgPss = getPssAverage(bucket);
979 long maxPss = getPssMaximum(bucket);
980 long minUss = getPssUssMinimum(bucket);
981 long avgUss = getPssUssAverage(bucket);
982 long maxUss = getPssUssMaximum(bucket);
983 if (data.numPss == 0) {
984 data.minPss = minPss;
985 data.avgPss = avgPss;
986 data.maxPss = maxPss;
987 data.minUss = minUss;
988 data.avgUss = avgUss;
989 data.maxUss = maxUss;
990 } else {
991 if (minPss < data.minPss) {
992 data.minPss = minPss;
993 }
994 data.avgPss = (long)( ((data.avgPss*(double)data.numPss)
995 + (avgPss*(double)samples)) / (data.numPss+samples) );
996 if (maxPss > data.maxPss) {
997 data.maxPss = maxPss;
998 }
999 if (minUss < data.minUss) {
1000 data.minUss = minUss;
1001 }
1002 data.avgUss = (long)( ((data.avgUss*(double)data.numPss)
1003 + (avgUss*(double)samples)) / (data.numPss+samples) );
1004 if (maxUss > data.maxUss) {
1005 data.maxUss = maxUss;
1006 }
1007 }
1008 data.numPss += samples;
1009 }
1010 }
1011 }
1012 }
1013 }
1014
1015 public void dumpCsv(PrintWriter pw,
1016 boolean sepScreenStates, int[] screenStates, boolean sepMemStates,
1017 int[] memStates, boolean sepProcStates, int[] procStates, long now) {
1018 final int NSS = sepScreenStates ? screenStates.length : 1;
1019 final int NMS = sepMemStates ? memStates.length : 1;
1020 final int NPS = sepProcStates ? procStates.length : 1;
1021 for (int iss=0; iss<NSS; iss++) {
1022 for (int ims=0; ims<NMS; ims++) {
1023 for (int ips=0; ips<NPS; ips++) {
1024 final int vsscreen = sepScreenStates ? screenStates[iss] : 0;
1025 final int vsmem = sepMemStates ? memStates[ims] : 0;
1026 final int vsproc = sepProcStates ? procStates[ips] : 0;
1027 final int NSA = sepScreenStates ? 1 : screenStates.length;
1028 final int NMA = sepMemStates ? 1 : memStates.length;
1029 final int NPA = sepProcStates ? 1 : procStates.length;
1030 long totalTime = 0;
1031 for (int isa=0; isa<NSA; isa++) {
1032 for (int ima=0; ima<NMA; ima++) {
1033 for (int ipa=0; ipa<NPA; ipa++) {
1034 final int vascreen = sepScreenStates ? 0 : screenStates[isa];
1035 final int vamem = sepMemStates ? 0 : memStates[ima];
1036 final int vaproc = sepProcStates ? 0 : procStates[ipa];
1037 final int bucket = ((vsscreen + vascreen + vsmem + vamem)
1038 * STATE_COUNT) + vsproc + vaproc;
1039 totalTime += getDuration(bucket, now);
1040 }
1041 }
1042 }
1043 pw.print(DumpUtils.CSV_SEP);
1044 pw.print(totalTime);
1045 }
1046 }
1047 }
1048 }
1049
1050 public void dumpPackageProcCheckin(PrintWriter pw, String pkgName, int uid, int vers,
1051 String itemName, long now) {
1052 pw.print("pkgproc,");
1053 pw.print(pkgName);
1054 pw.print(",");
1055 pw.print(uid);
1056 pw.print(",");
1057 pw.print(vers);
1058 pw.print(",");
1059 pw.print(DumpUtils.collapseString(pkgName, itemName));
1060 dumpAllStateCheckin(pw, now);
1061 pw.println();
1062 if (mPssTable.getKeyCount() > 0) {
1063 pw.print("pkgpss,");
1064 pw.print(pkgName);
1065 pw.print(",");
1066 pw.print(uid);
1067 pw.print(",");
1068 pw.print(vers);
1069 pw.print(",");
1070 pw.print(DumpUtils.collapseString(pkgName, itemName));
1071 dumpAllPssCheckin(pw);
1072 pw.println();
1073 }
1074 if (mNumExcessiveWake > 0 || mNumExcessiveCpu > 0 || mNumCachedKill > 0) {
1075 pw.print("pkgkills,");
1076 pw.print(pkgName);
1077 pw.print(",");
1078 pw.print(uid);
1079 pw.print(",");
1080 pw.print(vers);
1081 pw.print(",");
1082 pw.print(DumpUtils.collapseString(pkgName, itemName));
1083 pw.print(",");
1084 pw.print(mNumExcessiveWake);
1085 pw.print(",");
1086 pw.print(mNumExcessiveCpu);
1087 pw.print(",");
1088 pw.print(mNumCachedKill);
1089 pw.print(",");
1090 pw.print(mMinCachedKillPss);
1091 pw.print(":");
1092 pw.print(mAvgCachedKillPss);
1093 pw.print(":");
1094 pw.print(mMaxCachedKillPss);
1095 pw.println();
1096 }
1097 }
1098
1099 public void dumpProcCheckin(PrintWriter pw, String procName, int uid, long now) {
1100 if (mDurations.getKeyCount() > 0) {
1101 pw.print("proc,");
1102 pw.print(procName);
1103 pw.print(",");
1104 pw.print(uid);
1105 dumpAllStateCheckin(pw, now);
1106 pw.println();
1107 }
1108 if (mPssTable.getKeyCount() > 0) {
1109 pw.print("pss,");
1110 pw.print(procName);
1111 pw.print(",");
1112 pw.print(uid);
1113 dumpAllPssCheckin(pw);
1114 pw.println();
1115 }
1116 if (mNumExcessiveWake > 0 || mNumExcessiveCpu > 0 || mNumCachedKill > 0) {
1117 pw.print("kills,");
1118 pw.print(procName);
1119 pw.print(",");
1120 pw.print(uid);
1121 pw.print(",");
1122 pw.print(mNumExcessiveWake);
1123 pw.print(",");
1124 pw.print(mNumExcessiveCpu);
1125 pw.print(",");
1126 pw.print(mNumCachedKill);
1127 pw.print(",");
1128 pw.print(mMinCachedKillPss);
1129 pw.print(":");
1130 pw.print(mAvgCachedKillPss);
1131 pw.print(":");
1132 pw.print(mMaxCachedKillPss);
1133 pw.println();
1134 }
1135 }
1136
1137 public void dumpAllStateCheckin(PrintWriter pw, long now) {
1138 boolean didCurState = false;
1139 for (int i=0; i<mDurations.getKeyCount(); i++) {
1140 final int key = mDurations.getKeyAt(i);
1141 final int type = SparseMappingTable.getIdFromKey(key);
1142 long time = mDurations.getValue(key);
1143 if (mCurState == type) {
1144 didCurState = true;
1145 time += now - mStartTime;
1146 }
1147 DumpUtils.printProcStateTagAndValue(pw, type, time);
1148 }
1149 if (!didCurState && mCurState != STATE_NOTHING) {
1150 DumpUtils.printProcStateTagAndValue(pw, mCurState, now - mStartTime);
1151 }
1152 }
1153
1154 public void dumpAllPssCheckin(PrintWriter pw) {
1155 final int N = mPssTable.getKeyCount();
1156 for (int i=0; i<N; i++) {
1157 final int key = mPssTable.getKeyAt(i);
1158 final int type = SparseMappingTable.getIdFromKey(key);
1159 pw.print(',');
1160 DumpUtils.printProcStateTag(pw, type);
1161 pw.print(':');
1162 pw.print(mPssTable.getValue(key, PSS_SAMPLE_COUNT));
1163 pw.print(':');
1164 pw.print(mPssTable.getValue(key, PSS_MINIMUM));
1165 pw.print(':');
1166 pw.print(mPssTable.getValue(key, PSS_AVERAGE));
1167 pw.print(':');
1168 pw.print(mPssTable.getValue(key, PSS_MAXIMUM));
1169 pw.print(':');
1170 pw.print(mPssTable.getValue(key, PSS_USS_MINIMUM));
1171 pw.print(':');
1172 pw.print(mPssTable.getValue(key, PSS_USS_AVERAGE));
1173 pw.print(':');
1174 pw.print(mPssTable.getValue(key, PSS_USS_MAXIMUM));
1175 }
1176 }
1177
1178 public String toString() {
1179 StringBuilder sb = new StringBuilder(128);
1180 sb.append("ProcessState{").append(Integer.toHexString(System.identityHashCode(this)))
1181 .append(" ").append(mName).append("/").append(mUid)
1182 .append(" pkg=").append(mPackage);
1183 if (mMultiPackage) sb.append(" (multi)");
1184 if (mCommonProcess != this) sb.append(" (sub)");
1185 sb.append("}");
1186 return sb.toString();
1187 }
1188}