Home | History | Annotate | Download | only in Logging
      1 /*
      2  * Copyright (C) 2016 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 
     17 package android.telecom.Logging;
     18 
     19 import android.annotation.NonNull;
     20 import android.os.Parcel;
     21 import android.os.Parcelable;
     22 import android.telecom.Log;
     23 import android.text.TextUtils;
     24 
     25 import com.android.internal.annotations.VisibleForTesting;
     26 
     27 import java.util.ArrayList;
     28 
     29 /**
     30  * Stores information about a thread's point of entry into that should persist until that thread
     31  * exits.
     32  * @hide
     33  */
     34 public class Session {
     35 
     36     public static final String START_SESSION = "START_SESSION";
     37     public static final String START_EXTERNAL_SESSION = "START_EXTERNAL_SESSION";
     38     public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
     39     public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION";
     40     public static final String END_SUBSESSION = "END_SUBSESSION";
     41     public static final String END_SESSION = "END_SESSION";
     42 
     43     public static final String SUBSESSION_SEPARATION_CHAR = "->";
     44     public static final String SESSION_SEPARATION_CHAR_CHILD = "_";
     45     public static final String EXTERNAL_INDICATOR = "E-";
     46     public static final String TRUNCATE_STRING = "...";
     47 
     48     /**
     49      * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()}
     50      * if the Session is canceled.
     51      */
     52     public static final int UNDEFINED = -1;
     53 
     54     public static class Info implements Parcelable {
     55         public final String sessionId;
     56         public final String methodPath;
     57 
     58         private Info(String id, String path) {
     59             sessionId = id;
     60             methodPath = path;
     61         }
     62 
     63         public static Info getInfo (Session s) {
     64             // Create Info based on the truncated method path if the session is external, so we do
     65             // not get multiple stacking external sessions (unless we have DEBUG level logging or
     66             // lower).
     67             return new Info(s.getFullSessionId(), s.getFullMethodPath(
     68                     !Log.DEBUG && s.isSessionExternal()));
     69         }
     70 
     71         /** Responsible for creating Info objects for deserialized Parcels. */
     72         public static final Parcelable.Creator<Info> CREATOR =
     73                 new Parcelable.Creator<Info> () {
     74                     @Override
     75                     public Info createFromParcel(Parcel source) {
     76                         String id = source.readString();
     77                         String methodName = source.readString();
     78                         return new Info(id, methodName);
     79                     }
     80 
     81                     @Override
     82                     public Info[] newArray(int size) {
     83                         return new Info[size];
     84                     }
     85                 };
     86 
     87         /** {@inheritDoc} */
     88         @Override
     89         public int describeContents() {
     90             return 0;
     91         }
     92 
     93         /** Writes Info object into a Parcel. */
     94         @Override
     95         public void writeToParcel(Parcel destination, int flags) {
     96             destination.writeString(sessionId);
     97             destination.writeString(methodPath);
     98         }
     99     }
    100 
    101     private String mSessionId;
    102     private String mShortMethodName;
    103     private long mExecutionStartTimeMs;
    104     private long mExecutionEndTimeMs = UNDEFINED;
    105     private Session mParentSession;
    106     private ArrayList<Session> mChildSessions;
    107     private boolean mIsCompleted = false;
    108     private boolean mIsExternal = false;
    109     private int mChildCounter = 0;
    110     // True if this is a subsession that has been started from the same thread as the parent
    111     // session. This can happen if Log.startSession(...) is called multiple times on the same
    112     // thread in the case of one Telecom entry point method calling another entry point method.
    113     // In this case, we can just make this subsession "invisible," but still keep track of it so
    114     // that the Log.endSession() calls match up.
    115     private boolean mIsStartedFromActiveSession = false;
    116     // Optionally provided info about the method/class/component that started the session in order
    117     // to make Logging easier. This info will be provided in parentheses along with the session.
    118     private String mOwnerInfo;
    119     // Cache Full Method path so that recursive population of the full method path only needs to
    120     // be calculated once.
    121     private String mFullMethodPathCache;
    122 
    123     public Session(String sessionId, String shortMethodName, long startTimeMs,
    124             boolean isStartedFromActiveSession, String ownerInfo) {
    125         setSessionId(sessionId);
    126         setShortMethodName(shortMethodName);
    127         mExecutionStartTimeMs = startTimeMs;
    128         mParentSession = null;
    129         mChildSessions = new ArrayList<>(5);
    130         mIsStartedFromActiveSession = isStartedFromActiveSession;
    131         mOwnerInfo = ownerInfo;
    132     }
    133 
    134     public void setSessionId(@NonNull String sessionId) {
    135         if (sessionId == null) {
    136             mSessionId = "?";
    137         }
    138         mSessionId = sessionId;
    139     }
    140 
    141     public String getShortMethodName() {
    142         return mShortMethodName;
    143     }
    144 
    145     public void setShortMethodName(String shortMethodName) {
    146         if (shortMethodName == null) {
    147             shortMethodName = "";
    148         }
    149         mShortMethodName = shortMethodName;
    150     }
    151 
    152     public void setIsExternal(boolean isExternal) {
    153         mIsExternal = isExternal;
    154     }
    155 
    156     public boolean isExternal() {
    157         return mIsExternal;
    158     }
    159 
    160     public void setParentSession(Session parentSession) {
    161         mParentSession = parentSession;
    162     }
    163 
    164     public void addChild(Session childSession) {
    165         if (childSession != null) {
    166             mChildSessions.add(childSession);
    167         }
    168     }
    169 
    170     public void removeChild(Session child) {
    171         if (child != null) {
    172             mChildSessions.remove(child);
    173         }
    174     }
    175 
    176     public long getExecutionStartTimeMilliseconds() {
    177         return mExecutionStartTimeMs;
    178     }
    179 
    180     public void setExecutionStartTimeMs(long startTimeMs) {
    181         mExecutionStartTimeMs = startTimeMs;
    182     }
    183 
    184     public Session getParentSession() {
    185         return mParentSession;
    186     }
    187 
    188     public ArrayList<Session> getChildSessions() {
    189         return mChildSessions;
    190     }
    191 
    192     public boolean isSessionCompleted() {
    193         return mIsCompleted;
    194     }
    195 
    196     public boolean isStartedFromActiveSession() {
    197         return mIsStartedFromActiveSession;
    198     }
    199 
    200     public Info getInfo() {
    201         return Info.getInfo(this);
    202     }
    203 
    204     @VisibleForTesting
    205     public String getSessionId() {
    206         return mSessionId;
    207     }
    208 
    209     // Mark this session complete. This will be deleted by Log when all subsessions are complete
    210     // as well.
    211     public void markSessionCompleted(long executionEndTimeMs) {
    212         mExecutionEndTimeMs = executionEndTimeMs;
    213         mIsCompleted = true;
    214     }
    215 
    216     public long getLocalExecutionTime() {
    217         if (mExecutionEndTimeMs == UNDEFINED) {
    218             return UNDEFINED;
    219         }
    220         return mExecutionEndTimeMs - mExecutionStartTimeMs;
    221     }
    222 
    223     public synchronized String getNextChildId() {
    224         return String.valueOf(mChildCounter++);
    225     }
    226 
    227     // Builds full session id recursively
    228     private String getFullSessionId() {
    229         // Cache mParentSession locally to prevent a concurrency problem where
    230         // Log.endParentSessions() is called while a logging statement is running (Log.i, for
    231         // example) and setting mParentSession to null in a different thread after the null check
    232         // occurred.
    233         Session parentSession = mParentSession;
    234         if (parentSession == null) {
    235             return mSessionId;
    236         } else {
    237             if (Log.VERBOSE) {
    238                 return parentSession.getFullSessionId() +
    239                         // Append "_X" to subsession to show subsession designation.
    240                         SESSION_SEPARATION_CHAR_CHILD + mSessionId;
    241             } else {
    242                 // Only worry about the base ID at the top of the tree.
    243                 return parentSession.getFullSessionId();
    244             }
    245 
    246         }
    247     }
    248 
    249     // Print out the full Session tree from any subsession node
    250     public String printFullSessionTree() {
    251         // Get to the top of the tree
    252         Session topNode = this;
    253         while (topNode.getParentSession() != null) {
    254             topNode = topNode.getParentSession();
    255         }
    256         return topNode.printSessionTree();
    257     }
    258 
    259     // Recursively move down session tree using DFS, but print out each node when it is reached.
    260     public String printSessionTree() {
    261         StringBuilder sb = new StringBuilder();
    262         printSessionTree(0, sb);
    263         return sb.toString();
    264     }
    265 
    266     private void printSessionTree(int tabI, StringBuilder sb) {
    267         sb.append(toString());
    268         for (Session child : mChildSessions) {
    269             sb.append("\n");
    270             for (int i = 0; i <= tabI; i++) {
    271                 sb.append("\t");
    272             }
    273             child.printSessionTree(tabI + 1, sb);
    274         }
    275     }
    276 
    277     // Recursively concatenate mShortMethodName with the parent Sessions to create full method
    278     // path. if truncatePath is set to true, all other external sessions (except for the most
    279     // recent) will be truncated to "..."
    280     public String getFullMethodPath(boolean truncatePath) {
    281         StringBuilder sb = new StringBuilder();
    282         getFullMethodPath(sb, truncatePath);
    283         return sb.toString();
    284     }
    285 
    286     private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath) {
    287         // Return cached value for method path. When returning the truncated path, recalculate the
    288         // full path without using the cached value.
    289         if (!TextUtils.isEmpty(mFullMethodPathCache) && !truncatePath) {
    290             sb.append(mFullMethodPathCache);
    291             return;
    292         }
    293         Session parentSession = getParentSession();
    294         boolean isSessionStarted = false;
    295         if (parentSession != null) {
    296             // Check to see if the session has been renamed yet. If it has not, then the session
    297             // has not been continued.
    298             isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName);
    299             parentSession.getFullMethodPath(sb, truncatePath);
    300             sb.append(SUBSESSION_SEPARATION_CHAR);
    301         }
    302         // Encapsulate the external session's method name so it is obvious what part of the session
    303         // is external or truncate it if we do not want the entire history.
    304         if (isExternal()) {
    305             if (truncatePath) {
    306                 sb.append(TRUNCATE_STRING);
    307             } else {
    308                 sb.append("(");
    309                 sb.append(mShortMethodName);
    310                 sb.append(")");
    311             }
    312         } else {
    313             sb.append(mShortMethodName);
    314         }
    315         // If we are returning the truncated path, do not save that path as the full path.
    316         if (isSessionStarted && !truncatePath) {
    317             // Cache this value so that we do not have to do this work next time!
    318             // We do not cache the value if the session being evaluated hasn't been continued yet.
    319             mFullMethodPathCache = sb.toString();
    320         }
    321     }
    322     // Recursively move to the top of the tree to see if the parent session is external.
    323     private boolean isSessionExternal() {
    324         if (getParentSession() == null) {
    325             return isExternal();
    326         } else {
    327             return getParentSession().isSessionExternal();
    328         }
    329     }
    330 
    331     @Override
    332     public int hashCode() {
    333         int result = mSessionId != null ? mSessionId.hashCode() : 0;
    334         result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0);
    335         result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32));
    336         result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32));
    337         result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0);
    338         result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0);
    339         result = 31 * result + (mIsCompleted ? 1 : 0);
    340         result = 31 * result + mChildCounter;
    341         result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0);
    342         result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0);
    343         return result;
    344     }
    345 
    346     @Override
    347     public boolean equals(Object o) {
    348         if (this == o) return true;
    349         if (o == null || getClass() != o.getClass()) return false;
    350 
    351         Session session = (Session) o;
    352 
    353         if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false;
    354         if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false;
    355         if (mIsCompleted != session.mIsCompleted) return false;
    356         if (mChildCounter != session.mChildCounter) return false;
    357         if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false;
    358         if (mSessionId != null ?
    359                 !mSessionId.equals(session.mSessionId) : session.mSessionId != null)
    360             return false;
    361         if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName)
    362                 : session.mShortMethodName != null)
    363             return false;
    364         if (mParentSession != null ? !mParentSession.equals(session.mParentSession)
    365                 : session.mParentSession != null)
    366             return false;
    367         if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions)
    368                 : session.mChildSessions != null)
    369             return false;
    370         return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo)
    371                 : session.mOwnerInfo == null;
    372 
    373     }
    374 
    375     @Override
    376     public String toString() {
    377         if (mParentSession != null && mIsStartedFromActiveSession) {
    378             // Log.startSession was called from within another active session. Use the parent's
    379             // Id instead of the child to reduce confusion.
    380             return mParentSession.toString();
    381         } else {
    382             StringBuilder methodName = new StringBuilder();
    383             methodName.append(getFullMethodPath(false /*truncatePath*/));
    384             if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
    385                 methodName.append("(InCall package: ");
    386                 methodName.append(mOwnerInfo);
    387                 methodName.append(")");
    388             }
    389             return methodName.toString() + "@" + getFullSessionId();
    390         }
    391     }
    392 }
    393