diff --git a/.classpath b/.classpath new file mode 100644 index 0000000000000000000000000000000000000000..adc2064a617b125af403a5c4294d3c3aea502d3e --- /dev/null +++ b/.classpath @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> + <classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/json/json-prebuilt.jar"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ddb0a2d48c183c5b8a2f200765c660a849e6ea62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +.settings diff --git a/.project b/.project new file mode 100644 index 0000000000000000000000000000000000000000..427863301aac5955b306a61e926e566bafb3eb4d --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>loganalysis</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..ead7a6da4963d2cece249b196b3b14f21aaee9bc --- /dev/null +++ b/Android.mk @@ -0,0 +1,41 @@ +# Copyright (C) 2013 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVACFLAGS += -g -Xlint + +LOCAL_MODULE := loganalysis +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := json-prebuilt junit + +include $(BUILD_HOST_JAVA_LIBRARY) + +# makefile rules to copy jars to HOST_OUT/tradefed +# so tradefed.sh can automatically add to classpath + +DEST_JAR := $(HOST_OUT)/tradefed/$(LOCAL_MODULE).jar +$(DEST_JAR): $(LOCAL_BUILT_MODULE) + $(copy-file-to-new-target) + +# this dependency ensure the above rule will be executed if module is built +$(LOCAL_INSTALLED_MODULE) : $(DEST_JAR) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/src/com/android/loganalysis/item/AnrItem.java b/src/com/android/loganalysis/item/AnrItem.java new file mode 100644 index 0000000000000000000000000000000000000000..d6bbae3de64f1c8643cef8dde9b7a96dcaa4d2e9 --- /dev/null +++ b/src/com/android/loganalysis/item/AnrItem.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2012 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.loganalysis.item; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * An {@link IItem} used to store ANR info. + */ +public class AnrItem extends GenericLogcatItem { + public static final String TYPE = "ANR"; + + /** + * An enum used to select the CPU usage category. + */ + public enum CpuUsageCategory { + TOTAL, + USER, + KERNEL, + IOWAIT, + } + + /** + * An enum used to select the load category. + */ + public enum LoadCategory { + LOAD_1, + LOAD_5, + LOAD_15; + } + + private static final String ACTIVITY = "ACTIVITY"; + private static final String REASON = "REASON"; + private static final String STACK = "STACK"; + private static final String TRACE = "TRACE"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + CpuUsageCategory.TOTAL.toString(), + CpuUsageCategory.USER.toString(), + CpuUsageCategory.KERNEL.toString(), + CpuUsageCategory.IOWAIT.toString(), + LoadCategory.LOAD_1.toString(), + LoadCategory.LOAD_5.toString(), + LoadCategory.LOAD_15.toString(), + ACTIVITY, REASON, STACK, TRACE)); + + /** + * The constructor for {@link AnrItem}. + */ + public AnrItem() { + super(TYPE, ATTRIBUTES); + } + + /** + * Get the CPU usage for a given category. + */ + public Double getCpuUsage(CpuUsageCategory category) { + return (Double) getAttribute(category.toString()); + } + + /** + * Set the CPU usage for a given category. + */ + public void setCpuUsage(CpuUsageCategory category, Double usage) { + setAttribute(category.toString(), usage); + } + + /** + * Get the load for a given category. + */ + public Double getLoad(LoadCategory category) { + return (Double) getAttribute(category.toString()); + } + + /** + * Set the load for a given category. + */ + public void setLoad(LoadCategory category, Double usage) { + setAttribute(category.toString(), usage); + } + + /** + * Get the activity for the ANR. + */ + public String getActivity() { + return (String) getAttribute(ACTIVITY); + } + + /** + * Set the activity for the ANR. + */ + public void setActivity(String activity) { + setAttribute(ACTIVITY, activity); + } + + /** + * Get the reason for the ANR. + */ + public String getReason() { + return (String) getAttribute(REASON); + } + + /** + * Set the reason for the ANR. + */ + public void setReason(String reason) { + setAttribute(REASON, reason); + } + + /** + * Get the stack for the ANR. + */ + public String getStack() { + return (String) getAttribute(STACK); + } + + /** + * Set the stack for the ANR. + */ + public void setStack(String stack) { + setAttribute(STACK, stack); + } + + /** + * Get the main trace for the ANR. + */ + public String getTrace() { + return (String) getAttribute(TRACE); + } + + /** + * Set the main trace for the ANR. + */ + public void setTrace(String trace) { + setAttribute(TRACE, trace); + } +} diff --git a/src/com/android/loganalysis/item/BugreportItem.java b/src/com/android/loganalysis/item/BugreportItem.java new file mode 100644 index 0000000000000000000000000000000000000000..11a41fe6d67d5ff50271caae8fc2251670df2c65 --- /dev/null +++ b/src/com/android/loganalysis/item/BugreportItem.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 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.loganalysis.item; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * An {@link IItem} used to store Bugreport info. + */ +public class BugreportItem extends GenericItem { + public static final String TYPE = "BUGREPORT"; + + private static final String TIME = "TIME"; + private static final String MEM_INFO = "MEM_INFO"; + private static final String PROCRANK = "PROCRANK"; + private static final String SYSTEM_LOG = "SYSTEM_LOG"; + private static final String SYSTEM_PROPS = "SYSTEM_PROPS"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + TIME, MEM_INFO, PROCRANK, SYSTEM_LOG, SYSTEM_PROPS)); + + /** + * The constructor for {@link BugreportItem}. + */ + public BugreportItem() { + super(TYPE, ATTRIBUTES); + } + + /** + * Get the time of the bugreport. + */ + public Date getTime() { + return (Date) getAttribute(TIME); + } + + /** + * Set the time of the bugreport. + */ + public void setTime(Date time) { + setAttribute(TIME, time); + } + + /** + * Get the {@link MemInfoItem} of the bugreport. + */ + public MemInfoItem getMemInfo() { + return (MemInfoItem) getAttribute(MEM_INFO); + } + + /** + * Set the {@link MemInfoItem} of the bugreport. + */ + public void setMemInfo(MemInfoItem memInfo) { + setAttribute(MEM_INFO, memInfo); + } + + /** + * Get the {@link ProcrankItem} of the bugreport. + */ + public ProcrankItem getProcrank() { + return (ProcrankItem) getAttribute(PROCRANK); + } + + /** + * Set the {@link ProcrankItem} of the bugreport. + */ + public void setProcrank(ProcrankItem procrank) { + setAttribute(PROCRANK, procrank); + } + + /** + * Get the {@link LogcatItem} of the bugreport. + */ + public LogcatItem getSystemLog() { + return (LogcatItem) getAttribute(SYSTEM_LOG); + } + + /** + * Set the {@link LogcatItem} of the bugreport. + */ + public void setSystemLog(LogcatItem systemLog) { + setAttribute(SYSTEM_LOG, systemLog); + } + + /** + * Get the {@link SystemPropsItem} of the bugreport. + */ + public SystemPropsItem getSystemProps() { + return (SystemPropsItem) getAttribute(SYSTEM_PROPS); + } + + /** + * Set the {@link SystemPropsItem} of the bugreport. + */ + public void setSystemProps(SystemPropsItem systemProps) { + setAttribute(SYSTEM_PROPS, systemProps); + } +} diff --git a/src/com/android/loganalysis/item/ConflictingItemException.java b/src/com/android/loganalysis/item/ConflictingItemException.java new file mode 100644 index 0000000000000000000000000000000000000000..75d1e33213743e5795e6e9f66ffe186f638b2dbb --- /dev/null +++ b/src/com/android/loganalysis/item/ConflictingItemException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +/** + * Thrown if there is conflicting information when trying to combine two items. + */ +public class ConflictingItemException extends Exception { + + private static final long serialVersionUID = 3303627598068792143L; + + /** + * Creates a {@link ConflictingItemException}. + * + * @param message The reason for the conflict. + */ + ConflictingItemException(String message) { + super(message); + } +} diff --git a/src/com/android/loganalysis/item/GenericItem.java b/src/com/android/loganalysis/item/GenericItem.java new file mode 100644 index 0000000000000000000000000000000000000000..7da3662bc363090c193449ac272559021b9e5fa8 --- /dev/null +++ b/src/com/android/loganalysis/item/GenericItem.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of the {@link IItem} interface which implements helper methods. + */ +public class GenericItem implements IItem { + private Map<String, Object> mAttributes = new HashMap<String, Object>(); + private Set<String> mAllowedAttributes; + private String mType = null; + + protected GenericItem(String type, Set<String> allowedAttributes) { + mAllowedAttributes = new HashSet<String>(); + mAllowedAttributes.addAll(allowedAttributes); + mType = type; + } + + protected GenericItem(String type, Set<String> allowedAttributes, + Map<String, Object> attributes) { + this(type, allowedAttributes); + + for (Map.Entry<String, Object> entry : attributes.entrySet()) { + setAttribute(entry.getKey(), entry.getValue()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getType() { + return mType; + } + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) throws ConflictingItemException { + if (this == other) { + return this; + } + if (other == null || getClass() != other.getClass()) { + throw new ConflictingItemException("Conflicting class types"); + } + + return new GenericItem(getType(), mAllowedAttributes, mergeAttributes(other)); + } + + /** + * Merges the attributes from the item and another and returns a Map of the merged attributes. + * <p> + * Goes through each field in the item preferring non-null attributes over null attributes. + * </p> + * + * @param other The other item + * @return A Map from Strings to Objects containing merged attributes. + * @throws ConflictingItemException If the two items are not consistent. + */ + protected Map<String, Object> mergeAttributes(IItem other) throws ConflictingItemException { + if (this == other) { + return mAttributes; + } + if (other == null || getClass() != other.getClass()) { + throw new ConflictingItemException("Conflicting class types"); + } + + GenericItem item = (GenericItem) other; + Map<String, Object> mergedAttributes = new HashMap<String, Object>(); + for (String attribute : mAllowedAttributes) { + mergedAttributes.put(attribute, + mergeObjects(getAttribute(attribute), item.getAttribute(attribute))); + } + return mergedAttributes; + } + + /** + * {@inhertiDoc} + */ + @Override + public boolean isConsistent(IItem other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + GenericItem item = (GenericItem) other; + for (String attribute : mAllowedAttributes) { + if (!areConsistent(getAttribute(attribute), item.getAttribute(attribute))) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + GenericItem item = (GenericItem) other; + for (String attribute : mAllowedAttributes) { + if (!areEqual(getAttribute(attribute), item.getAttribute(attribute))) { + return false; + } + } + return true; + } + + /** + * Set an attribute to a value. + * + * @param attribute The name of the attribute. + * @param value The value. + * @throws IllegalArgumentException If the attribute is not in allowedAttributes. + */ + protected void setAttribute(String attribute, Object value) throws IllegalArgumentException { + if (!mAllowedAttributes.contains(attribute)) { + throw new IllegalArgumentException(); + } + mAttributes.put(attribute, value); + } + + /** + * Get the value of an attribute. + * + * @param attribute The name of the attribute. + * @return The value or null if the attribute has not been set. + * @throws IllegalArgumentException If the attribute is not in allowedAttributes. + */ + protected Object getAttribute(String attribute) throws IllegalArgumentException { + if (!mAllowedAttributes.contains(attribute)) { + throw new IllegalArgumentException(); + } + return mAttributes.get(attribute); + } + + /** + * Helper method to return if two objects are equal. + * + * @param object1 The first object + * @param object2 The second object + * @return True if object1 and object2 are both null or if object1 is equal to object2, false + * otherwise. + */ + static protected boolean areEqual(Object object1, Object object2) { + return object1 == null ? object2 == null : object1.equals(object2); + } + + /** + * Helper method to return if two objects are consistent. + * + * @param object1 The first object + * @param object2 The second object + * @return True if either object1 or object2 is null or if object1 is equal to object2, false if + * both objects are not null and not equal. + */ + static protected boolean areConsistent(Object object1, Object object2) { + return object1 == null || object2 == null ? true : object1.equals(object2); + } + + /** + * Helper method used for merging two objects. + * + * @param object1 The first object + * @param object2 The second object + * @return If both objects are null, then null, else the non-null item if both items are equal. + * @throws ConflictingItemException If both objects are not null and they are not equal. + */ + static protected Object mergeObjects(Object object1, Object object2) + throws ConflictingItemException { + if (!areConsistent(object1, object2)) { + throw new ConflictingItemException(String.format("%s conflicts with %s", object1, + object2)); + } + return object1 == null ? object2 : object1; + } +} diff --git a/src/com/android/loganalysis/item/GenericLogcatItem.java b/src/com/android/loganalysis/item/GenericLogcatItem.java new file mode 100644 index 0000000000000000000000000000000000000000..4a3642d867966f51168e91357257ff6acaa01df7 --- /dev/null +++ b/src/com/android/loganalysis/item/GenericLogcatItem.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2012 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.loganalysis.item; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * A generic item containing attributes for time, process, and thread and can be extended for + * items such as {@link AnrItem} and {@link JavaCrashItem}. + */ +public abstract class GenericLogcatItem extends GenericItem { + private static final String EVENT_TIME = "EVENT_TIME"; + private static final String PID = "PID"; + private static final String TID = "TID"; + private static final String APP = "APP"; + private static final String LAST_PREAMBLE = "LAST_PREAMBLE"; + private static final String PROC_PREAMBLE = "PROC_PREAMBLE"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + EVENT_TIME, PID, TID, APP, LAST_PREAMBLE, PROC_PREAMBLE)); + + /** + * Constructor for {@link GenericLogcatItem}. + * + * @param type The type of the item. + * @param attributes A list of allowed attributes. + */ + protected GenericLogcatItem(String type, Set<String> attributes) { + super(type, getAllAttributes(attributes)); + } + + /** + * Get the {@link Date} object when the event happened. + */ + public Date getEventTime() { + return (Date) getAttribute(EVENT_TIME); + } + + /** + * Set the {@link Date} object when the event happened. + */ + public void setEventTime(Date time) { + setAttribute(EVENT_TIME, time); + } + + /** + * Get the PID of the event. + */ + public Integer getPid() { + return (Integer) getAttribute(PID); + } + + /** + * Set the PID of the event. + */ + public void setPid(Integer pid) { + setAttribute(PID, pid); + } + + /** + * Get the TID of the event. + */ + public Integer getTid() { + return (Integer) getAttribute(TID); + } + + /** + * Set the TID of the event. + */ + public void setTid(Integer tid) { + setAttribute(TID, tid); + } + + /** + * Get the app or package name of the event. + */ + public String getApp() { + return (String) getAttribute(APP); + } + + /** + * Set the app or package name of the event. + */ + public void setApp(String app) { + setAttribute(APP, app); + } + + /** + * Get the last preamble for of the event. + */ + public String getLastPreamble() { + return (String) getAttribute(LAST_PREAMBLE); + } + + /** + * Set the last preamble for of the event. + */ + public void setLastPreamble(String preamble) { + setAttribute(LAST_PREAMBLE, preamble); + } + + /** + * Get the process preamble for of the event. + */ + public String getProcessPreamble() { + return (String) getAttribute(PROC_PREAMBLE); + } + + /** + * Set the process preamble for of the event. + */ + public void setProcessPreamble(String preamble) { + setAttribute(PROC_PREAMBLE, preamble); + } + + /** + * Combine an array of attributes with the internal list of attributes. + */ + private static Set<String> getAllAttributes(Set<String> attributes) { + Set<String> allAttributes = new HashSet<String>(ATTRIBUTES); + allAttributes.addAll(attributes); + return allAttributes; + } +} diff --git a/src/com/android/loganalysis/item/GenericMapItem.java b/src/com/android/loganalysis/item/GenericMapItem.java new file mode 100644 index 0000000000000000000000000000000000000000..e197db242decf1a153b9465abab11a13604bce3f --- /dev/null +++ b/src/com/android/loganalysis/item/GenericMapItem.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +import java.util.HashMap; + +/** + * An IItem that just represents a simple key/value map + */ +@SuppressWarnings("serial") +public class GenericMapItem<K, V> extends HashMap<K,V> implements IItem { + private String mType = null; + + /** + * No-op zero-arg constructor + */ + public GenericMapItem() {} + + /** + * Convenience constructor that sets the type + */ + public GenericMapItem(String type) { + setType(type); + } + + /** + * Set the self-reported type that this {@link GenericMapItem} represents. + */ + public void setType(String type) { + mType = type; + } + + /** + * {@inheritDoc} + */ + @Override + public String getType() { + return mType; + } + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) { + // FIXME + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConsistent(IItem other) { + // FIXME + return true; + } +} diff --git a/src/com/android/loganalysis/item/IItem.java b/src/com/android/loganalysis/item/IItem.java new file mode 100644 index 0000000000000000000000000000000000000000..d52ec5db6a2e5b3815452a20905a3d844499757a --- /dev/null +++ b/src/com/android/loganalysis/item/IItem.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +/** + * Interface for all items that are created by any parser. + */ +public interface IItem { + + /** + * Determine what type this IItem represents. May return {@code null} + */ + public String getType(); + + /** + * Merges the item and another into an item with the most complete information. + * + * <p> + * Goes through each field in the item preferring non-null fields over null fields. + * </p> + * + * @param other The other item + * @return The product of both items combined. + * @throws ConflictingItemException If the two items are not consistent. + */ + public IItem merge(IItem other) throws ConflictingItemException; + + /** + * Checks that the item and another are consistent. + * + * <p> + * Consistency means that no individual fields in either item conflict with the other. + * However, one item might contain more complete information. Two items of different types + * are never consistent. + * </p> + * + * @param other The other item. + * @return True if the objects are the same type and all the fields are either equal or one of + * the fields is null. + */ + public boolean isConsistent(IItem other); +} diff --git a/src/com/android/loganalysis/item/JavaCrashItem.java b/src/com/android/loganalysis/item/JavaCrashItem.java new file mode 100644 index 0000000000000000000000000000000000000000..042cf61d84b51d22a4b6d234c9248fa7b4f8578a --- /dev/null +++ b/src/com/android/loganalysis/item/JavaCrashItem.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 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.loganalysis.item; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * An {@link IItem} used to store Java crash info. + */ +public class JavaCrashItem extends GenericLogcatItem { + public static final String TYPE = "JAVA CRASH"; + + private static final String EXCEPTION = "EXCEPTION"; + private static final String MESSAGE = "MESSAGE"; + private static final String STACK = "STACK"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + EXCEPTION, MESSAGE, STACK)); + + /** + * The constructor for {@link JavaCrashItem}. + */ + public JavaCrashItem() { + super(TYPE, ATTRIBUTES); + } + + /** + * Get the exception for the Java crash. + */ + public String getException() { + return (String) getAttribute(EXCEPTION); + } + + /** + * Get the exception for the Java crash. + */ + public void setException(String exception) { + setAttribute(EXCEPTION, exception); + } + + /** + * Get the message for the Java crash. + */ + public String getMessage() { + return (String) getAttribute(MESSAGE); + } + + /** + * Set the message for the Java crash. + */ + public void setMessage(String message) { + setAttribute(MESSAGE, message); + } + + /** + * Get the stack for the ANR. + */ + public String getStack() { + return (String) getAttribute(STACK); + } + + /** + * Set the stack for the ANR. + */ + public void setStack(String stack) { + setAttribute(STACK, stack); + } +} diff --git a/src/com/android/loganalysis/item/LogcatItem.java b/src/com/android/loganalysis/item/LogcatItem.java new file mode 100644 index 0000000000000000000000000000000000000000..7bf42a2e57582f49ebc6fcc56cbc4f83944b10a2 --- /dev/null +++ b/src/com/android/loganalysis/item/LogcatItem.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2012 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.loganalysis.item; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * An {@link IItem} used to store logcat info. + */ +public class LogcatItem extends GenericItem { + public static final String TYPE = "LOGCAT"; + + private static final String START_TIME = "START_TIME"; + private static final String STOP_TIME = "STOP_TIME"; + private static final String EVENTS = "EVENTS"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + START_TIME, STOP_TIME, EVENTS)); + + private class ItemList extends LinkedList<IItem> { + private static final long serialVersionUID = 1088529764741812025L; + } + + /** + * The constructor for {@link LogcatItem}. + */ + public LogcatItem() { + super(TYPE, ATTRIBUTES); + + setAttribute(EVENTS, new ItemList()); + } + + /** + * Get the start time of the logcat. + */ + public Date getStartTime() { + return (Date) getAttribute(START_TIME); + } + + /** + * Set the start time of the logcat. + */ + public void setStartTime(Date time) { + setAttribute(START_TIME, time); + } + + /** + * Get the stop time of the logcat. + */ + public Date getStopTime() { + return (Date) getAttribute(STOP_TIME); + } + + /** + * Set the stop time of the logcat. + */ + public void setStopTime(Date time) { + setAttribute(STOP_TIME, time); + } + + /** + * Get the list of all {@link IItem} events. + */ + public List<IItem> getEvents() { + return (ItemList) getAttribute(EVENTS); + } + + /** + * Add an {@link IItem} event to the end of the list of events. + */ + public void addEvent(IItem event) { + ((ItemList) getAttribute(EVENTS)).add(event); + } + + /** + * Get the list of all {@link AnrItem} events. + */ + public List<AnrItem> getAnrs() { + List<AnrItem> anrs = new LinkedList<AnrItem>(); + for (IItem item : getEvents()) { + if (item instanceof AnrItem) { + anrs.add((AnrItem) item); + } + } + return anrs; + } + + /** + * Get the list of all {@link JavaCrashItem} events. + */ + public List<JavaCrashItem> getJavaCrashes() { + List<JavaCrashItem> jcs = new LinkedList<JavaCrashItem>(); + for (IItem item : getEvents()) { + if (item instanceof JavaCrashItem) { + jcs.add((JavaCrashItem) item); + } + } + return jcs; + } + + /** + * Get the list of all {@link NativeCrashItem} events. + */ + public List<NativeCrashItem> getNativeCrashes() { + List<NativeCrashItem> ncs = new LinkedList<NativeCrashItem>(); + for (IItem item : getEvents()) { + if (item instanceof NativeCrashItem) { + ncs.add((NativeCrashItem) item); + } + } + return ncs; + } +} diff --git a/src/com/android/loganalysis/item/MemInfoItem.java b/src/com/android/loganalysis/item/MemInfoItem.java new file mode 100644 index 0000000000000000000000000000000000000000..3246cba6e3dcf889935afeaf40defeaec6913600 --- /dev/null +++ b/src/com/android/loganalysis/item/MemInfoItem.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +/** + * An {@link IItem} used to store the memory info output. + */ +public class MemInfoItem extends GenericMapItem<String, Integer> { + private static final long serialVersionUID = 2648395553885243585L; + + public static final String TYPE = "MEMORY INFO"; + + public MemInfoItem() { + super(TYPE); + } +} diff --git a/src/com/android/loganalysis/item/MonkeyLogItem.java b/src/com/android/loganalysis/item/MonkeyLogItem.java new file mode 100644 index 0000000000000000000000000000000000000000..4f89917d852962f07cd36472e97657985c4fa8b3 --- /dev/null +++ b/src/com/android/loganalysis/item/MonkeyLogItem.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2012 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.loganalysis.item; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * An {@link IItem} used to store monkey log info. + */ +public class MonkeyLogItem extends GenericItem { + + private class StringSet extends HashSet<String> { + private static final long serialVersionUID = -2206822563602989856L; + } + + public enum DroppedCategory { + KEYS, + POINTERS, + TRACKBALLS, + FLIPS, + ROTATIONS + } + + private static final String TYPE = "MONKEY_LOG"; + + private static final String START_TIME = "START_TIME"; + private static final String STOP_TIME = "STOP_TIME"; + private static final String PACKAGES = "PACKAGES"; + private static final String CATEGORIES = "CATEGORIES"; + private static final String THROTTLE = "THROTTLE"; + private static final String SEED = "SEED"; + private static final String TARGET_COUNT = "TARGET_COUNT"; + private static final String IGNORE_SECURITY_EXCEPTIONS = "IGNORE_SECURITY_EXCEPTIONS"; + private static final String TOTAL_DURATION = "TOTAL_TIME"; + private static final String START_UPTIME_DURATION = "START_UPTIME"; + private static final String STOP_UPTIME_DURATION = "STOP_UPTIME"; + private static final String IS_FINISHED = "IS_FINISHED"; + private static final String NO_ACTIVITIES = "NO_ACTIVITIES"; + private static final String INTERMEDIATE_COUNT = "INTERMEDIATE_COUNT"; + private static final String FINAL_COUNT = "FINAL_COUNT"; + private static final String CRASH = "CRASH"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + START_TIME, STOP_TIME, PACKAGES, CATEGORIES, THROTTLE, SEED, TARGET_COUNT, + IGNORE_SECURITY_EXCEPTIONS, TOTAL_DURATION, START_UPTIME_DURATION, STOP_UPTIME_DURATION, + IS_FINISHED, NO_ACTIVITIES, INTERMEDIATE_COUNT, FINAL_COUNT, CRASH, + DroppedCategory.KEYS.toString(), + DroppedCategory.POINTERS.toString(), + DroppedCategory.TRACKBALLS.toString(), + DroppedCategory.FLIPS.toString(), + DroppedCategory.ROTATIONS.toString())); + + /** + * The constructor for {@link MonkeyLogItem}. + */ + public MonkeyLogItem() { + super(TYPE, ATTRIBUTES); + + setAttribute(PACKAGES, new StringSet()); + setAttribute(CATEGORIES, new StringSet()); + setAttribute(THROTTLE, 0); + setAttribute(IGNORE_SECURITY_EXCEPTIONS, false); + setAttribute(IS_FINISHED, false); + setAttribute(NO_ACTIVITIES, false); + setAttribute(INTERMEDIATE_COUNT, 0); + } + + /** + * Get the start time of the monkey log. + */ + public Date getStartTime() { + return (Date) getAttribute(START_TIME); + } + + /** + * Set the start time of the monkey log. + */ + public void setStartTime(Date time) { + setAttribute(START_TIME, time); + } + + /** + * Get the stop time of the monkey log. + */ + public Date getStopTime() { + return (Date) getAttribute(STOP_TIME); + } + + /** + * Set the stop time of the monkey log. + */ + public void setStopTime(Date time) { + setAttribute(STOP_TIME, time); + } + + /** + * Get the set of packages that the monkey is run on. + */ + public Set<String> getPackages() { + return (StringSet) getAttribute(PACKAGES); + } + + /** + * Add a package to the set that the monkey is run on. + */ + public void addPackage(String thePackage) { + ((StringSet) getAttribute(PACKAGES)).add(thePackage); + } + + /** + * Get the set of categories that the monkey is run on. + */ + public Set<String> getCategories() { + return (StringSet) getAttribute(CATEGORIES); + } + + /** + * Add a category to the set that the monkey is run on. + */ + public void addCategory(String category) { + ((StringSet) getAttribute(CATEGORIES)).add(category); + } + + /** + * Get the throttle for the monkey run. + */ + public int getThrottle() { + return (Integer) getAttribute(THROTTLE); + } + + /** + * Set the throttle for the monkey run. + */ + public void setThrottle(int throttle) { + setAttribute(THROTTLE, throttle); + } + + /** + * Get the seed for the monkey run. + */ + public Integer getSeed() { + return (Integer) getAttribute(SEED); + } + + /** + * Set the seed for the monkey run. + */ + public void setSeed(int seed) { + setAttribute(SEED, seed); + } + + /** + * Get the target count for the monkey run. + */ + public Integer getTargetCount() { + return (Integer) getAttribute(TARGET_COUNT); + } + + /** + * Set the target count for the monkey run. + */ + public void setTargetCount(int count) { + setAttribute(TARGET_COUNT, count); + } + + /** + * Get if the ignore security exceptions flag is set for the monkey run. + */ + public boolean getIgnoreSecurityExceptions() { + return (Boolean) getAttribute(IGNORE_SECURITY_EXCEPTIONS); + } + + /** + * Set if the ignore security exceptions flag is set for the monkey run. + */ + public void setIgnoreSecurityExceptions(boolean ignore) { + setAttribute(IGNORE_SECURITY_EXCEPTIONS, ignore); + } + + /** + * Get the total duration of the monkey run in milliseconds. + */ + public Long getTotalDuration() { + return (Long) getAttribute(TOTAL_DURATION); + } + + /** + * Set the total duration of the monkey run in milliseconds. + */ + public void setTotalDuration(long time) { + setAttribute(TOTAL_DURATION, time); + } + + /** + * Get the start uptime duration of the monkey run in milliseconds. + */ + public Long getStartUptimeDuration() { + return (Long) getAttribute(START_UPTIME_DURATION); + } + + /** + * Set the start uptime duration of the monkey run in milliseconds. + */ + public void setStartUptimeDuration(long uptime) { + setAttribute(START_UPTIME_DURATION, uptime); + } + + /** + * Get the stop uptime duration of the monkey run in milliseconds. + */ + public Long getStopUptimeDuration() { + return (Long) getAttribute(STOP_UPTIME_DURATION); + } + + /** + * Set the stop uptime duration of the monkey run in milliseconds. + */ + public void setStopUptimeDuration(long uptime) { + setAttribute(STOP_UPTIME_DURATION, uptime); + } + + /** + * Get if the monkey run finished without crashing. + */ + public boolean getIsFinished() { + return (Boolean) getAttribute(IS_FINISHED); + } + + /** + * Set if the monkey run finished without crashing. + */ + public void setIsFinished(boolean finished) { + setAttribute(IS_FINISHED, finished); + } + + /** + * Get if the monkey run aborted due to no activies to run. + */ + public boolean getNoActivities() { + return (Boolean) getAttribute(NO_ACTIVITIES); + } + + /** + * Set if the monkey run aborted due to no activies to run. + */ + public void setNoActivities(boolean noActivities) { + setAttribute(NO_ACTIVITIES, noActivities); + } + + + /** + * Get the intermediate count for the monkey run. + * <p> + * This count starts at 0 and increments every 100 events. This number should be within 100 of + * the final count. + * </p> + */ + public int getIntermediateCount() { + return (Integer) getAttribute(INTERMEDIATE_COUNT); + } + + /** + * Set the intermediate count for the monkey run. + * <p> + * This count starts at 0 and increments every 100 events. This number should be within 100 of + * the final count. + * </p> + */ + public void setIntermediateCount(int count) { + setAttribute(INTERMEDIATE_COUNT, count); + } + + /** + * Get the final count for the monkey run. + */ + public Integer getFinalCount() { + return (Integer) getAttribute(FINAL_COUNT); + } + + /** + * Set the final count for the monkey run. + */ + public void setFinalCount(int count) { + setAttribute(FINAL_COUNT, count); + } + + /** + * Get the dropped events count for a {@link DroppedCategory} for the monkey run. + */ + public Integer getDroppedCount(DroppedCategory category) { + return (Integer) getAttribute(category.toString()); + } + + /** + * Set the dropped events count for a {@link DroppedCategory} for the monkey run. + */ + public void setDroppedCount(DroppedCategory category, int count) { + setAttribute(category.toString(), count); + } + + /** + * Get the {@link AnrItem} or {@link JavaCrashItem} for the monkey run or null if there was no + * crash. + */ + public GenericLogcatItem getCrash() { + return (GenericLogcatItem) getAttribute(CRASH); + } + + /** + * Set the {@link AnrItem} or {@link JavaCrashItem} for the monkey run. + */ + public void setCrash(GenericLogcatItem crash) { + setAttribute(CRASH, crash); + } +} diff --git a/src/com/android/loganalysis/item/NativeCrashItem.java b/src/com/android/loganalysis/item/NativeCrashItem.java new file mode 100644 index 0000000000000000000000000000000000000000..e06e0a42c035630df24718a7058151e4326a4b76 --- /dev/null +++ b/src/com/android/loganalysis/item/NativeCrashItem.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 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.loganalysis.item; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * An {@link IItem} used to store native crash info. + */ +public class NativeCrashItem extends GenericLogcatItem { + public static final String TYPE = "NATIVE CRASH"; + + private static final String FINGERPRINT = "FINGERPRINT"; + private static final String STACK = "STACK"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + FINGERPRINT, STACK)); + + /** + * The constructor for {@link NativeCrashItem}. + */ + public NativeCrashItem() { + super(TYPE, ATTRIBUTES); + } + + /** + * Get the fingerprint for the crash. + */ + public String getFingerprint() { + return (String) getAttribute(FINGERPRINT); + } + + /** + * Set the fingerprint for the crash. + */ + public void setFingerprint(String fingerprint) { + setAttribute(FINGERPRINT, fingerprint); + } + + /** + * Get the stack for the crash. + */ + public String getStack() { + return (String) getAttribute(STACK); + } + + /** + * Set the stack for the crash. + */ + public void setStack(String stack) { + setAttribute(STACK, stack); + } +} diff --git a/src/com/android/loganalysis/item/ProcrankItem.java b/src/com/android/loganalysis/item/ProcrankItem.java new file mode 100644 index 0000000000000000000000000000000000000000..a2d7e74552f6f5d5cec3a285e24d1912bc5dbfa2 --- /dev/null +++ b/src/com/android/loganalysis/item/ProcrankItem.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + + +/** + * An {@link IItem} used to procrank info. + */ +public class ProcrankItem implements IItem { + public static final String TYPE = "PROCRANK"; + + private class ProcrankValue { + public String mProcessName = null; + public int mVss; + public int mRss; + public int mPss; + public int mUss; + + public ProcrankValue(String processName, int vss, int rss, int pss, int uss) { + mProcessName = processName; + mVss = vss; + mRss = rss; + mPss = pss; + mUss = uss; + } + } + + private Map<Integer, ProcrankValue> mProcrankLines = new HashMap<Integer, ProcrankValue>(); + + /** + * Add a line from the procrank output to the {@link ProcrankItem}. + * + * @param pid The PID from the output + * @param processName The process name from the cmdline column + * @param vss The VSS in KB + * @param rss The RSS in KB + * @param pss The PSS in KB + * @param uss The USS in KB + */ + public void addProcrankLine(int pid, String processName, int vss, int rss, int pss, int uss) { + mProcrankLines.put(pid, new ProcrankValue(processName, vss, rss, pss, uss)); + } + + /** + * Get a set of PIDs seen in the procrank output. + */ + public Set<Integer> getPids() { + return mProcrankLines.keySet(); + } + + /** + * Get the process name for a given PID. + */ + public String getProcessName(int pid) { + if (!mProcrankLines.containsKey(pid)) { + return null; + } + + return mProcrankLines.get(pid).mProcessName; + } + + /** + * Get the VSS for a given PID. + */ + public Integer getVss(int pid) { + if (!mProcrankLines.containsKey(pid)) { + return null; + } + + return mProcrankLines.get(pid).mVss; + } + + /** + * Get the RSS for a given PID. + */ + public Integer getRss(int pid) { + if (!mProcrankLines.containsKey(pid)) { + return null; + } + + return mProcrankLines.get(pid).mRss; + } + + /** + * Get the PSS for a given PID. + */ + public Integer getPss(int pid) { + if (!mProcrankLines.containsKey(pid)) { + return null; + } + + return mProcrankLines.get(pid).mPss; + } + + /** + * Get the USS for a given PID. + */ + public Integer getUss(int pid) { + if (!mProcrankLines.containsKey(pid)) { + return null; + } + + return mProcrankLines.get(pid).mUss; + } + + /** + * {@inheritDoc} + */ + @Override + public String getType() { + return TYPE; + } + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) throws ConflictingItemException { + throw new ConflictingItemException("Procrank items cannot be merged"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConsistent(IItem other) { + return false; + } +} diff --git a/src/com/android/loganalysis/item/SystemPropsItem.java b/src/com/android/loganalysis/item/SystemPropsItem.java new file mode 100644 index 0000000000000000000000000000000000000000..67e8de8b53e24011a9877337317d2acb9e186acc --- /dev/null +++ b/src/com/android/loganalysis/item/SystemPropsItem.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +/** + * An {@link IItem} used to store the system props info. + */ +public class SystemPropsItem extends GenericMapItem<String, String> { + + private static final long serialVersionUID = 7280770512647682477L; + + public static final String TYPE = "SYSTEM PROPERTIES"; + + public SystemPropsItem() { + super(TYPE); + } +} diff --git a/src/com/android/loganalysis/item/TracesItem.java b/src/com/android/loganalysis/item/TracesItem.java new file mode 100644 index 0000000000000000000000000000000000000000..1dfff7639e79f5bbf3c0cd8d9310627bfa1bf891 --- /dev/null +++ b/src/com/android/loganalysis/item/TracesItem.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + + +/** + * An {@link IItem} used to store traces info. + * <p> + * For now, this only stores info about the main stack trace from the first process. It is used to + * get a stack from {@code /data/anr/traces.txt} which can be used to give some context about the + * ANR. If there is a need, this item can be expanded to store all stacks from all processes. + * </p> + */ +public class TracesItem extends GenericItem { + public static final String TYPE = "TRACES_ITEM"; + + private static final String PID = "PID"; + private static final String APP = "APP"; + private static final String STACK = "STACK"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + PID, APP, STACK)); + + /** + * The constructor for {@link TracesItem}. + */ + public TracesItem() { + super(TYPE, ATTRIBUTES); + } + + /** + * Get the PID of the event. + */ + public Integer getPid() { + return (Integer) getAttribute(PID); + } + + /** + * Set the PID of the event. + */ + public void setPid(Integer pid) { + setAttribute(PID, pid); + } + + /** + * Get the app or package name of the event. + */ + public String getApp() { + return (String) getAttribute(APP); + } + + /** + * Set the app or package name of the event. + */ + public void setApp(String app) { + setAttribute(APP, app); + } + + /** + * Get the stack for the crash. + */ + public String getStack() { + return (String) getAttribute(STACK); + } + + /** + * Set the stack for the crash. + */ + public void setStack(String stack) { + setAttribute(STACK, stack); + } +} diff --git a/src/com/android/loganalysis/parser/AbstractSectionParser.java b/src/com/android/loganalysis/parser/AbstractSectionParser.java new file mode 100644 index 0000000000000000000000000000000000000000..c57546d4506edfbfd876ee525c7b2d38de05a4a0 --- /dev/null +++ b/src/com/android/loganalysis/parser/AbstractSectionParser.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.IItem; +import com.android.loganalysis.util.RegexTrie; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * A {@link IParser} that splits an input file into discrete sections and passes each section to an + * {@link IParser} to parse. + * <p> + * Before parsing input, {@link IParser}s can be added with + * {@link #addSectionParser(IParser, String)}. The default parser is {@link NoopParser} but this can + * be overwritten by calling {@link #setParser(IParser)} before parsing the input. + * </p> + */ +public abstract class AbstractSectionParser implements IParser { + private RegexTrie<IParser> mSectionTrie = new RegexTrie<IParser>(); + private IParser mCurrentParser = new NoopParser(); + private List<String> mParseBlock = new LinkedList<String>(); + private Map<String, IItem> mSections = new HashMap<String, IItem>(); + + /** + * A method to add a given section parser to the set of potential parsers to use. + * + * @param parser The {@link IParser} to add + * @param pattern The regular expression to trigger this parser + */ + protected void addSectionParser(IParser parser, String pattern) { + if (parser == null) { + throw new NullPointerException("Parser is null"); + } + if (pattern == null) { + throw new NullPointerException("Pattern is null"); + } + mSectionTrie.put(parser, pattern); + } + + /** + * Parse a line of input, either adding the input to the current block or switching parsers and + * running the current parser. + * + * @param line The line to parse + */ + protected void parseLine(String line) { + IParser nextParser = mSectionTrie.retrieve(line); + + if (nextParser == null) { + // no match, so buffer this for the current parser, if there is one + if (mCurrentParser != null) { + mParseBlock.add(line); + } else { + // CLog.w("Line outside of parsed section: %s", line); + } + } else { + runCurrentParser(); + mParseBlock.clear(); + mCurrentParser = nextParser; + + onSwitchParser(); + } + } + + /** + * Signal that the input has finished and run the last parser. + */ + protected void commit() { + runCurrentParser(); + } + + /** + * Gets the {@link IItem} for a given section. + * + * @param section The {@link IItem} type for the section. + * @return The {@link IItem}. + */ + protected IItem getSection(String section) { + return mSections.get(section); + } + + /** + * Set the {@link IParser}. Used to set the initial parser. + * + * @param parser The {@link IParser} to set. + */ + protected void setParser(IParser parser) { + mCurrentParser = parser; + } + + protected void onSwitchParser() { + } + + /** + * Run the current parser and add the {@link IItem} to the sections map. + */ + private void runCurrentParser() { + if (mCurrentParser != null) { + IItem item = mCurrentParser.parse(mParseBlock); + if (item != null && !(mCurrentParser instanceof NoopParser)) { + mSections.put(item.getType(), item); + // CLog.v("Just ran the %s parser", mCurrentParser.getClass().getSimpleName()); + } + } + } +} + diff --git a/src/com/android/loganalysis/parser/AnrParser.java b/src/com/android/loganalysis/parser/AnrParser.java new file mode 100644 index 0000000000000000000000000000000000000000..5102436d75a6aa4e4d544f1fb9e774c7c87f2d16 --- /dev/null +++ b/src/com/android/loganalysis/parser/AnrParser.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.AnrItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An {@link IParser} to handle ANRs. + */ +public class AnrParser implements IParser { + /** + * Matches: ANR (application not responding) in process: app + * Matches: ANR in app + * Matches: ANR in app (class/package) + */ + public static final Pattern START = Pattern.compile( + "^ANR (?:\\(application not responding\\) )?in (?:process: )?(\\S+).*$"); + /** + * Matches: Reason: reason + */ + private static final Pattern REASON = Pattern.compile("^Reason: (.*)$"); + /** + * Matches: Load: 0.71 / 0.83 / 0.51 + */ + private static final Pattern LOAD = Pattern.compile( + "^Load: (\\d+\\.\\d+) / (\\d+\\.\\d+) / (\\d+\\.\\d+)$"); + + /** + * Matches: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait + */ + private static final Pattern TOTAL = Pattern.compile("^(\\d+(\\.\\d+)?)% TOTAL: .*$"); + private static final Pattern USER = Pattern.compile("^.* (\\d+(\\.\\d+)?)% user.*$"); + private static final Pattern KERNEL = Pattern.compile("^.* (\\d+(\\.\\d+)?)% kernel.*$"); + private static final Pattern IOWAIT = Pattern.compile("^.* (\\d+(\\.\\d+)?)% iowait.*$"); + + /** + * {@inheritDoc} + * + * @return The {@link AnrItem}. + */ + @Override + public AnrItem parse(List<String> lines) { + AnrItem anr = null; + StringBuilder stack = new StringBuilder(); + boolean matchedTotal = false; + + for (String line : lines) { + Matcher m = START.matcher(line); + // Ignore all input until the start pattern is matched. + if (m.matches()) { + anr = new AnrItem(); + anr.setApp(m.group(1)); + } + + if (anr != null) { + m = REASON.matcher(line); + if (m.matches()) { + anr.setReason(m.group(1)); + } + + m = LOAD.matcher(line); + if (m.matches()) { + anr.setLoad(AnrItem.LoadCategory.LOAD_1, Double.parseDouble(m.group(1))); + anr.setLoad(AnrItem.LoadCategory.LOAD_5, Double.parseDouble(m.group(2))); + anr.setLoad(AnrItem.LoadCategory.LOAD_15, Double.parseDouble(m.group(3))); + } + + m = TOTAL.matcher(line); + if (!matchedTotal && m.matches()) { + matchedTotal = true; + anr.setCpuUsage(AnrItem.CpuUsageCategory.TOTAL, Double.parseDouble(m.group(1))); + + m = USER.matcher(line); + Double usage = m.matches() ? Double.parseDouble(m.group(1)) : 0.0; + anr.setCpuUsage(AnrItem.CpuUsageCategory.USER, usage); + + m = KERNEL.matcher(line); + usage = m.matches() ? Double.parseDouble(m.group(1)) : 0.0; + anr.setCpuUsage(AnrItem.CpuUsageCategory.KERNEL, usage); + + m = IOWAIT.matcher(line); + usage = m.matches() ? Double.parseDouble(m.group(1)) : 0.0; + anr.setCpuUsage(AnrItem.CpuUsageCategory.IOWAIT, usage); + } + + stack.append(line); + stack.append("\n"); + } + } + + if (anr != null) { + anr.setStack(stack.toString().trim()); + } + return anr; + } +} + diff --git a/src/com/android/loganalysis/parser/BugreportParser.java b/src/com/android/loganalysis/parser/BugreportParser.java new file mode 100644 index 0000000000000000000000000000000000000000..748f751838edd237e25df299faf6370fb5c3a009 --- /dev/null +++ b/src/com/android/loganalysis/parser/BugreportParser.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.AnrItem; +import com.android.loganalysis.item.BugreportItem; +import com.android.loganalysis.item.GenericLogcatItem; +import com.android.loganalysis.item.IItem; +import com.android.loganalysis.item.LogcatItem; +import com.android.loganalysis.item.MemInfoItem; +import com.android.loganalysis.item.ProcrankItem; +import com.android.loganalysis.item.SystemPropsItem; +import com.android.loganalysis.item.TracesItem; + +import java.io.BufferedReader; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.ListIterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to parse Android bugreports. + */ +public class BugreportParser extends AbstractSectionParser { + private static final String MEM_INFO_SECTION_REGEX = "------ MEMORY INFO .*"; + private static final String PROCRANK_SECTION_REGEX = "------ PROCRANK .*"; + private static final String SYSTEM_PROP_SECTION_REGEX = "------ SYSTEM PROPERTIES .*"; + private static final String SYSTEM_LOG_SECTION_REGEX = + "------ (SYSTEM|MAIN|MAIN AND SYSTEM) LOG .*"; + private static final String ANR_TRACES_SECTION_REGEX = "------ VM TRACES AT LAST ANR .*"; + private static final String NOOP_SECTION_REGEX = "------ .*"; + + /** + * Matches: == dumpstate: 2012-04-26 12:13:14 + */ + private static final Pattern DATE = Pattern.compile( + "^== dumpstate: (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})$"); + + private LogcatParser mLogcatParser = new LogcatParser(); + private BugreportItem mBugreport = null; + + /** + * Parse a bugreport from a {@link BufferedReader} into an {@link BugreportItem} object. + * + * @param input a {@link BufferedReader}. + * @return The {@link BugreportItem}. + * @see #parse(List) + */ + public BugreportItem parse(BufferedReader input) throws IOException { + String line; + + setup(); + while ((line = input.readLine()) != null) { + parseLine(line); + } + commit(); + + return mBugreport; + } + + /** + * {@inheritDoc} + * + * @return The {@link BugreportItem}. + */ + @Override + public BugreportItem parse(List<String> lines) { + setup(); + for (String line : lines) { + parseLine(line); + } + commit(); + + return mBugreport; + } + + /** + * Sets up the parser by adding the section parsers and adding an initial {@link IParser} to + * parse the bugreport header. + */ + protected void setup() { + // Set the initial parser explicitly since the header isn't part of a section. + setParser(new IParser() { + @Override + public BugreportItem parse(List<String> lines) { + BugreportItem bugreport = new BugreportItem(); + for (String line : lines) { + Matcher m = DATE.matcher(line); + if (m.matches()) { + bugreport.setTime(parseTime(m.group(1))); + } + } + return bugreport; + } + }); + addSectionParser(new MemInfoParser(), MEM_INFO_SECTION_REGEX); + addSectionParser(new ProcrankParser(), PROCRANK_SECTION_REGEX); + addSectionParser(new SystemPropsParser(), SYSTEM_PROP_SECTION_REGEX); + addSectionParser(new TracesParser(), ANR_TRACES_SECTION_REGEX); + addSectionParser(mLogcatParser, SYSTEM_LOG_SECTION_REGEX); + addSectionParser(new NoopParser(), NOOP_SECTION_REGEX); + } + + /** + * {@inheritDoc} + */ + @Override + protected void commit() { + // signal EOF + super.commit(); + + if (mBugreport != null) { + mBugreport.setMemInfo((MemInfoItem) getSection(MemInfoItem.TYPE)); + mBugreport.setProcrank((ProcrankItem) getSection(ProcrankItem.TYPE)); + mBugreport.setSystemLog((LogcatItem) getSection(LogcatItem.TYPE)); + mBugreport.setSystemProps((SystemPropsItem) getSection(SystemPropsItem.TYPE)); + + if (mBugreport.getSystemLog() != null && mBugreport.getProcrank() != null) { + for (IItem item : mBugreport.getSystemLog().getEvents()) { + if (item instanceof GenericLogcatItem && + ((GenericLogcatItem) item).getApp() == null) { + GenericLogcatItem logcatItem = (GenericLogcatItem) item; + logcatItem.setApp(mBugreport.getProcrank().getProcessName( + logcatItem.getPid())); + } + } + } + + TracesItem traces = (TracesItem) getSection(TracesItem.TYPE); + if (traces != null && traces.getApp() != null && traces.getStack() != null && + mBugreport.getSystemLog() != null) { + addAnrTrace(mBugreport.getSystemLog().getAnrs(), traces.getApp(), + traces.getStack()); + + } + } + } + + /** + * Add the trace from {@link TracesItem} to the last seen {@link AnrItem} matching a given app. + */ + private void addAnrTrace(List<AnrItem> anrs, String app, String trace) { + ListIterator<AnrItem> li = anrs.listIterator(anrs.size()); + + while (li.hasPrevious()) { + AnrItem anr = li.previous(); + if (app.equals(anr.getApp())) { + anr.setTrace(trace); + return; + } + } + } + + /** + * Set the {@link BugreportItem} and the year of the {@link LogcatParser} from the bugreport + * header. + */ + @Override + protected void onSwitchParser() { + if (mBugreport == null) { + mBugreport = (BugreportItem) getSection(BugreportItem.TYPE); + if (mBugreport.getTime() != null) { + mLogcatParser.setYear(new SimpleDateFormat("yyyy").format(mBugreport.getTime())); + } + } + } + + /** + * Converts a {@link String} into a {@link Date}. + */ + private static Date parseTime(String timeStr) { + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + return formatter.parse(timeStr); + } catch (ParseException e) { + // CLog.e("Could not parse time string %s", timeStr); + return null; + } + } +} + diff --git a/src/com/android/loganalysis/parser/IParser.java b/src/com/android/loganalysis/parser/IParser.java new file mode 100644 index 0000000000000000000000000000000000000000..43750d7ba1e20d95007480bae91bef2bacae54c0 --- /dev/null +++ b/src/com/android/loganalysis/parser/IParser.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.IItem; + +import java.util.List; + +/** + * An interface which defines the behavior for a parser. The parser will receive a block of data + * that it can consider complete. It parses the input and returns a single {@link IItem} instance. + * Furthermore, the parser should be robust against invalid input -- the input format may drift over + * time. + */ +public interface IParser { + + /** + * Parses a list of {@link String} objects and returns a {@link IItem}. + * + * @param lines A list of {@link String} objects. + * @return The parsed {@link IItem} object. + */ + public IItem parse(List<String> lines); +} + diff --git a/src/com/android/loganalysis/parser/JavaCrashParser.java b/src/com/android/loganalysis/parser/JavaCrashParser.java new file mode 100644 index 0000000000000000000000000000000000000000..f1c87a17a55349e5acb957b37e904bae907fd6fe --- /dev/null +++ b/src/com/android/loganalysis/parser/JavaCrashParser.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.JavaCrashItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An {@link IParser} to handle Java crashes. + */ +public class JavaCrashParser implements IParser { + + /** + * Matches: java.lang.Exception + * Matches: java.lang.Exception: reason + */ + private static final Pattern EXCEPTION = Pattern.compile("^([^\\s:]+)(: (.*))?$"); + /** + * Matches: Caused by: java.lang.Exception + */ + private static final Pattern CAUSEDBY = Pattern.compile("^Caused by: .+$"); + /** + * Matches: \tat class.method(Class.java:1) + */ + private static final Pattern AT = Pattern.compile("^\tat .+$"); + + /** + * {@inheritDoc} + * + * @return The {@link JavaCrashItem}. + */ + @Override + public JavaCrashItem parse(List<String> lines) { + JavaCrashItem jc = null; + StringBuilder stack = new StringBuilder(); + StringBuilder message = new StringBuilder(); + boolean inMessage = false; + boolean inCausedBy = false; + boolean inStack = false; + + for (String line : lines) { + if (!inStack) { + Matcher exceptionMatch = EXCEPTION.matcher(line); + if (exceptionMatch.matches()) { + inMessage = true; + inStack = true; + + jc = new JavaCrashItem(); + jc.setException(exceptionMatch.group(1)); + if (exceptionMatch.group(3) != null) { + message.append(exceptionMatch.group(3)); + } + } + } else { + // Match: Caused by: java.lang.Exception + Matcher causedByMatch = CAUSEDBY.matcher(line); + if (causedByMatch.matches()) { + inMessage = false; + inCausedBy = true; + } + + // Match: \tat class.method(Class.java:1) + Matcher atMatch = AT.matcher(line); + if (atMatch.matches()) { + inMessage = false; + inCausedBy = false; + } + + if (!causedByMatch.matches() && !atMatch.matches()) { + if (inMessage) { + message.append("\n"); + message.append(line); + } + if (!inMessage && !inCausedBy) { + addMessageStack(jc, message.toString(), stack.toString()); + return jc; + } + } + } + + if (inStack) { + stack.append(line); + stack.append("\n"); + } + } + + addMessageStack(jc, message.toString(), stack.toString()); + return jc; + } + + /** + * Adds the message and stack to the {@link JavaCrashItem}. + */ + private void addMessageStack(JavaCrashItem jc, String message, String stack) { + if (jc != null) { + if (message.length() > 0) { + jc.setMessage(message); + } + jc.setStack(stack.trim()); + } + } +} + diff --git a/src/com/android/loganalysis/parser/LogcatParser.java b/src/com/android/loganalysis/parser/LogcatParser.java new file mode 100644 index 0000000000000000000000000000000000000000..9edbede9f96d6166f032d5718dcf1e8aa84cacf7 --- /dev/null +++ b/src/com/android/loganalysis/parser/LogcatParser.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.GenericLogcatItem; +import com.android.loganalysis.item.LogcatItem; +import com.android.loganalysis.util.ArrayUtil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An {@link IParser} to handle logcat. The parser can handle the time and threadtime logcat + * formats. + * <p> + * Since the timestamps in the logcat do not have a year, the year can be set manually when the + * parser is created or through {@link #setYear(String)}. If a year is not set, the current year + * will be used. + * </p> + */ +public class LogcatParser implements IParser { + + /** + * Match a single line of `logcat -v threadtime`, such as: + * 05-26 11:02:36.886 5689 5689 D AndroidRuntime: CheckJNI is OFF + */ + private static final Pattern THREADTIME_LINE = Pattern.compile( + "^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})\\s+" + /* timestamp [1] */ + "(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+" + /* pid/tid and log level [2-4] */ + "(.+?)\\s*: (.*)$" /* tag and message [5-6]*/); + + /** + * Match a single line of `logcat -v time`, such as: + * 06-04 02:32:14.002 D/dalvikvm( 236): GC_CONCURRENT freed 580K, 51% free [...] + */ + private static final Pattern TIME_LINE = Pattern.compile( + "^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})\\s+" + /* timestamp [1] */ + "(\\w)/(.+?)\\(\\s*(\\d+)\\): (.*)$"); /* level, tag, pid, msg [2-5] */ + + /** + * Class for storing logcat meta data for a particular grouped list of lines. + */ + private class LogcatData { + public Integer mPid = null; + public Integer mTid = null; + public Date mTime = null; + public String mLevel = null; + public String mTag = null; + public String mLastPreamble = null; + public String mProcPreamble = null; + public List<String> mLines = new LinkedList<String>(); + + public LogcatData(Integer pid, Integer tid, Date time, String level, String tag, + String lastPreamble, String procPreamble) { + mPid = pid; + mTid = tid; + mTime = time; + mLevel = level; + mTag = tag; + mLastPreamble = lastPreamble; + mProcPreamble = procPreamble; + } + } + + private static final int MAX_BUFF_SIZE = 500; + private static final int MAX_LAST_PREAMBLE_SIZE = 15; + private static final int MAX_PROC_PREAMBLE_SIZE = 15; + + private LinkedList<String> mRingBuffer = new LinkedList<String>(); + private String mYear = null; + + LogcatItem mLogcat = new LogcatItem(); + + Map<String, LogcatData> mDataMap = new HashMap<String, LogcatData>(); + List<LogcatData> mDataList = new LinkedList<LogcatData>(); + + private Date mStartTime = null; + private Date mStopTime = null; + + /** + * Constructor for {@link LogcatParser}. + */ + public LogcatParser() { + } + + /** + * Constructor for {@link LogcatParser}. + * + * @param year The year as a string. + */ + public LogcatParser(String year) { + setYear(year); + } + + /** + * Sets the year for {@link LogcatParser}. + * + * @param year The year as a string. + */ + public void setYear(String year) { + mYear = year; + } + + /** + * Parse a logcat from a {@link BufferedReader} into an {@link LogcatItem} object. + * + * @param input a {@link BufferedReader}. + * @return The {@link LogcatItem}. + * @see #parse(List) + */ + public LogcatItem parse(BufferedReader input) throws IOException { + String line; + while ((line = input.readLine()) != null) { + parseLine(line); + } + commit(); + + return mLogcat; + } + + /** + * {@inheritDoc} + * + * @return The {@link LogcatItem}. + */ + @Override + public LogcatItem parse(List<String> lines) { + for (String line : lines) { + parseLine(line); + } + commit(); + + return mLogcat; + } + + /** + * Parse a line of input. + * + * @param line The line to parse + */ + private void parseLine(String line) { + Integer pid = null; + Integer tid = null; + Date time = null; + String level = null; + String tag = null; + String msg = null; + + Matcher m = THREADTIME_LINE.matcher(line); + Matcher tm = TIME_LINE.matcher(line); + if (m.matches()) { + time = parseTime(m.group(1)); + pid = Integer.parseInt(m.group(2)); + tid = Integer.parseInt(m.group(3)); + level = m.group(4); + tag = m.group(5); + msg = m.group(6); + } else if (tm.matches()) { + time = parseTime(tm.group(1)); + level = tm.group(2); + tag = tm.group(3); + pid = Integer.parseInt(tm.group(4)); + msg = tm.group(5); + } else { + // CLog.w("Failed to parse line '%s'", line); + return; + } + + if (mStartTime == null) { + mStartTime = time; + } + mStopTime = time; + + // ANRs are split when START matches a line. The newest entry is kept in the dataMap + // for quick lookup while all entries are added to the list. + if ("E".equals(level) && "ActivityManager".equals(tag)) { + String key = encodeLine(pid, tid, level, tag); + LogcatData data; + if (!mDataMap.containsKey(key) || AnrParser.START.matcher(msg).matches()) { + data = new LogcatData(pid, tid, time, level, tag, getLastPreamble(), + getProcPreamble(pid)); + mDataMap.put(key, data); + mDataList.add(data); + } else { + data = mDataMap.get(key); + } + data.mLines.add(msg); + } + + // PID and TID are enough to separate Java and native crashes. + if (("E".equals(level) && "AndroidRuntime".equals(tag)) || + ("I".equals(level) && "DEBUG".equals(tag))) { + String key = encodeLine(pid, tid, level, tag); + LogcatData data; + if (!mDataMap.containsKey(key)) { + data = new LogcatData(pid, tid, time, level, tag, getLastPreamble(), + getProcPreamble(pid)); + mDataMap.put(key, data); + mDataList.add(data); + } else { + data = mDataMap.get(key); + } + data.mLines.add(msg); + } + + // After parsing the line, add it the the buffer for the preambles. + mRingBuffer.add(line); + if (mRingBuffer.size() > MAX_BUFF_SIZE) { + mRingBuffer.removeFirst(); + } + } + + /** + * Signal that the input has finished. + */ + private void commit() { + for (LogcatData data : mDataList) { + GenericLogcatItem item = null; + if ("E".equals(data.mLevel) && "ActivityManager".equals(data.mTag)) { + // CLog.v("Parsing ANR: %s", data.mLines); + item = new AnrParser().parse(data.mLines); + } else if ("E".equals(data.mLevel) && "AndroidRuntime".equals(data.mTag)) { + // CLog.v("Parsing Java crash: %s", data.mLines); + item = new JavaCrashParser().parse(data.mLines); + } else if ("I".equals(data.mLevel) && "DEBUG".equals(data.mTag)) { + // CLog.v("Parsing native crash: %s", data.mLines); + item = new NativeCrashParser().parse(data.mLines); + } + if (item != null) { + item.setEventTime(data.mTime); + item.setPid(data.mPid); + item.setTid(data.mTid); + item.setLastPreamble(data.mLastPreamble); + item.setProcessPreamble(data.mProcPreamble); + mLogcat.addEvent(item); + } + } + + mLogcat.setStartTime(mStartTime); + mLogcat.setStopTime(mStopTime); + } + + /** + * Create an identifier that "should" be unique for a given logcat. In practice, we do use it as + * a unique identifier. + */ + private static String encodeLine(Integer pid, Integer tid, String level, String tag) { + if (tid == null) { + return String.format("%d|%s|%s", pid, level, tag); + } + return String.format("%d|%d|%s|%s", pid, tid, level, tag); + } + + /** + * Parse the timestamp and return a {@link Date}. If year is not set, the current year will be + * used. + * + * @param timeStr The timestamp in the format {@code MM-dd HH:mm:ss.SSS}. + * @return The {@link Date}. + */ + private Date parseTime(String timeStr) { + // If year is null, just use the current year. + if (mYear == null) { + DateFormat yearFormatter = new SimpleDateFormat("yyyy"); + mYear = yearFormatter.format(new Date()); + } + + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + try { + return formatter.parse(String.format("%s-%s", mYear, timeStr)); + } catch (ParseException e) { + // CLog.e("Could not parse time string %s", timeStr); + return null; + } + } + + /** + * Get the last {@value #MAX_LAST_PREAMBLE_SIZE} lines of logcat. + */ + private String getLastPreamble() { + final int size = mRingBuffer.size(); + List<String> preamble; + if (size > getLastPreambleSize()) { + preamble = mRingBuffer.subList(size - getLastPreambleSize(), size); + } else { + preamble = mRingBuffer; + } + return ArrayUtil.join("\n", preamble).trim(); + } + + /** + * Get the last {@value #MAX_PROC_PREAMBLE_SIZE} lines of logcat which match the given pid. + */ + private String getProcPreamble(int pid) { + LinkedList<String> preamble = new LinkedList<String>(); + + ListIterator<String> li = mRingBuffer.listIterator(mRingBuffer.size()); + while (li.hasPrevious()) { + String line = li.previous(); + + Matcher m = THREADTIME_LINE.matcher(line); + Matcher tm = TIME_LINE.matcher(line); + if ((m.matches() && pid == Integer.parseInt(m.group(2))) || + (tm.matches() && pid == Integer.parseInt(tm.group(4)))) { + preamble.addFirst(line); + } + + if (preamble.size() == getProcPreambleSize()) { + return ArrayUtil.join("\n", preamble).trim(); + } + } + return ArrayUtil.join("\n", preamble).trim(); + } + + /** + * Get the number of lines in the last preamble. Exposed for unit testing. + */ + int getLastPreambleSize() { + return MAX_LAST_PREAMBLE_SIZE; + } + + /** + * Get the number of lines in the process preamble. Exposed for unit testing. + */ + int getProcPreambleSize() { + return MAX_PROC_PREAMBLE_SIZE; + } +} diff --git a/src/com/android/loganalysis/parser/MemInfoParser.java b/src/com/android/loganalysis/parser/MemInfoParser.java new file mode 100644 index 0000000000000000000000000000000000000000..3aeaf05c633610948c6c979cb9e5ba2ff7dce5ab --- /dev/null +++ b/src/com/android/loganalysis/parser/MemInfoParser.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.MemInfoItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to handle the output from {@code /proc/meminfo}. + */ +public class MemInfoParser implements IParser { + + /** Match a single MemoryInfo line, such as "MemFree: 65420 kB" */ + private static final Pattern INFO_LINE = Pattern.compile("^([^:]+):\\s+(\\d+) kB"); + + /** + * {@inheritDoc} + * + * @return The {@link MemInfoItem}. + */ + @Override + public MemInfoItem parse(List<String> block) { + MemInfoItem item = new MemInfoItem(); + + for (String line : block) { + Matcher m = INFO_LINE.matcher(line); + if (m.matches()) { + String key = m.group(1); + Integer value = Integer.parseInt(m.group(2)); + item.put(key, value); + } else { + // CLog.w("Failed to parse line '%s'", line); + } + } + + return item; + } +} diff --git a/src/com/android/loganalysis/parser/MonkeyLogParser.java b/src/com/android/loganalysis/parser/MonkeyLogParser.java new file mode 100644 index 0000000000000000000000000000000000000000..60cc42844d7215a48c2b4e7f7fc16c7f4157968f --- /dev/null +++ b/src/com/android/loganalysis/parser/MonkeyLogParser.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.AnrItem; +import com.android.loganalysis.item.GenericLogcatItem; +import com.android.loganalysis.item.MonkeyLogItem; +import com.android.loganalysis.item.MonkeyLogItem.DroppedCategory; +import com.android.loganalysis.item.TracesItem; + +import java.io.BufferedReader; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to parse monkey logs. + */ +public class MonkeyLogParser implements IParser { + private static final Pattern THROTTLE = Pattern.compile( + "adb shell monkey.* --throttle (\\d+).*"); + private static final Pattern SEED_AND_TARGET_COUNT = Pattern.compile( + ":Monkey: seed=(\\d+) count=(\\d+)"); + private static final Pattern SECURITY_EXCEPTIONS = Pattern.compile( + "adb shell monkey.* --ignore-security-exceptions.*"); + + private static final Pattern PACKAGES = Pattern.compile(":AllowPackage: (\\S+)"); + private static final Pattern CATEGORIES = Pattern.compile(":IncludeCategory: (\\S+)"); + + private static final Pattern START_UPTIME = Pattern.compile( + "# (.*) - device uptime = (\\d+\\.\\d+): Monkey command used for this test:"); + private static final Pattern STOP_UPTIME = Pattern.compile( + "# (.*) - device uptime = (\\d+\\.\\d+): Monkey command ran for: " + + "(\\d+):(\\d+) \\(mm:ss\\)"); + + private static final Pattern INTERMEDIATE_COUNT = Pattern.compile( + "\\s+// Sending event #(\\d+)"); + private static final Pattern FINISHED = Pattern.compile("// Monkey finished"); + private static final Pattern FINAL_COUNT = Pattern.compile("Events injected: (\\d+)"); + private static final Pattern NO_ACTIVITIES = Pattern.compile( + "\\*\\* No activities found to run, monkey aborted."); + + private static final Pattern DROPPED_KEYS = Pattern.compile(":Dropped: .*keys=(\\d+).*"); + private static final Pattern DROPPED_POINTERS = Pattern.compile( + ":Dropped: .*pointers=(\\d+).*"); + private static final Pattern DROPPED_TRACKBALLS = Pattern.compile( + ":Dropped: .*trackballs=(\\d+).*"); + private static final Pattern DROPPED_FLIPS = Pattern.compile(":Dropped: .*flips=(\\d+).*"); + private static final Pattern DROPPED_ROTATIONS = Pattern.compile( + ":Dropped: .*rotations=(\\d+).*"); + + private static final Pattern ANR = Pattern.compile( + "// NOT RESPONDING: (\\S+) \\(pid (\\d+)\\)"); + private static final Pattern JAVA_CRASH = Pattern.compile( + "// CRASH: (\\S+) \\(pid (\\d+)\\)"); + + private static final Pattern TRACES_START = Pattern.compile("anr traces:"); + private static final Pattern TRACES_STOP = Pattern.compile("// anr traces status was \\d+"); + + private boolean mMatchingAnr = false; + private boolean mMatchingJavaCrash = false; + private boolean mMatchingTraces = false; + private List<String> mBlock = null; + private String mApp = null; + private int mPid = 0; + + private MonkeyLogItem mMonkeyLog = new MonkeyLogItem(); + + /** + * Parse a monkey log from a {@link BufferedReader} into an {@link MonkeyLogItem} object. + * + * @param input a {@link BufferedReader}. + * @return The {@link MonkeyLogItem}. + * @see #parse(List) + */ + public MonkeyLogItem parse(BufferedReader input) throws IOException { + String line; + while ((line = input.readLine()) != null) { + parseLine(line); + } + + return mMonkeyLog; + } + + /** + * {@inheritDoc} + * + * @return The {@link MonkeyLogItem}. + */ + @Override + public MonkeyLogItem parse(List<String> lines) { + for (String line : lines) { + parseLine(line); + } + + return mMonkeyLog; + } + + /** + * Parse a line of input. + */ + private void parseLine(String line) { + Matcher m; + + if (mMatchingAnr || mMatchingJavaCrash) { + if (mMatchingJavaCrash) { + line = line.replace("// ", ""); + } + if ("".equals(line)) { + GenericLogcatItem crash; + if (mMatchingAnr) { + crash = new AnrParser().parse(mBlock); + } else { + crash = new JavaCrashParser().parse(mBlock); + } + if (crash != null) { + crash.setPid(mPid); + crash.setApp(mApp); + mMonkeyLog.setCrash(crash); + } + + mMatchingAnr = false; + mMatchingJavaCrash = false; + mBlock = null; + mApp = null; + mPid = 0; + } else { + mBlock.add(line); + } + return; + } + + if (mMatchingTraces) { + m = TRACES_STOP.matcher(line); + if (m.matches()) { + TracesItem traces = new TracesParser().parse(mBlock); + + // Set the trace if the crash is an ANR and if the app for the crash and trace match + if (traces != null && traces.getApp() != null && traces.getStack() != null && + mMonkeyLog.getCrash() instanceof AnrItem && + traces.getApp().equals(mMonkeyLog.getCrash().getApp())) { + ((AnrItem) mMonkeyLog.getCrash()).setTrace(traces.getStack()); + } + + mMatchingTraces = false; + mBlock = null; + } else { + mBlock.add(line); + } + } + + m = THROTTLE.matcher(line); + if (m.matches()) { + mMonkeyLog.setThrottle(Integer.parseInt(m.group(1))); + } + m = SEED_AND_TARGET_COUNT.matcher(line); + if (m.matches()) { + mMonkeyLog.setSeed(Integer.parseInt(m.group(1))); + mMonkeyLog.setTargetCount(Integer.parseInt(m.group(2))); + } + m = SECURITY_EXCEPTIONS.matcher(line); + if (m.matches()) { + mMonkeyLog.setIgnoreSecurityExceptions(true); + } + m = PACKAGES.matcher(line); + if (m.matches()) { + mMonkeyLog.addPackage(m.group(1)); + } + m = CATEGORIES.matcher(line); + if (m.matches()) { + mMonkeyLog.addCategory(m.group(1)); + } + m = START_UPTIME.matcher(line); + if (m.matches()) { + mMonkeyLog.setStartTime(parseTime(m.group(1))); + mMonkeyLog.setStartUptimeDuration((long) (Double.parseDouble(m.group(2)) * 1000)); + } + m = STOP_UPTIME.matcher(line); + if (m.matches()) { + mMonkeyLog.setStopTime(parseTime(m.group(1))); + mMonkeyLog.setStopUptimeDuration((long) (Double.parseDouble(m.group(2)) * 1000)); + mMonkeyLog.setTotalDuration(60 * 1000 * Integer.parseInt(m.group(3)) + + 1000 *Integer.parseInt(m.group(4))); + } + m = INTERMEDIATE_COUNT.matcher(line); + if (m.matches()) { + mMonkeyLog.setIntermediateCount(Integer.parseInt(m.group(1))); + } + m = FINAL_COUNT.matcher(line); + if (m.matches()) { + mMonkeyLog.setFinalCount(Integer.parseInt(m.group(1))); + } + m = FINISHED.matcher(line); + if (m.matches()) { + mMonkeyLog.setIsFinished(true); + } + m = NO_ACTIVITIES.matcher(line); + if (m.matches()) { + mMonkeyLog.setNoActivities(true); + } + m = DROPPED_KEYS.matcher(line); + if (m.matches()) { + mMonkeyLog.setDroppedCount(DroppedCategory.KEYS, Integer.parseInt(m.group(1))); + } + m = DROPPED_POINTERS.matcher(line); + if (m.matches()) { + mMonkeyLog.setDroppedCount(DroppedCategory.POINTERS, Integer.parseInt(m.group(1))); + } + m = DROPPED_TRACKBALLS.matcher(line); + if (m.matches()) { + mMonkeyLog.setDroppedCount(DroppedCategory.TRACKBALLS, Integer.parseInt(m.group(1))); + } + m = DROPPED_FLIPS.matcher(line); + if (m.matches()) { + mMonkeyLog.setDroppedCount(DroppedCategory.FLIPS, Integer.parseInt(m.group(1))); + } + m = DROPPED_ROTATIONS.matcher(line); + if (m.matches()) { + mMonkeyLog.setDroppedCount(DroppedCategory.ROTATIONS, Integer.parseInt(m.group(1))); + } + m = ANR.matcher(line); + if (m.matches()) { + mApp = m.group(1); + mPid = Integer.parseInt(m.group(2)); + mBlock = new LinkedList<String>(); + mMatchingAnr = true; + } + m = JAVA_CRASH.matcher(line); + if (m.matches()) { + mApp = m.group(1); + mPid = Integer.parseInt(m.group(2)); + mBlock = new LinkedList<String>(); + mMatchingJavaCrash = true; + } + m = TRACES_START.matcher(line); + if (m.matches()) { + mBlock = new LinkedList<String>(); + mMatchingTraces = true; + } + } + + /** + * Parse the timestamp and return a date. + * + * @param timeStr The timestamp in the format {@code E, MM/dd/yyyy hh:mm:ss a} or + * {@code EEE MMM dd HH:mm:ss zzz yyyy}. + * @return The {@link Date}. + */ + private Date parseTime(String timeStr) { + try { + return new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").parse(timeStr); + } catch (ParseException e) { + // CLog.v("Could not parse date %s with format EEE MMM dd HH:mm:ss zzz yyyy", timeStr); + } + + try { + return new SimpleDateFormat("E, MM/dd/yyyy hh:mm:ss a").parse(timeStr); + } catch (ParseException e) { + // CLog.v("Could not parse date %s with format E, MM/dd/yyyy hh:mm:ss a", timeStr); + } + + // CLog.e("Could not parse date %s", timeStr); + return null; + } + +} diff --git a/src/com/android/loganalysis/parser/NativeCrashParser.java b/src/com/android/loganalysis/parser/NativeCrashParser.java new file mode 100644 index 0000000000000000000000000000000000000000..362ed8e6287efea587a88643675a0eae40136971 --- /dev/null +++ b/src/com/android/loganalysis/parser/NativeCrashParser.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.NativeCrashItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An {@link IParser} to handle native crashes. + */ +public class NativeCrashParser implements IParser { + + /** Matches: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */ + private static final Pattern START = Pattern.compile("^(?:\\*\\*\\* ){15}\\*\\*\\*$"); + /** Matches: Build fingerprint: 'fingerprint' */ + private static final Pattern FINGERPRINT = Pattern.compile("^Build fingerprint: '(.*)'$"); + /** Matches: pid: 957, tid: 963 >>> com.android.camera <<< */ + private static final Pattern APP = Pattern.compile( + "^pid: \\d+, tid: \\d+(, name: \\S+)? >>> (\\S+) <<<$"); + + + /** + * {@inheritDoc} + * + * @return The {@link NativeCrashItem}. + */ + @Override + public NativeCrashItem parse(List<String> lines) { + NativeCrashItem nc = null; + StringBuilder stack = new StringBuilder(); + + for (String line : lines) { + Matcher m = START.matcher(line); + if (m.matches()) { + nc = new NativeCrashItem(); + } + + if (nc != null) { + m = FINGERPRINT.matcher(line); + if (m.matches()) { + nc.setFingerprint(m.group(1)); + } + m = APP.matcher(line); + if (m.matches()) { + nc.setApp(m.group(2)); + } + + stack.append(line); + stack.append("\n"); + } + } + if (nc != null) { + nc.setStack(stack.toString().trim()); + } + return nc; + } +} + diff --git a/src/com/android/loganalysis/parser/NoopParser.java b/src/com/android/loganalysis/parser/NoopParser.java new file mode 100644 index 0000000000000000000000000000000000000000..2c787420bd6b2716b160e4b32dc23427b6957662 --- /dev/null +++ b/src/com/android/loganalysis/parser/NoopParser.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.IItem; + +import java.util.List; + +/** + * A {@link IParser} that consumes nothing. + */ +public class NoopParser implements IParser { + /** + * {@inheritDoc} + */ + @Override + public IItem parse(List<String> block) { + // ignore + return null; + } +} + diff --git a/src/com/android/loganalysis/parser/ProcrankParser.java b/src/com/android/loganalysis/parser/ProcrankParser.java new file mode 100644 index 0000000000000000000000000000000000000000..938a62c98c463cd9fc61adf1a47591c18d3a069a --- /dev/null +++ b/src/com/android/loganalysis/parser/ProcrankParser.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.ProcrankItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to handle the output of {@code procrank}. Memory values returned are in units + * of kilobytes. + */ +public class ProcrankParser implements IParser { + + /** Match a valid line, such as: + * " 1313 78128K 77996K 48603K 45812K com.google.android.apps.maps" */ + private static final Pattern LINE_PAT = Pattern.compile( + "\\s*(\\d+)\\s+" + /* PID [1] */ + "(\\d+)K\\s+(\\d+)K\\s+(\\d+)K\\s+(\\d+)K\\s+" + /* Vss Rss Pss Uss [2-5] */ + "(\\S+)" /* process name [6] */); + + /** Match the end of the Procrank table, determined by three sets of "------". */ + private static final Pattern END_PAT = Pattern.compile("^\\s+-{6}\\s+-{6}\\s+-{6}"); + + /** + * {@inheritDoc} + */ + @Override + public ProcrankItem parse(List<String> lines) { + ProcrankItem item = new ProcrankItem(); + + for (String line : lines) { + // If we have reached the end. + Matcher endMatcher = END_PAT.matcher(line); + if (endMatcher.matches()) { + return item; + } + + Matcher m = LINE_PAT.matcher(line); + if (m.matches()) { + item.addProcrankLine(Integer.parseInt(m.group(1)), m.group(6), + Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3)), + Integer.parseInt(m.group(4)), Integer.parseInt(m.group(5))); + } + } + + return item; + } +} + diff --git a/src/com/android/loganalysis/parser/SystemPropsParser.java b/src/com/android/loganalysis/parser/SystemPropsParser.java new file mode 100644 index 0000000000000000000000000000000000000000..a1cd9da1c75d833ec269394e30863b3cb4160f31 --- /dev/null +++ b/src/com/android/loganalysis/parser/SystemPropsParser.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.SystemPropsItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to handle the output from {@code getprop}. + */ +public class SystemPropsParser implements IParser { + /** Match a single property line, such as "[gsm.sim.operator.numeric]: []" */ + private static final Pattern PROP_LINE = Pattern.compile("^\\[(.*)\\]: \\[(.*)\\]$"); + + /** + * {@inheritDoc} + * + * @return The {@link SystemPropsItem}. + */ + @Override + public SystemPropsItem parse(List<String> lines) { + SystemPropsItem item = new SystemPropsItem(); + + for (String line : lines) { + Matcher m = PROP_LINE.matcher(line); + if (m.matches()) { + item.put(m.group(1), m.group(2)); + } else { + // CLog.w("Failed to parse line '%s'", line); + } + } + return item; + } +} + diff --git a/src/com/android/loganalysis/parser/TracesParser.java b/src/com/android/loganalysis/parser/TracesParser.java new file mode 100644 index 0000000000000000000000000000000000000000..4d0f5810aff22c7232e13617d2622c948643bc45 --- /dev/null +++ b/src/com/android/loganalysis/parser/TracesParser.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.TracesItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to parse Android traces files. + * <p> + * For now, this only extracts the main stack trace from the first process. It is used to get a + * stack from {@code /data/anr/traces.txt} which can be used to give some context about the ANR. If + * there is a need, this parser can be expanded to parse all stacks from all processes. + */ +public class TracesParser implements IParser { + + /** + * Matches: ----- pid PID at YYYY-MM-DD hh:mm:ss ----- + */ + private static final Pattern PID = Pattern.compile( + "^----- pid (\\d+) at \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} -----$"); + + /** + * Matches: Cmd line: APP + */ + private static final Pattern APP = Pattern.compile("^Cmd line: (\\S+)$"); + + /** + * Matches: "main" prio=5 tid=1 STATE + */ + private static final Pattern STACK = Pattern.compile("^\"main\" .*$"); + + /** + * {@inheritDoc} + * + * @return The {@link TracesItem}. + */ + @Override + public TracesItem parse(List<String> lines) { + TracesItem traces = new TracesItem(); + StringBuffer stack = null; + + for (String line : lines) { + if (stack == null) { + Matcher m = PID.matcher(line); + if (m.matches()) { + traces.setPid(Integer.parseInt(m.group(1))); + } + m = APP.matcher(line); + if (m.matches()) { + traces.setApp(m.group(1)); + } + m = STACK.matcher(line); + if (m.matches()) { + stack = new StringBuffer(); + stack.append(line); + stack.append("\n"); + } + } else if (!"".equals(line)) { + stack.append(line); + stack.append("\n"); + } else { + traces.setStack(stack.toString().trim()); + return traces; + } + } + if (stack == null) { + return null; + } + traces.setStack(stack.toString().trim()); + return traces; + } + +} diff --git a/src/com/android/loganalysis/util/ArrayUtil.java b/src/com/android/loganalysis/util/ArrayUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..95b1634f4111c6c3f1339ba1e52812589c12ba36 --- /dev/null +++ b/src/com/android/loganalysis/util/ArrayUtil.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2011 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.loganalysis.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * Utility methods for arrays + */ +// TODO: Use libTF once this is copied over. +public class ArrayUtil { + + private ArrayUtil() { + } + + /** + * Build an array from the provided contents. + * + * <p> + * The resulting array will be the concatenation of <var>arrays</var> input arrays, in their + * original order. + * </p> + * + * @param arrays the arrays to concatenate + * @return the newly constructed array + */ + public static String[] buildArray(String[]... arrays) { + int length = 0; + for (String[] array : arrays) { + length += array.length; + } + String[] newArray = new String[length]; + int offset = 0; + for (String[] array : arrays) { + System.arraycopy(array, 0, newArray, offset, array.length); + offset += array.length; + } + return newArray; + } + + /** + * Convert a varargs list/array to an {@link List}. This is useful for building instances of + * {@link List} by hand. Note that this differs from {@link java.util.Arrays#asList} in that + * the returned array is mutable. + * + * @param inputAry an array, or a varargs list + * @return a {@link List} instance with the identical contents + */ + public static <T> List<T> list(T... inputAry) { + List<T> retList = new ArrayList<T>(inputAry.length); + for (T item : inputAry) { + retList.add(item); + } + return retList; + } + + private static String internalJoin(String sep, Collection<Object> pieces) { + StringBuilder sb = new StringBuilder(); + boolean skipSep = true; + Iterator<Object> iter = pieces.iterator(); + while (iter.hasNext()) { + if (skipSep) { + skipSep = false; + } else { + sb.append(sep); + } + + Object obj = iter.next(); + if (obj == null) { + sb.append("null"); + } else { + sb.append(obj.toString()); + } + } + return sb.toString(); + } + + /** + * Turns a sequence of objects into a string, delimited by {@code sep}. If a single + * {@code Collection} is passed, it is assumed that the elements of that Collection are to be + * joined. Otherwise, wraps the passed {@link Object}(s) in a {@link List} and joins the + * generated list. + * + * @param sep the string separator to delimit the different output segments. + * @param pieces A {@link Collection} or a varargs {@code Array} of objects. + */ + @SuppressWarnings("unchecked") + public static String join(String sep, Object... pieces) { + if ((pieces.length == 1) && (pieces[0] instanceof Collection)) { + // Don't re-wrap the Collection + return internalJoin(sep, (Collection<Object>) pieces[0]); + } else { + return internalJoin(sep, Arrays.asList(pieces)); + } + } +} + diff --git a/src/com/android/loganalysis/util/RegexTrie.java b/src/com/android/loganalysis/util/RegexTrie.java new file mode 100644 index 0000000000000000000000000000000000000000..f3c2d6ab42290c46d46c2313c9cb5245a173042d --- /dev/null +++ b/src/com/android/loganalysis/util/RegexTrie.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2010 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.loganalysis.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The RegexTrie is a trie where each _stored_ segment of the key is a regex {@link Pattern}. Thus, + * the full _stored_ key is a List<Pattern> rather than a String as in a standard trie. Note that + * the {@link #get(Object key)} method requires a List<String>, which will be matched against the + * {@link Pattern}s, rather than checked for equality as in a standard trie. It will likely perform + * poorly for large datasets. + * <p /> + * One can also use a {@code null} entry in the {@code Pattern} sequence to serve as a wildcard. If + * a {@code null} is encountered, all subsequent entries in the sequence will be ignored. + * When the retrieval code encounters a {@code null} {@code Pattern}, it will first wait to see if a + * more-specific entry matches the sequence. If one does, that more-specific entry will proceed, + * even if it subsequently fails to match. + * <p /> + * If no more-specific entry matches, the wildcard match will add all remaining {@code String}s + * to the list of captures (if enabled) and return the value associated with the wildcard. + * <p /> + * A short sample of the wildcard functionality: + * <pre> + * List<List<String>> captures = new LinkedList<List<String>>(); + * RegexTrie<Integer> trie = new RegexTrie<Integer>(); + * trie.put(2, "a", null); + * trie.put(4, "a", "b"); + * trie.retrieve(captures, "a", "c", "e"); + * // returns 2. captures is now [[], ["c"], ["e"]] + * trie.retrieve(captures, "a", "b"); + * // returns 4. captures is now [[], []] + * trie.retrieve(captures, "a", "b", "c"); + * // returns null. captures is now [[], []] + * </pre> + */ +//TODO: Use libTF once this is copied over. +public class RegexTrie<V> { + private V mValue = null; + private Map<CompPattern, RegexTrie<V>> mChildren = + new LinkedHashMap<CompPattern, RegexTrie<V>>(); + + /** + * Patterns aren't comparable by default, which prevents you from retrieving them from a + * HashTable. This is a simple stub class that makes a Pattern with a working + * {@link CompPattern#equals()} method. + */ + static class CompPattern { + protected final Pattern mPattern; + + CompPattern(Pattern pattern) { + if (pattern == null) { + throw new NullPointerException(); + } + mPattern = pattern; + } + + @Override + public boolean equals(Object other) { + Pattern otherPat; + if (other instanceof Pattern) { + otherPat = (Pattern) other; + } else if (other instanceof CompPattern) { + CompPattern otherCPat = (CompPattern) other; + otherPat = otherCPat.mPattern; + } else { + return false; + } + return mPattern.toString().equals(otherPat.toString()); + } + + @Override + public int hashCode() { + return mPattern.toString().hashCode(); + } + + @Override + public String toString() { + return String.format("CP(%s)", mPattern.toString()); + } + + public Matcher matcher(String string) { + return mPattern.matcher(string); + } + } + + public void clear() { + mValue = null; + for (RegexTrie child : mChildren.values()) { + child.clear(); + } + mChildren.clear(); + } + + boolean containsKey(String... strings) { + return retrieve(strings) != null; + } + + V recursivePut(V value, List<CompPattern> patterns) { + // Cases: + // 1) patterns is empty -- set our value + // 2) patterns is non-empty -- recurse downward, creating a child if necessary + if (patterns.isEmpty()) { + V oldValue = mValue; + mValue = value; + return oldValue; + } else { + CompPattern curKey = patterns.get(0); + List<CompPattern> nextKeys = patterns.subList(1, patterns.size()); + + // Create a new child to handle + RegexTrie<V> nextChild = mChildren.get(curKey); + if (nextChild == null) { + nextChild = new RegexTrie<V>(); + mChildren.put(curKey, nextChild); + } + return nextChild.recursivePut(value, nextKeys); + } + } + + /** + * A helper method to consolidate validation before adding an entry to the trie. + * + * @param value The value to set + * @param patterns The sequence of {@link CompPattern}s that must be sequentially matched to + * retrieve the associated {@code value} + */ + private V validateAndPut(V value, List<CompPattern> pList) { + if (pList.size() == 0) { + throw new IllegalArgumentException("pattern list must be non-empty."); + } + return recursivePut(value, pList); + } + + /** + * Add an entry to the trie. + * + * @param value The value to set + * @param patterns The sequence of {@link Pattern}s that must be sequentially matched to + * retrieve the associated {@code value} + */ + public V put(V value, Pattern... patterns) { + List<CompPattern> pList = new ArrayList<CompPattern>(patterns.length); + for (Pattern pat : patterns) { + if (pat == null) { + pList.add(null); + break; + } + pList.add(new CompPattern(pat)); + } + return validateAndPut(value, pList); + } + + /** + * This helper method takes a list of regular expressions as {@link String}s and compiles them + * on-the-fly before adding the subsequent {@link Pattern}s to the trie + * + * @param value The value to set + * @param patterns The sequence of regular expressions (as {@link String}s) that must be + * sequentially matched to retrieve the associated {@code value}. Each String will be + * compiled as a {@link Pattern} before invoking {@link #put(V, Pattern...)}. + */ + public V put(V value, String... regexen) { + List<CompPattern> pList = new ArrayList<CompPattern>(regexen.length); + for (String regex : regexen) { + if (regex == null) { + pList.add(null); + break; + } + Pattern pat = Pattern.compile(regex); + pList.add(new CompPattern(pat)); + } + return validateAndPut(value, pList); + } + + V recursiveRetrieve(List<List<String>> captures, List<String> strings) { + // Cases: + // 1) strings is empty -- return our value + // 2) strings is non-empty -- find the first child that matches, recurse downward + if (strings.isEmpty()) { + return mValue; + } else { + boolean wildcardMatch = false; + V wildcardValue = null; + String curKey = strings.get(0); + List<String> nextKeys = strings.subList(1, strings.size()); + + for (Map.Entry<CompPattern, RegexTrie<V>> child : mChildren.entrySet()) { + CompPattern pattern = child.getKey(); + if (pattern == null) { + wildcardMatch = true; + wildcardValue = child.getValue().getValue(); + continue; + } + + Matcher matcher = pattern.matcher(curKey); + if (matcher.matches()) { + if (captures != null) { + List<String> curCaptures = new ArrayList<String>(matcher.groupCount()); + for (int i = 0; i < matcher.groupCount(); i++) { + // i+1 since group 0 is the entire matched string + curCaptures.add(matcher.group(i+1)); + } + captures.add(curCaptures); + } + + return child.getValue().recursiveRetrieve(captures, nextKeys); + } + } + + if (wildcardMatch) { + // Stick the rest of the query string into the captures list and return + if (captures != null) { + for (String str : strings) { + captures.add(Arrays.asList(str)); + } + } + return wildcardValue; + } + + // no match + return null; + } + } + + /** + * Fetch a value from the trie, by matching the provided sequence of {@link String}s to a + * sequence of {@link Pattern}s stored in the trie. + * + * @param strings A sequence of {@link String}s to match + * @return The associated value, or {@code null} if no value was found + */ + public V retrieve(String... strings) { + return retrieve(null, strings); + } + + /** + * Fetch a value from the trie, by matching the provided sequence of {@link String}s to a + * sequence of {@link Pattern}s stored in the trie. This version of the method also returns + * a {@link List} of capture groups for each {@link Pattern} that was matched. + * <p /> + * Each entry in the outer List corresponds to one level of {@code Pattern} in the trie. + * For each level, the list of capture groups will be stored. If there were no captures + * for a particular level, an empty list will be stored. + * <p /> + * Note that {@code captures} will be {@link List#clear()}ed before the retrieval begins. + * Also, if the retrieval fails after a partial sequence of matches, {@code captures} will + * still reflect the capture groups from the partial match. + * + * @param captures A {@code List<List<String>>} through which capture groups will be returned. + * @param strings A sequence of {@link String}s to match + * @return The associated value, or {@code null} if no value was found + */ + public V retrieve(List<List<String>> captures, String... strings) { + if (strings.length == 0) { + throw new IllegalArgumentException("string list must be non-empty"); + } + List<String> sList = Arrays.asList(strings); + if (captures != null) { + captures.clear(); + } + return recursiveRetrieve(captures, sList); + } + + private V getValue() { + return mValue; + } + + @Override + public String toString() { + return String.format("{V: %s, C: %s}", mValue, mChildren); + } +} + diff --git a/tests/.classpath b/tests/.classpath new file mode 100644 index 0000000000000000000000000000000000000000..32d4a0f0ad30f01b0ecf9507fe436b4174a68182 --- /dev/null +++ b/tests/.classpath @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> + <classpathentry combineaccessrules="false" kind="src" path="/loganalysis"/> + <classpathentry exported="true" kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/easymock_intermediates/javalib.jar" sourcepath="/TRADEFED_ROOT/external/easymock/src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/tests/.project b/tests/.project new file mode 100644 index 0000000000000000000000000000000000000000..f4a1c0b5a8e195317ab96584689bd4a55a9ceb87 --- /dev/null +++ b/tests/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>loganalysis-tests</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..32019a7b3e4d92d55b2527de091d271391b43d37 --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,42 @@ +# Copyright (C) 2013 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVACFLAGS += -g -Xlint + +LOCAL_MODULE := loganalysis-tests +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := easymock junit +LOCAL_JAVA_LIBRARIES := loganalysis + +include $(BUILD_HOST_JAVA_LIBRARY) + +# makefile rules to copy jars to HOST_OUT/tradefed +# so tradefed.sh can automatically add to classpath + +DEST_JAR := $(HOST_OUT)/tradefed/$(LOCAL_MODULE).jar +$(DEST_JAR): $(LOCAL_BUILT_MODULE) + $(copy-file-to-new-target) + +# this dependency ensure the above rule will be executed if module is built +$(LOCAL_INSTALLED_MODULE) : $(DEST_JAR) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/src/com/android/loganalysis/FuncTests.java b/tests/src/com/android/loganalysis/FuncTests.java new file mode 100644 index 0000000000000000000000000000000000000000..7a06cbd46485b8063a5685c5099141ee6d8470bf --- /dev/null +++ b/tests/src/com/android/loganalysis/FuncTests.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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.loganalysis; + +import com.android.loganalysis.parser.BugreportParserFuncTest; +import com.android.loganalysis.parser.LogcatParserFuncTest; +import com.android.loganalysis.parser.MonkeyLogParserFuncTest; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * A test suite for all log analysis functional tests. + */ +public class FuncTests extends TestSuite { + + public FuncTests() { + super(); + + addTestSuite(BugreportParserFuncTest.class); + addTestSuite(LogcatParserFuncTest.class); + addTestSuite(MonkeyLogParserFuncTest.class); + } + + public static Test suite() { + return new FuncTests(); + } +} diff --git a/tests/src/com/android/loganalysis/UnitTests.java b/tests/src/com/android/loganalysis/UnitTests.java new file mode 100644 index 0000000000000000000000000000000000000000..083d571454336f5b2353178aea96ccf908d73537 --- /dev/null +++ b/tests/src/com/android/loganalysis/UnitTests.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 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.loganalysis; + +import com.android.loganalysis.item.GenericItemTest; +import com.android.loganalysis.parser.AbstractSectionParserTest; +import com.android.loganalysis.parser.AnrParserTest; +import com.android.loganalysis.parser.BugreportParserTest; +import com.android.loganalysis.parser.JavaCrashParserTest; +import com.android.loganalysis.parser.LogcatParserTest; +import com.android.loganalysis.parser.MemInfoParserTest; +import com.android.loganalysis.parser.MonkeyLogParserTest; +import com.android.loganalysis.parser.NativeCrashParserTest; +import com.android.loganalysis.parser.ProcrankParserTest; +import com.android.loganalysis.parser.SystemPropsParserTest; +import com.android.loganalysis.parser.TracesParserTest; +import com.android.loganalysis.util.ArrayUtilTest; +import com.android.loganalysis.util.RegexTrieTest; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * A test suite for all Trade Federation unit tests. + * <p/> + * All tests listed here should be self-contained, and should not require any external dependencies. + */ +public class UnitTests extends TestSuite { + + public UnitTests() { + super(); + + // item + addTestSuite(GenericItemTest.class); + + // parser + addTestSuite(AbstractSectionParserTest.class); + addTestSuite(AnrParserTest.class); + addTestSuite(BugreportParserTest.class); + addTestSuite(JavaCrashParserTest.class); + addTestSuite(LogcatParserTest.class); + addTestSuite(MemInfoParserTest.class); + addTestSuite(MonkeyLogParserTest.class); + addTestSuite(NativeCrashParserTest.class); + addTestSuite(ProcrankParserTest.class); + addTestSuite(SystemPropsParserTest.class); + addTestSuite(TracesParserTest.class); + + // util + addTestSuite(ArrayUtilTest.class); + addTestSuite(RegexTrieTest.class); + } + + public static Test suite() { + return new UnitTests(); + } +} diff --git a/tests/src/com/android/loganalysis/item/GenericItemTest.java b/tests/src/com/android/loganalysis/item/GenericItemTest.java new file mode 100644 index 0000000000000000000000000000000000000000..037f39a98dca21ce190fa0ca029d2873a36dff63 --- /dev/null +++ b/tests/src/com/android/loganalysis/item/GenericItemTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2011 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.loganalysis.item; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Unit test for {@link GenericItem}. + */ +public class GenericItemTest extends TestCase { + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + "integer", "string")); + + private String mStringAttribute = "String"; + private Integer mIntegerAttribute = 1; + + /** Empty item with no attributes set */ + private GenericItem mEmptyItem1; + /** Empty item with no attributes set */ + private GenericItem mEmptyItem2; + /** Item with only the string attribute set */ + private GenericItem mStringItem; + /** Item with only the integer attribute set */ + private GenericItem mIntegerItem; + /** Item with both attributes set, product of mStringItem and mIntegerItem */ + private GenericItem mFullItem1; + /** Item with both attributes set, product of mStringItem and mIntegerItem */ + private GenericItem mFullItem2; + /** Item that is inconsistent with the others */ + private GenericItem mInconsistentItem; + + @Override + public void setUp() { + mEmptyItem1 = new GenericItem(null, ATTRIBUTES); + mEmptyItem2 = new GenericItem(null, ATTRIBUTES); + mStringItem = new GenericItem(null, ATTRIBUTES); + mStringItem.setAttribute("string", mStringAttribute); + mIntegerItem = new GenericItem(null, ATTRIBUTES); + mIntegerItem.setAttribute("integer", mIntegerAttribute); + mFullItem1 = new GenericItem(null, ATTRIBUTES); + mFullItem1.setAttribute("string", mStringAttribute); + mFullItem1.setAttribute("integer", mIntegerAttribute); + mFullItem2 = new GenericItem(null, ATTRIBUTES); + mFullItem2.setAttribute("string", mStringAttribute); + mFullItem2.setAttribute("integer", mIntegerAttribute); + mInconsistentItem = new GenericItem(null, ATTRIBUTES); + mInconsistentItem.setAttribute("string", "gnirts"); + mInconsistentItem.setAttribute("integer", 2); + } + + /** + * Test for {@link GenericItem#mergeAttributes(IItem)}. + */ + public void testMergeAttributes() throws ConflictingItemException { + Map<String, Object> attributes; + + attributes = mEmptyItem1.mergeAttributes(mEmptyItem1); + assertNull(attributes.get("string")); + assertNull(attributes.get("integer")); + + attributes = mEmptyItem1.mergeAttributes(mEmptyItem2); + assertNull(attributes.get("string")); + assertNull(attributes.get("integer")); + + attributes = mEmptyItem2.mergeAttributes(mEmptyItem1); + assertNull(attributes.get("string")); + assertNull(attributes.get("integer")); + + attributes = mEmptyItem1.mergeAttributes(mStringItem); + assertEquals(mStringAttribute, attributes.get("string")); + assertNull(attributes.get("integer")); + + attributes = mStringItem.mergeAttributes(mEmptyItem1); + assertEquals(mStringAttribute, attributes.get("string")); + assertNull(attributes.get("integer")); + + attributes = mIntegerItem.mergeAttributes(mStringItem); + assertEquals(mStringAttribute, attributes.get("string")); + assertEquals(mIntegerAttribute, attributes.get("integer")); + + attributes = mEmptyItem1.mergeAttributes(mFullItem1); + assertEquals(mStringAttribute, attributes.get("string")); + assertEquals(mIntegerAttribute, attributes.get("integer")); + + attributes = mFullItem1.mergeAttributes(mEmptyItem1); + assertEquals(mStringAttribute, attributes.get("string")); + assertEquals(mIntegerAttribute, attributes.get("integer")); + + attributes = mFullItem1.mergeAttributes(mFullItem2); + assertEquals(mStringAttribute, attributes.get("string")); + assertEquals(mIntegerAttribute, attributes.get("integer")); + + try { + mFullItem1.mergeAttributes(mInconsistentItem); + fail("Expecting a ConflictingItemException"); + } catch (ConflictingItemException e) { + // Expected + } + } + + /** + * Test for {@link GenericItem#isConsistent(IItem)}. + */ + public void testIsConsistent() { + assertTrue(mEmptyItem1.isConsistent(mEmptyItem1)); + assertFalse(mEmptyItem1.isConsistent(null)); + assertTrue(mEmptyItem1.isConsistent(mEmptyItem2)); + assertTrue(mEmptyItem2.isConsistent(mEmptyItem1)); + assertTrue(mEmptyItem1.isConsistent(mStringItem)); + assertTrue(mStringItem.isConsistent(mEmptyItem1)); + assertTrue(mIntegerItem.isConsistent(mStringItem)); + assertTrue(mEmptyItem1.isConsistent(mFullItem1)); + assertTrue(mFullItem1.isConsistent(mEmptyItem1)); + assertTrue(mFullItem1.isConsistent(mFullItem2)); + assertFalse(mFullItem1.isConsistent(mInconsistentItem)); + } + + /** + * Test {@link GenericItem#equals(Object)}. + */ + public void testEquals() { + assertTrue(mEmptyItem1.equals(mEmptyItem1)); + assertFalse(mEmptyItem1.equals(null)); + assertTrue(mEmptyItem1.equals(mEmptyItem2)); + assertTrue(mEmptyItem2.equals(mEmptyItem1)); + assertFalse(mEmptyItem1.equals(mStringItem)); + assertFalse(mStringItem.equals(mEmptyItem1)); + assertFalse(mIntegerItem.equals(mStringItem)); + assertFalse(mEmptyItem1.equals(mFullItem1)); + assertFalse(mFullItem1.equals(mEmptyItem1)); + assertTrue(mFullItem1.equals(mFullItem2)); + assertFalse(mFullItem1.equals(mInconsistentItem)); + } + + /** + * Test for {@link GenericItem#setAttribute(String, Object)} and + * {@link GenericItem#getAttribute(String)}. + */ + public void testAttributes() { + GenericItem item = new GenericItem(null, ATTRIBUTES); + + assertNull(item.getAttribute("string")); + assertNull(item.getAttribute("integer")); + + item.setAttribute("string", mStringAttribute); + item.setAttribute("integer", mIntegerAttribute); + + assertEquals(mStringAttribute, item.getAttribute("string")); + assertEquals(mIntegerAttribute, item.getAttribute("integer")); + + item.setAttribute("string", null); + item.setAttribute("integer", null); + + assertNull(item.getAttribute("string")); + assertNull(item.getAttribute("integer")); + + try { + item.setAttribute("object", new Object()); + fail("Failed to throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected because "object" is not "string" or "integer". + } + } + + /** + * Test for {@link GenericItem#areEqual(Object, Object)} + */ + public void testAreEqual() { + assertTrue(GenericItem.areEqual(null, null)); + assertTrue(GenericItem.areEqual("test", "test")); + assertFalse(GenericItem.areEqual(null, "test")); + assertFalse(GenericItem.areEqual("test", null)); + assertFalse(GenericItem.areEqual("test", "")); + } + + /** + * Test for {@link GenericItem#areConsistent(Object, Object)} + */ + public void testAreConsistent() { + assertTrue(GenericItem.areConsistent(null, null)); + assertTrue(GenericItem.areConsistent("test", "test")); + assertTrue(GenericItem.areConsistent(null, "test")); + assertTrue(GenericItem.areConsistent("test", null)); + assertFalse(GenericItem.areConsistent("test", "")); + } + + /** + * Test for {@link GenericItem#mergeObjects(Object, Object)} + */ + public void testMergeObjects() throws ConflictingItemException { + assertNull(GenericItem.mergeObjects(null, null)); + assertEquals("test", GenericItem.mergeObjects("test", "test")); + assertEquals("test", GenericItem.mergeObjects(null, "test")); + assertEquals("test", GenericItem.mergeObjects("test", null)); + + try { + assertEquals("test", GenericItem.mergeObjects("test", "")); + fail("Expected ConflictingItemException to be thrown"); + } catch (ConflictingItemException e) { + // Expected because "test" conflicts with "". + } + } +} diff --git a/tests/src/com/android/loganalysis/parser/AbstractSectionParserTest.java b/tests/src/com/android/loganalysis/parser/AbstractSectionParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1092d0e247af13f59def234c26f8ad55b008e900 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/AbstractSectionParserTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.IItem; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for {@link AbstractSectionParser} + */ +public class AbstractSectionParserTest extends TestCase { + AbstractSectionParser mParser = null; + + @Override + public void setUp() throws Exception { + super.setUp(); + mParser = new AbstractSectionParser() { + @Override + public IItem parse(List<String> lines) { + for (String line : lines) { + parseLine(line); + } + commit(); + return null; + } + }; + } + + private static class FakeBlockParser implements IParser { + private String mExpected = null; + private int mCalls = 0; + + public FakeBlockParser(String expected) { + mExpected = expected; + } + + public int getCalls() { + return mCalls; + } + + @Override + public IItem parse(List<String> input) { + assertEquals(1, input.size()); + assertEquals("parseBlock() got unexpected input!", mExpected, input.get(0)); + mCalls += 1; + return null; + } + } + + /** + * Verifies that {@link AbstractSectionParser} switches between parsers as expected + */ + public void testSwitchParsers() { + final String lineFormat = "howdy, parser %d!"; + final String linePattern = "I spy %d candles"; + final int nParsers = 4; + FakeBlockParser[] parsers = new FakeBlockParser[nParsers]; + final List<String> lines = new ArrayList<String>(2*nParsers); + + for (int i = 0; i < nParsers; ++i) { + String line = String.format(lineFormat, i); + FakeBlockParser parser = new FakeBlockParser(line); + mParser.addSectionParser(parser, String.format(linePattern, i)); + parsers[i] = parser; + + // add the parser trigger + lines.add(String.format(linePattern, i)); + // and then add the line that the parser is expecting + lines.add(String.format(lineFormat, i)); + } + + mParser.parse(lines); + + // Verify that all the parsers were run + for (int i = 0; i < nParsers; ++i) { + assertEquals(String.format("Parser %d has wrong call count!", i), 1, + parsers[i].getCalls()); + } + } +} + diff --git a/tests/src/com/android/loganalysis/parser/AnrParserTest.java b/tests/src/com/android/loganalysis/parser/AnrParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..119f3a8d5cee3a160e63b2908b2abb7941755803 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/AnrParserTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.AnrItem; +import com.android.loganalysis.util.ArrayUtil; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link AnrParser}. + */ +public class AnrParserTest extends TestCase { + + /** + * Test that ANRs are parsed for the header "ANR (application not responding) in process: app" + */ + public void testParse_application_not_responding() { + List<String> lines = Arrays.asList( + "ANR (application not responding) in process: com.android.package", + "Reason: keyDispatchingTimedOut", + "Load: 0.71 / 0.83 / 0.51", + "CPU usage from 4357ms to -1434ms ago:", + " 22% 3378/com.android.package: 19% user + 3.6% kernel / faults: 73 minor 1 major", + " 16% 312/system_server: 12% user + 4.1% kernel / faults: 1082 minor 6 major", + "33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "CPU usage from 907ms to 1431ms later:", + " 14% 121/mediaserver: 11% user + 3.7% kernel / faults: 17 minor", + " 3.7% 183/AudioOut_2: 3.7% user + 0% kernel", + " 12% 312/system_server: 5.5% user + 7.4% kernel / faults: 6 minor", + " 5.5% 366/InputDispatcher: 0% user + 5.5% kernel", + "18% TOTAL: 11% user + 7.5% kernel"); + + AnrItem anr = new AnrParser().parse(lines); + assertNotNull(anr); + assertEquals("com.android.package", anr.getApp()); + assertEquals("keyDispatchingTimedOut", anr.getReason()); + assertEquals(0.71, anr.getLoad(AnrItem.LoadCategory.LOAD_1)); + assertEquals(0.83, anr.getLoad(AnrItem.LoadCategory.LOAD_5)); + assertEquals(0.51, anr.getLoad(AnrItem.LoadCategory.LOAD_15)); + assertEquals(33.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.TOTAL)); + assertEquals(21.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.USER)); + assertEquals(11.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.KERNEL)); + assertEquals(0.3, anr.getCpuUsage(AnrItem.CpuUsageCategory.IOWAIT)); + assertEquals(ArrayUtil.join("\n", lines), anr.getStack()); + } + + /** + * Test that ANRs are parsed for the header "ANR in app" + */ + public void testParse_anr_in_app() { + List<String> lines = Arrays.asList( + "ANR in com.android.package", + "Reason: keyDispatchingTimedOut", + "Load: 0.71 / 0.83 / 0.51", + "CPU usage from 4357ms to -1434ms ago:", + " 22% 3378/com.android.package: 19% user + 3.6% kernel / faults: 73 minor 1 major", + " 16% 312/system_server: 12% user + 4.1% kernel / faults: 1082 minor 6 major", + "33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "CPU usage from 907ms to 1431ms later:", + " 14% 121/mediaserver: 11% user + 3.7% kernel / faults: 17 minor", + " 3.7% 183/AudioOut_2: 3.7% user + 0% kernel", + " 12% 312/system_server: 5.5% user + 7.4% kernel / faults: 6 minor", + " 5.5% 366/InputDispatcher: 0% user + 5.5% kernel", + "18% TOTAL: 11% user + 7.5% kernel"); + + AnrItem anr = new AnrParser().parse(lines); + assertNotNull(anr); + assertEquals("com.android.package", anr.getApp()); + assertEquals("keyDispatchingTimedOut", anr.getReason()); + assertEquals(0.71, anr.getLoad(AnrItem.LoadCategory.LOAD_1)); + assertEquals(0.83, anr.getLoad(AnrItem.LoadCategory.LOAD_5)); + assertEquals(0.51, anr.getLoad(AnrItem.LoadCategory.LOAD_15)); + assertEquals(33.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.TOTAL)); + assertEquals(21.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.USER)); + assertEquals(11.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.KERNEL)); + assertEquals(0.3, anr.getCpuUsage(AnrItem.CpuUsageCategory.IOWAIT)); + assertEquals(ArrayUtil.join("\n", lines), anr.getStack()); + } + + /** + * Test that ANRs are parsed for the header "ANR in app (class/package)" + */ + public void testParse_anr_in_app_class_package() { + List<String> lines = Arrays.asList( + "ANR in com.android.package (com.android.package/.Activity)", + "Reason: keyDispatchingTimedOut", + "Load: 0.71 / 0.83 / 0.51", + "CPU usage from 4357ms to -1434ms ago:", + " 22% 3378/com.android.package: 19% user + 3.6% kernel / faults: 73 minor 1 major", + " 16% 312/system_server: 12% user + 4.1% kernel / faults: 1082 minor 6 major", + "33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "CPU usage from 907ms to 1431ms later:", + " 14% 121/mediaserver: 11% user + 3.7% kernel / faults: 17 minor", + " 3.7% 183/AudioOut_2: 3.7% user + 0% kernel", + " 12% 312/system_server: 5.5% user + 7.4% kernel / faults: 6 minor", + " 5.5% 366/InputDispatcher: 0% user + 5.5% kernel", + "18% TOTAL: 11% user + 7.5% kernel"); + + AnrItem anr = new AnrParser().parse(lines); + assertNotNull(anr); + assertEquals("com.android.package", anr.getApp()); + assertEquals("keyDispatchingTimedOut", anr.getReason()); + assertEquals(0.71, anr.getLoad(AnrItem.LoadCategory.LOAD_1)); + assertEquals(0.83, anr.getLoad(AnrItem.LoadCategory.LOAD_5)); + assertEquals(0.51, anr.getLoad(AnrItem.LoadCategory.LOAD_15)); + assertEquals(33.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.TOTAL)); + assertEquals(21.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.USER)); + assertEquals(11.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.KERNEL)); + assertEquals(0.3, anr.getCpuUsage(AnrItem.CpuUsageCategory.IOWAIT)); + assertEquals(ArrayUtil.join("\n", lines), anr.getStack()); + } +} diff --git a/tests/src/com/android/loganalysis/parser/BugreportParserFuncTest.java b/tests/src/com/android/loganalysis/parser/BugreportParserFuncTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2a456d283580da97bf083b6bfbd49a8c474cc706 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/BugreportParserFuncTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.BugreportItem; + +import junit.framework.TestCase; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +/** + * Functional tests for {@link BugreportParser} + */ +public class BugreportParserFuncTest extends TestCase { + // FIXME: Make bugreport file configurable. + private static final String BUGREPORT_PATH = "/tmp/bugreport.txt"; + + /** + * A test that is intended to force Brillopad to parse a bugreport. The purpose of this is to + * assist a developer in checking why a given bugreport file might not be parsed correctly by + * Brillopad. + */ + public void testParse() { + BufferedReader bugreportReader = null; + try { + bugreportReader = new BufferedReader(new FileReader(BUGREPORT_PATH)); + } catch (FileNotFoundException e) { + fail(String.format("File not found at %s", BUGREPORT_PATH)); + } + BugreportItem bugreport = null; + try { + long start = System.currentTimeMillis(); + bugreport = new BugreportParser().parse(bugreportReader); + long stop = System.currentTimeMillis(); + System.out.println(String.format("Bugreport took %d ms to parse.", stop - start)); + } catch (IOException e) { + fail(String.format("IOException: %s", e.toString())); + } finally { + if (bugreportReader != null) { + try { + bugreportReader.close(); + } catch (IOException e) { + // Ignore + } + } + } + + assertNotNull(bugreport); + assertNotNull(bugreport.getTime()); + + assertNotNull(bugreport.getSystemProps()); + assertTrue(bugreport.getSystemProps().size() > 0); + + assertNotNull(bugreport.getMemInfo()); + assertTrue(bugreport.getMemInfo().size() > 0); + + assertNotNull(bugreport.getProcrank()); + assertTrue(bugreport.getProcrank().getPids().size() > 0); + + assertNotNull(bugreport.getSystemLog()); + assertNotNull(bugreport.getSystemLog().getStartTime()); + assertNotNull(bugreport.getSystemLog().getStopTime()); + + System.out.println(String.format("Stats for bugreport:\n" + + " Time: %s\n" + + " System Properties: %d items\n" + + " Mem info: %d items\n" + + " Procrank: %d items\n" + + " System Log:\n" + + " Start time: %s\n" + + " Stop time: %s\n" + + " %d ANR(s), %d Java Crash(es), %d Native Crash(es)", + bugreport.getTime(), + bugreport.getSystemProps().size(), + bugreport.getMemInfo().size(), + bugreport.getProcrank().getPids().size(), + bugreport.getSystemLog().getStartTime().toString(), + bugreport.getSystemLog().getStopTime().toString(), + bugreport.getSystemLog().getAnrs().size(), + bugreport.getSystemLog().getJavaCrashes().size(), + bugreport.getSystemLog().getNativeCrashes().size())); + } +} + diff --git a/tests/src/com/android/loganalysis/parser/BugreportParserTest.java b/tests/src/com/android/loganalysis/parser/BugreportParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b5f973e1306a9b4a023c5df4c1e2977a107be988 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/BugreportParserTest.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.BugreportItem; +import com.android.loganalysis.util.ArrayUtil; + +import junit.framework.TestCase; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * Unit tests for {@link BugreportParser} + */ +public class BugreportParserTest extends TestCase { + + /** + * Test that a bugreport can be parsed. + */ + public void testParse() throws ParseException { + List<String> lines = Arrays.asList( + "========================================================", + "== dumpstate: 2012-04-25 20:45:10", + "========================================================", + "------ SECTION ------", + "", + "------ MEMORY INFO (/proc/meminfo) ------", + "MemTotal: 353332 kB", + "MemFree: 65420 kB", + "Buffers: 20800 kB", + "Cached: 86204 kB", + "SwapCached: 0 kB", + "", + "------ PROCRANK (procrank) ------", + " PID Vss Rss Pss Uss cmdline", + " 178 87136K 81684K 52829K 50012K system_server", + " 1313 78128K 77996K 48603K 45812K com.google.android.apps.maps", + " 3247 61652K 61492K 33122K 30972K com.android.browser", + " ------ ------ ------", + " 203624K 163604K TOTAL", + "RAM: 731448K total, 415804K free, 9016K buffers, 108548K cached", + "[procrank: 1.6s elapsed]", + "", + "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)", + "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "04-25 18:33:27.273 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'", + "04-25 18:33:27.273 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<", + "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000", + "", + "------ SYSTEM PROPERTIES ------", + "[dalvik.vm.dexopt-flags]: [m=y]", + "[dalvik.vm.heapgrowthlimit]: [48m]", + "[dalvik.vm.heapsize]: [256m]", + "[gsm.version.ril-impl]: [android moto-ril-multimode 1.0]", + "", + "------ SECTION ------", + "", + "------ VM TRACES AT LAST ANR (/data/anr/traces.txt: 2012-04-25 17:17:08) ------", + "", + "", + "----- pid 2887 at 2012-04-25 17:17:08 -----", + "Cmd line: com.android.package", + "", + "DALVIK THREADS:", + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)", + "", + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)", + "", + "----- end 2887 -----", + "", + "------ SECTION ------", + ""); + + BugreportItem bugreport = new BugreportParser().parse(lines); + assertNotNull(bugreport); + assertEquals(parseTime("2012-04-25 20:45:10.000"), bugreport.getTime()); + + assertNotNull(bugreport.getMemInfo()); + assertEquals(5, bugreport.getMemInfo().size()); + + assertNotNull(bugreport.getProcrank()); + assertEquals(3, bugreport.getProcrank().getPids().size()); + + assertNotNull(bugreport.getSystemLog()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), bugreport.getSystemLog().getStartTime()); + assertEquals(parseTime("2012-04-25 18:33:27.273"), bugreport.getSystemLog().getStopTime()); + assertEquals(3, bugreport.getSystemLog().getEvents().size()); + assertEquals(1, bugreport.getSystemLog().getAnrs().size()); + assertNotNull(bugreport.getSystemLog().getAnrs().get(0).getTrace()); + + assertNotNull(bugreport.getSystemProps()); + assertEquals(4, bugreport.getSystemProps().size()); + } + + /** + * Test that the logcat year is set correctly from the bugreport timestamp. + */ + public void testParse_set_logcat_year() throws ParseException { + List<String> lines = Arrays.asList( + "========================================================", + "== dumpstate: 1999-01-01 02:03:04", + "========================================================", + "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------", + "01-01 01:02:03.000 1 1 I TAG : message", + "01-01 01:02:04.000 1 1 I TAG : message", + ""); + + BugreportItem bugreport = new BugreportParser().parse(lines); + assertNotNull(bugreport); + assertEquals(parseTime("1999-01-01 02:03:04.000"), bugreport.getTime()); + assertNotNull(bugreport.getSystemLog()); + assertEquals(parseTime("1999-01-01 01:02:03.000"), bugreport.getSystemLog().getStartTime()); + assertEquals(parseTime("1999-01-01 01:02:04.000"), bugreport.getSystemLog().getStopTime()); + } + + /** + * Test that the trace is set correctly if there is only one ANR. + */ + public void testSetAnrTrace_single() { + List<String> lines = Arrays.asList( + "========================================================", + "== dumpstate: 2012-04-25 20:45:10", + "========================================================", + "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------", + "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "", + "------ VM TRACES AT LAST ANR (/data/anr/traces.txt: 2012-04-25 17:17:08) ------", + "", + "----- pid 2887 at 2012-04-25 17:17:08 -----", + "Cmd line: com.android.package", + "", + "DALVIK THREADS:", + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)", + "", + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)", + "", + "----- end 2887 -----", + ""); + + List<String> expectedStack = Arrays.asList( + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)"); + + BugreportItem bugreport = new BugreportParser().parse(lines); + + assertNotNull(bugreport.getSystemLog()); + assertEquals(1, bugreport.getSystemLog().getAnrs().size()); + assertEquals(ArrayUtil.join("\n", expectedStack), + bugreport.getSystemLog().getAnrs().get(0).getTrace()); + } + + /** + * Test that the trace is set correctly if there are multiple ANRs. + */ + public void testSetAnrTrace_multiple() { + List<String> lines = Arrays.asList( + "========================================================", + "== dumpstate: 2012-04-25 20:45:10", + "========================================================", + "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------", + "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "04-25 17:18:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:18:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:18:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:18:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "04-25 17:19:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.different.pacakge", + "04-25 17:19:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:19:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:19:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "", + "------ VM TRACES AT LAST ANR (/data/anr/traces.txt: 2012-04-25 17:18:08) ------", + "", + "----- pid 2887 at 2012-04-25 17:17:08 -----", + "Cmd line: com.android.package", + "", + "DALVIK THREADS:", + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)", + "", + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)", + "", + "----- end 2887 -----", + ""); + + List<String> expectedStack = Arrays.asList( + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)"); + + BugreportItem bugreport = new BugreportParser().parse(lines); + + assertNotNull(bugreport.getSystemLog()); + assertEquals(3, bugreport.getSystemLog().getAnrs().size()); + assertNull(bugreport.getSystemLog().getAnrs().get(0).getTrace()); + assertEquals(ArrayUtil.join("\n", expectedStack), + bugreport.getSystemLog().getAnrs().get(1).getTrace()); + assertNull(bugreport.getSystemLog().getAnrs().get(2).getTrace()); + } + + /** + * Test that the trace is set correctly if there is not traces file. + */ + public void testSetAnrTrace_no_traces() { + List<String> lines = Arrays.asList( + "========================================================", + "== dumpstate: 2012-04-25 20:45:10", + "========================================================", + "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------", + "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "", + "*** NO ANR VM TRACES FILE (/data/anr/traces.txt): No such file or directory", + ""); + + BugreportItem bugreport = new BugreportParser().parse(lines); + + assertNotNull(bugreport.getSystemLog()); + assertEquals(1, bugreport.getSystemLog().getAnrs().size()); + assertNull(bugreport.getSystemLog().getAnrs().get(0).getTrace()); + } + + /** + * Test that app names from logcat events are populated by matching the logcat PIDs with the + * PIDs from the logcat. + */ + public void testSetAppsFromProcrank() { + List<String> lines = Arrays.asList( + "========================================================", + "== dumpstate: 2012-04-25 20:45:10", + "========================================================", + "------ PROCRANK (procrank) ------", + " PID Vss Rss Pss Uss cmdline", + " 3064 87136K 81684K 52829K 50012K com.android.package", + " ------ ------ ------", + " 203624K 163604K TOTAL", + "RAM: 731448K total, 415804K free, 9016K buffers, 108548K cached", + "[procrank: 1.6s elapsed]", + "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)", + "04-25 09:55:47.799 3065 3083 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3065 3083 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3065 3083 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3065 3083 E AndroidRuntime: \tat class.method3(Class.java:3)"); + + BugreportItem bugreport = new BugreportParser().parse(lines); + assertNotNull(bugreport.getSystemLog()); + assertEquals(2, bugreport.getSystemLog().getJavaCrashes().size()); + assertEquals("com.android.package", + bugreport.getSystemLog().getJavaCrashes().get(0).getApp()); + assertNull(bugreport.getSystemLog().getJavaCrashes().get(1).getApp()); + } + + private Date parseTime(String timeStr) throws ParseException { + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + return formatter.parse(timeStr); + } + + /** + * Some Android devices refer to SYSTEM LOG as MAIN LOG. Check that parser recognizes this + * alternate syntax. + */ + public void testSystemLogAsMainLog() { + List<String> lines = Arrays.asList( + "------ MAIN LOG (logcat -b main -b system -v threadtime -d *:v) ------", + "--------- beginning of /dev/log/system", + "12-11 19:48:07.945 1484 1508 D BatteryService: update start"); + BugreportItem bugreport = new BugreportParser().parse(lines); + assertNotNull(bugreport.getSystemLog()); + } + + /** + * Some Android devices refer to SYSTEM LOG as MAIN AND SYSTEM LOG. Check that parser + * recognizes this alternate syntax. + */ + public void testSystemAndMainLog() { + List<String> lines = Arrays.asList( + "------ MAIN AND SYSTEM LOG (logcat -b main -b system -v threadtime -d *:v) ------", + "--------- beginning of /dev/log/system", + "12-17 15:15:12.877 1994 2019 D UiModeManager: updateConfigurationLocked: "); + BugreportItem bugreport = new BugreportParser().parse(lines); + assertNotNull(bugreport.getSystemLog()); + } +} + diff --git a/tests/src/com/android/loganalysis/parser/JavaCrashParserTest.java b/tests/src/com/android/loganalysis/parser/JavaCrashParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..723633f69699e630b5ca7d231bc520d9601d99f2 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/JavaCrashParserTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.JavaCrashItem; +import com.android.loganalysis.util.ArrayUtil; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link JavaCrashParser}. + */ +public class JavaCrashParserTest extends TestCase { + + /** + * Test that Java crashes are parsed with no message. + */ + public void testParse_no_message() { + List<String> lines = Arrays.asList( + "java.lang.Exception", + "\tat class.method1(Class.java:1)", + "\tat class.method2(Class.java:2)", + "\tat class.method3(Class.java:3)"); + + JavaCrashItem jc = new JavaCrashParser().parse(lines); + assertNotNull(jc); + assertEquals("java.lang.Exception", jc.getException()); + assertNull(jc.getMessage()); + assertEquals(ArrayUtil.join("\n", lines), jc.getStack()); + } + + /** + * Test that Java crashes are parsed with a message. + */ + public void testParse_message() { + List<String> lines = Arrays.asList( + "java.lang.Exception: This is the message", + "\tat class.method1(Class.java:1)", + "\tat class.method2(Class.java:2)", + "\tat class.method3(Class.java:3)"); + + JavaCrashItem jc = new JavaCrashParser().parse(lines); + assertNotNull(jc); + assertEquals("java.lang.Exception", jc.getException()); + assertEquals("This is the message", jc.getMessage()); + assertEquals(ArrayUtil.join("\n", lines), jc.getStack()); + } + + /** + * Test that Java crashes are parsed if the message spans multiple lines. + */ + public void testParse_multiline_message() { + List<String> lines = Arrays.asList( + "java.lang.Exception: This message", + "is many lines", + "long.", + "\tat class.method1(Class.java:1)", + "\tat class.method2(Class.java:2)", + "\tat class.method3(Class.java:3)"); + + JavaCrashItem jc = new JavaCrashParser().parse(lines); + assertNotNull(jc); + assertEquals("java.lang.Exception", jc.getException()); + assertEquals("This message\nis many lines\nlong.", jc.getMessage()); + assertEquals(ArrayUtil.join("\n", lines), jc.getStack()); + } + + /** + * Test that caused by sections of Java crashes are parsed, with no message or single or + * multiline messages. + */ + public void testParse_caused_by() { + List<String> lines = Arrays.asList( + "java.lang.Exception: This is the message", + "\tat class.method1(Class.java:1)", + "\tat class.method2(Class.java:2)", + "\tat class.method3(Class.java:3)", + "Caused by: java.lang.Exception", + "\tat class.method4(Class.java:4)", + "Caused by: java.lang.Exception: This is the caused by message", + "\tat class.method5(Class.java:5)", + "Caused by: java.lang.Exception: This is a multiline", + "caused by message", + "\tat class.method6(Class.java:6)"); + + JavaCrashItem jc = new JavaCrashParser().parse(lines); + assertNotNull(jc); + assertEquals("java.lang.Exception", jc.getException()); + assertEquals("This is the message", jc.getMessage()); + assertEquals(ArrayUtil.join("\n", lines), jc.getStack()); + } + + /** + * Test that the Java crash is cutoff if an unexpected line is handled. + */ + public void testParse_cutoff() { + List<String> lines = Arrays.asList( + "java.lang.Exception: This is the message", + "\tat class.method1(Class.java:1)", + "\tat class.method2(Class.java:2)", + "\tat class.method3(Class.java:3)", + "Invalid line", + "java.lang.Exception: This is the message"); + + JavaCrashItem jc = new JavaCrashParser().parse(lines); + assertNotNull(jc); + assertEquals("java.lang.Exception", jc.getException()); + assertEquals("This is the message", jc.getMessage()); + assertEquals(ArrayUtil.join("\n", lines.subList(0, lines.size()-2)), jc.getStack()); + } +} diff --git a/tests/src/com/android/loganalysis/parser/LogcatParserFuncTest.java b/tests/src/com/android/loganalysis/parser/LogcatParserFuncTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7d63b19d4ed32dd55ac99943b2e20790556aba72 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/LogcatParserFuncTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.LogcatItem; + +import junit.framework.TestCase; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +/** + * Functional tests for {@link LogcatParser} + */ +public class LogcatParserFuncTest extends TestCase { + // FIXME: Make logcat file configurable. + private static final String LOGCAT_PATH = "/tmp/logcat.txt"; + + /** + * A test that is intended to force Brillopad to parse a logcat. The purpose of this is to + * assist a developer in checking why a given logcat file might not be parsed correctly by + * Brillopad. + */ + public void testParse() { + BufferedReader logcatReader = null; + try { + logcatReader = new BufferedReader(new FileReader(LOGCAT_PATH)); + } catch (FileNotFoundException e) { + fail(String.format("File not found at %s", LOGCAT_PATH)); + } + LogcatItem logcat = null; + try { + long start = System.currentTimeMillis(); + logcat = new LogcatParser().parse(logcatReader); + long stop = System.currentTimeMillis(); + System.out.println(String.format("Logcat took %d ms to parse.", stop - start)); + } catch (IOException e) { + fail(String.format("IOException: %s", e.toString())); + } finally { + if (logcatReader != null) { + try { + logcatReader.close(); + } catch (IOException e) { + // Ignore + } + } } + + assertNotNull(logcat); + assertNotNull(logcat.getStartTime()); + assertNotNull(logcat.getStopTime()); + + System.out.println(String.format("Stats for logcat:\n" + + " Start time: %s\n" + + " Stop time: %s\n" + + " %d ANR(s), %d Java Crash(es), %d Native Crash(es)", + logcat.getStartTime().toString(), + logcat.getStopTime().toString(), + logcat.getAnrs().size(), + logcat.getJavaCrashes().size(), + logcat.getNativeCrashes().size())); + } +} + diff --git a/tests/src/com/android/loganalysis/parser/LogcatParserTest.java b/tests/src/com/android/loganalysis/parser/LogcatParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..37a39b68512d88f69224700a827b95134f7676db --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/LogcatParserTest.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.LogcatItem; +import com.android.loganalysis.util.ArrayUtil; + +import junit.framework.TestCase; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * Unit tests for {@link LogcatParserTest}. + */ +public class LogcatParserTest extends TestCase { + + /** + * Test that an ANR is parsed in the log. + */ + public void testParse_anr() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait"); + + LogcatItem logcat = new LogcatParser("2012").parse(lines); + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getStopTime()); + assertEquals(1, logcat.getEvents().size()); + assertEquals(1, logcat.getAnrs().size()); + assertEquals(312, logcat.getAnrs().get(0).getPid().intValue()); + assertEquals(366, logcat.getAnrs().get(0).getTid().intValue()); + assertEquals("", logcat.getAnrs().get(0).getLastPreamble()); + assertEquals("", logcat.getAnrs().get(0).getProcessPreamble()); + assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(0).getEventTime()); + } + + /** + * Test that Java crashes can be parsed. + */ + public void testParse_java_crash() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)"); + + LogcatItem logcat = new LogcatParser("2012").parse(lines); + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime()); + assertEquals(1, logcat.getEvents().size()); + assertEquals(1, logcat.getJavaCrashes().size()); + assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue()); + assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue()); + assertEquals("", logcat.getJavaCrashes().get(0).getLastPreamble()); + assertEquals("", logcat.getJavaCrashes().get(0).getProcessPreamble()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(0).getEventTime()); + } + + /** + * Test that native crashes can be parsed. + */ + public void testParse_native_crash() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 18:33:27.273 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'", + "04-25 18:33:27.273 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<", + "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000"); + + LogcatItem logcat = new LogcatParser("2012").parse(lines); + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStopTime()); + assertEquals(1, logcat.getEvents().size()); + assertEquals(1, logcat.getNativeCrashes().size()); + assertEquals(115, logcat.getNativeCrashes().get(0).getPid().intValue()); + assertEquals(115, logcat.getNativeCrashes().get(0).getTid().intValue()); + assertEquals("", logcat.getNativeCrashes().get(0).getLastPreamble()); + assertEquals("", logcat.getNativeCrashes().get(0).getProcessPreamble()); + assertEquals(parseTime("2012-04-25 18:33:27.273"), + logcat.getNativeCrashes().get(0).getEventTime()); + } + + /** + * Test that multiple events can be parsed. + */ + public void testParse_multiple_events() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method3(Class.java:3)", + "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package", + "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut", + "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51", + "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait", + "04-25 18:33:27.273 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'", + "04-25 18:33:27.273 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<", + "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000", + "04-25 18:33:27.273 117 117 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "04-25 18:33:27.273 117 117 I DEBUG : Build fingerprint: 'product:build:target'", + "04-25 18:33:27.273 117 117 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<", + "04-25 18:33:27.273 117 117 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000"); + + + LogcatItem logcat = new LogcatParser("2012").parse(lines); + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStopTime()); + assertEquals(6, logcat.getEvents().size()); + assertEquals(2, logcat.getAnrs().size()); + assertEquals(2, logcat.getJavaCrashes().size()); + assertEquals(2, logcat.getNativeCrashes().size()); + + assertEquals(312, logcat.getAnrs().get(0).getPid().intValue()); + assertEquals(366, logcat.getAnrs().get(0).getTid().intValue()); + assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(0).getEventTime()); + + assertEquals(312, logcat.getAnrs().get(1).getPid().intValue()); + assertEquals(366, logcat.getAnrs().get(1).getTid().intValue()); + assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(1).getEventTime()); + + assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue()); + assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(0).getEventTime()); + + assertEquals(3065, logcat.getJavaCrashes().get(1).getPid().intValue()); + assertEquals(3090, logcat.getJavaCrashes().get(1).getTid().intValue()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(1).getEventTime()); + + assertEquals(115, logcat.getNativeCrashes().get(0).getPid().intValue()); + assertEquals(115, logcat.getNativeCrashes().get(0).getTid().intValue()); + assertEquals(parseTime("2012-04-25 18:33:27.273"), + logcat.getNativeCrashes().get(0).getEventTime()); + + assertEquals(117, logcat.getNativeCrashes().get(1).getPid().intValue()); + assertEquals(117, logcat.getNativeCrashes().get(1).getTid().intValue()); + assertEquals(parseTime("2012-04-25 18:33:27.273"), + logcat.getNativeCrashes().get(1).getEventTime()); + } + + /** + * Test that multiple java crashes and native crashes can be parsed even when interleaved. + */ + public void testParse_multiple_events_interleaved() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "04-25 09:55:47.799 117 117 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 115 115 I DEBUG : Build fingerprint: 'product:build:target'", + "04-25 09:55:47.799 117 117 I DEBUG : Build fingerprint: 'product:build:target'", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<", + "04-25 09:55:47.799 117 117 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)", + "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method3(Class.java:3)", + "04-25 09:55:47.799 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000", + "04-25 09:55:47.799 117 117 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000"); + + LogcatItem logcat = new LogcatParser("2012").parse(lines); + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime()); + assertEquals(4, logcat.getEvents().size()); + assertEquals(0, logcat.getAnrs().size()); + assertEquals(2, logcat.getJavaCrashes().size()); + assertEquals(2, logcat.getNativeCrashes().size()); + + assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue()); + assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(0).getEventTime()); + + assertEquals(3065, logcat.getJavaCrashes().get(1).getPid().intValue()); + assertEquals(3090, logcat.getJavaCrashes().get(1).getTid().intValue()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(1).getEventTime()); + + assertEquals(115, logcat.getNativeCrashes().get(0).getPid().intValue()); + assertEquals(115, logcat.getNativeCrashes().get(0).getTid().intValue()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getNativeCrashes().get(0).getEventTime()); + + assertEquals(117, logcat.getNativeCrashes().get(1).getPid().intValue()); + assertEquals(117, logcat.getNativeCrashes().get(1).getTid().intValue()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getNativeCrashes().get(1).getEventTime()); + } + + /** + * Test that the preambles are set correctly if there's only partial preambles. + */ + public void testParse_partial_preambles() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 09:15:47.799 123 3082 I tag: message 1", + "04-25 09:20:47.799 3064 3082 I tag: message 2", + "04-25 09:25:47.799 345 3082 I tag: message 3", + "04-25 09:30:47.799 3064 3082 I tag: message 4", + "04-25 09:35:47.799 456 3082 I tag: message 5", + "04-25 09:40:47.799 3064 3082 I tag: message 6", + "04-25 09:45:47.799 567 3082 I tag: message 7", + "04-25 09:50:47.799 3064 3082 I tag: message 8", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)"); + + List<String> expectedLastPreamble = Arrays.asList( + "04-25 09:15:47.799 123 3082 I tag: message 1", + "04-25 09:20:47.799 3064 3082 I tag: message 2", + "04-25 09:25:47.799 345 3082 I tag: message 3", + "04-25 09:30:47.799 3064 3082 I tag: message 4", + "04-25 09:35:47.799 456 3082 I tag: message 5", + "04-25 09:40:47.799 3064 3082 I tag: message 6", + "04-25 09:45:47.799 567 3082 I tag: message 7", + "04-25 09:50:47.799 3064 3082 I tag: message 8"); + + List<String> expectedProcPreamble = Arrays.asList( + "04-25 09:20:47.799 3064 3082 I tag: message 2", + "04-25 09:30:47.799 3064 3082 I tag: message 4", + "04-25 09:40:47.799 3064 3082 I tag: message 6", + "04-25 09:50:47.799 3064 3082 I tag: message 8"); + + LogcatItem logcat = new LogcatParser("2012").parse(lines); + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 09:15:47.799"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime()); + assertEquals(1, logcat.getEvents().size()); + assertEquals(1, logcat.getJavaCrashes().size()); + assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue()); + assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue()); + assertEquals(ArrayUtil.join("\n", expectedLastPreamble), + logcat.getJavaCrashes().get(0).getLastPreamble()); + assertEquals(ArrayUtil.join("\n", expectedProcPreamble), + logcat.getJavaCrashes().get(0).getProcessPreamble()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(0).getEventTime()); + } + + /** + * Test that the preambles are set correctly if there's only full preambles. + */ + public void testParse_preambles() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 09:43:47.799 3064 3082 I tag: message 1", + "04-25 09:44:47.799 123 3082 I tag: message 2", + "04-25 09:45:47.799 3064 3082 I tag: message 3", + "04-25 09:46:47.799 234 3082 I tag: message 4", + "04-25 09:47:47.799 3064 3082 I tag: message 5", + "04-25 09:48:47.799 345 3082 I tag: message 6", + "04-25 09:49:47.799 3064 3082 I tag: message 7", + "04-25 09:50:47.799 456 3082 I tag: message 8", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)"); + + List<String> expectedLastPreamble = Arrays.asList( + "04-25 09:48:47.799 345 3082 I tag: message 6", + "04-25 09:49:47.799 3064 3082 I tag: message 7", + "04-25 09:50:47.799 456 3082 I tag: message 8"); + + List<String> expectedProcPreamble = Arrays.asList( + "04-25 09:45:47.799 3064 3082 I tag: message 3", + "04-25 09:47:47.799 3064 3082 I tag: message 5", + "04-25 09:49:47.799 3064 3082 I tag: message 7"); + + LogcatItem logcat = new LogcatParser("2012") { + @Override + int getLastPreambleSize() { + return 3; + } + + @Override + int getProcPreambleSize() { + return 3; + } + }.parse(lines); + + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 09:43:47.799"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime()); + assertEquals(1, logcat.getEvents().size()); + assertEquals(1, logcat.getJavaCrashes().size()); + assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue()); + assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue()); + assertEquals(ArrayUtil.join("\n", expectedLastPreamble), + logcat.getJavaCrashes().get(0).getLastPreamble()); + assertEquals(ArrayUtil.join("\n", expectedProcPreamble), + logcat.getJavaCrashes().get(0).getProcessPreamble()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(0).getEventTime()); + } + + /** + * Test that the time logcat format can be parsed. + */ + public void testParse_time() throws ParseException { + List<String> lines = Arrays.asList( + "04-25 09:55:47.799 E/AndroidRuntime(3064): java.lang.Exception", + "04-25 09:55:47.799 E/AndroidRuntime(3064): \tat class.method1(Class.java:1)", + "04-25 09:55:47.799 E/AndroidRuntime(3064): \tat class.method2(Class.java:2)", + "04-25 09:55:47.799 E/AndroidRuntime(3064): \tat class.method3(Class.java:3)"); + + LogcatItem logcat = new LogcatParser("2012").parse(lines); + assertNotNull(logcat); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime()); + assertEquals(1, logcat.getEvents().size()); + assertEquals(1, logcat.getJavaCrashes().size()); + assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue()); + assertNull(logcat.getJavaCrashes().get(0).getTid()); + assertEquals(parseTime("2012-04-25 09:55:47.799"), + logcat.getJavaCrashes().get(0).getEventTime()); + } + + private Date parseTime(String timeStr) throws ParseException { + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + return formatter.parse(timeStr); + } +} diff --git a/tests/src/com/android/loganalysis/parser/MemInfoParserTest.java b/tests/src/com/android/loganalysis/parser/MemInfoParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2069d12ecedfac10d4cae3fec841abfe126d9683 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/MemInfoParserTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.MemInfoItem; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link MemInfoParser} + */ +public class MemInfoParserTest extends TestCase { + public void testMemInfoParser() { + List<String> inputBlock = Arrays.asList( + "MemTotal: 353332 kB", + "MemFree: 65420 kB", + "Buffers: 20800 kB", + "Cached: 86204 kB", + "SwapCached: 0 kB"); + MemInfoParser parser = new MemInfoParser(); + MemInfoItem output = parser.parse(inputBlock); + + assertEquals(5, output.size()); + assertEquals((Integer)353332, output.get("MemTotal")); + assertEquals((Integer)65420, output.get("MemFree")); + assertEquals((Integer)20800, output.get("Buffers")); + assertEquals((Integer)86204, output.get("Cached")); + assertEquals((Integer)0, output.get("SwapCached")); + } +} diff --git a/tests/src/com/android/loganalysis/parser/MonkeyLogParserFuncTest.java b/tests/src/com/android/loganalysis/parser/MonkeyLogParserFuncTest.java new file mode 100644 index 0000000000000000000000000000000000000000..58fcebe4faebd873de828080b251397b7b742bb5 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/MonkeyLogParserFuncTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.AnrItem; +import com.android.loganalysis.item.JavaCrashItem; +import com.android.loganalysis.item.MonkeyLogItem; +import com.android.loganalysis.item.MonkeyLogItem.DroppedCategory; + +import junit.framework.TestCase; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +/** + * Functional tests for {@link MonkeyLogParser} + */ +public class MonkeyLogParserFuncTest extends TestCase { + // FIXME: Make monkey log file configurable. + private static final String MONKEY_LOG_PATH = "/tmp/monkey_log.txt"; + + /** + * A test that is intended to force Brillopad to parse a monkey log. The purpose of this is to + * assist a developer in checking why a given monkey log file might not be parsed correctly by + * Brillopad. + */ + public void testParse() { + BufferedReader monkeyLogReader = null; + try { + monkeyLogReader = new BufferedReader(new FileReader(MONKEY_LOG_PATH)); + } catch (FileNotFoundException e) { + fail(String.format("File not found at %s", MONKEY_LOG_PATH)); + } + MonkeyLogItem monkeyLog = null; + try { + long start = System.currentTimeMillis(); + monkeyLog = new MonkeyLogParser().parse(monkeyLogReader); + long stop = System.currentTimeMillis(); + System.out.println(String.format("Monkey log took %d ms to parse.", stop - start)); + } catch (IOException e) { + fail(String.format("IOException: %s", e.toString())); + } finally { + if (monkeyLogReader != null) { + try { + monkeyLogReader.close(); + } catch (IOException e) { + // Ignore + } + } } + + assertNotNull(monkeyLog); + assertNotNull(monkeyLog.getStartTime()); + assertNotNull(monkeyLog.getStopTime()); + assertNotNull(monkeyLog.getTargetCount()); + assertNotNull(monkeyLog.getThrottle()); + assertNotNull(monkeyLog.getSeed()); + assertNotNull(monkeyLog.getIgnoreSecurityExceptions()); + assertTrue(monkeyLog.getPackages().size() > 0); + assertTrue(monkeyLog.getCategories().size() > 0); + assertNotNull(monkeyLog.getIsFinished()); + assertNotNull(monkeyLog.getIntermediateCount()); + assertNotNull(monkeyLog.getTotalDuration()); + assertNotNull(monkeyLog.getStartUptimeDuration()); + assertNotNull(monkeyLog.getStopUptimeDuration()); + + + StringBuffer sb = new StringBuffer(); + sb.append("Stats for monkey log:\n"); + sb.append(String.format(" Start time: %s\n", monkeyLog.getStartTime())); + sb.append(String.format(" Stop time: %s\n", monkeyLog.getStopTime())); + sb.append(String.format(" Parameters: target-count=%d, throttle=%d, seed=%d, " + + "ignore-security-exceptions=%b\n", + monkeyLog.getTargetCount(), monkeyLog.getThrottle(), monkeyLog.getSeed(), + monkeyLog.getIgnoreSecurityExceptions())); + sb.append(String.format(" Packages: %s\n", monkeyLog.getPackages())); + sb.append(String.format(" Categories: %s\n", monkeyLog.getCategories())); + if (monkeyLog.getNoActivities()) { + sb.append(" Status: no-activities=true\n"); + } else { + sb.append(String.format(" Status: finished=%b, final-count=%d, " + + "intermediate-count=%d\n", monkeyLog.getIsFinished(), monkeyLog.getFinalCount(), + monkeyLog.getIntermediateCount())); + + sb.append(" Dropped events:"); + for (DroppedCategory drop : DroppedCategory.values()) { + sb.append(String.format(" %s=%d,", drop.toString(), + monkeyLog.getDroppedCount(drop))); + } + sb.deleteCharAt(sb.length()-1); + sb.append("\n"); + } + sb.append(String.format(" Run time: duration=%d ms, delta-uptime=%d (%d - %d) ms\n", + monkeyLog.getTotalDuration(), + monkeyLog.getStopUptimeDuration() - monkeyLog.getStartUptimeDuration(), + monkeyLog.getStopUptimeDuration(), monkeyLog.getStartUptimeDuration())); + + if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof AnrItem) { + sb.append(String.format(" Stopped due to ANR\n")); + } + if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof JavaCrashItem) { + sb.append(String.format(" Stopped due to Java crash\n")); + } + System.out.println(sb.toString()); + } +} + diff --git a/tests/src/com/android/loganalysis/parser/MonkeyLogParserTest.java b/tests/src/com/android/loganalysis/parser/MonkeyLogParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d5aef867bff0c5a5162ca1e9bbba23dafbb490ad --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/MonkeyLogParserTest.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.AnrItem; +import com.android.loganalysis.item.JavaCrashItem; +import com.android.loganalysis.item.MonkeyLogItem; +import com.android.loganalysis.item.MonkeyLogItem.DroppedCategory; +import com.android.loganalysis.util.ArrayUtil; + +import junit.framework.TestCase; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * Unit tests for {@link MonkeyLogParser} + */ +public class MonkeyLogParserTest extends TestCase { + + /** + * Test that a monkey can be parsed if there are no crashes. + */ + public void testParse_success() throws ParseException { + List<String> lines = Arrays.asList( + "# Wednesday, 04/25/2012 01:37:12 AM - device uptime = 242.13: Monkey command used for this test:", + "adb shell monkey -p com.google.android.browser -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 528 -v -v -v 10000 ", + "", + ":Monkey: seed=528 count=10000", + ":AllowPackage: com.google.android.browser", + ":IncludeCategory: android.intent.category.LAUNCHER", + ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.browser/com.android.browser.BrowserActivity;end", + " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.browser/com.android.browser.BrowserActivity } in package com.google.android.browser", + "Sleeping for 100 milliseconds", + ":Sending Key (ACTION_DOWN): 23 // KEYCODE_DPAD_CENTER", + ":Sending Key (ACTION_UP): 23 // KEYCODE_DPAD_CENTER", + "Sleeping for 100 milliseconds", + ":Sending Trackball (ACTION_MOVE): 0:(-5.0,3.0)", + ":Sending Trackball (ACTION_MOVE): 0:(3.0,3.0)", + ":Sending Trackball (ACTION_MOVE): 0:(-1.0,3.0)", + ":Sending Trackball (ACTION_MOVE): 0:(4.0,-2.0)", + ":Sending Trackball (ACTION_MOVE): 0:(1.0,4.0)", + ":Sending Trackball (ACTION_MOVE): 0:(-4.0,2.0)", + " //[calendar_time:2012-04-25 01:42:20.140 system_uptime:535179]", + " // Sending event #9900", + ":Sending Trackball (ACTION_MOVE): 0:(2.0,-4.0)", + ":Sending Trackball (ACTION_MOVE): 0:(-2.0,0.0)", + ":Sending Trackball (ACTION_MOVE): 0:(2.0,2.0)", + ":Sending Trackball (ACTION_MOVE): 0:(-5.0,4.0)", + "Events injected: 10000", + ":Dropped: keys=5 pointers=6 trackballs=7 flips=8 rotations=9", + "// Monkey finished", + "", + "# Wednesday, 04/25/2012 01:42:09 AM - device uptime = 539.21: Monkey command ran for: 04:57 (mm:ss)", + "", + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + + MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines); + assertNotNull(monkeyLog); + // FIXME: Add test back once time situation has been worked out. + // assertEquals(parseTime("2012-04-25 01:37:12"), monkeyLog.getStartTime()); + // assertEquals(parseTime("2012-04-25 01:42:09"), monkeyLog.getStopTime()); + assertEquals(1, monkeyLog.getPackages().size()); + assertTrue(monkeyLog.getPackages().contains("com.google.android.browser")); + assertEquals(1, monkeyLog.getCategories().size()); + assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER")); + assertEquals(100, monkeyLog.getThrottle()); + assertEquals(528, monkeyLog.getSeed().intValue()); + assertEquals(10000, monkeyLog.getTargetCount().intValue()); + assertTrue(monkeyLog.getIgnoreSecurityExceptions()); + assertEquals(4 * 60 * 1000 + 57 * 1000, monkeyLog.getTotalDuration().longValue()); + assertEquals(242130, monkeyLog.getStartUptimeDuration().longValue()); + assertEquals(539210, monkeyLog.getStopUptimeDuration().longValue()); + assertTrue(monkeyLog.getIsFinished()); + assertFalse(monkeyLog.getNoActivities()); + assertEquals(9900, monkeyLog.getIntermediateCount()); + assertEquals(10000, monkeyLog.getFinalCount().intValue()); + assertEquals(5, monkeyLog.getDroppedCount(DroppedCategory.KEYS).intValue()); + assertEquals(6, monkeyLog.getDroppedCount(DroppedCategory.POINTERS).intValue()); + assertEquals(7, monkeyLog.getDroppedCount(DroppedCategory.TRACKBALLS).intValue()); + assertEquals(8, monkeyLog.getDroppedCount(DroppedCategory.FLIPS).intValue()); + assertEquals(9, monkeyLog.getDroppedCount(DroppedCategory.ROTATIONS).intValue()); + assertNull(monkeyLog.getCrash()); + } + + /** + * Test that a monkey can be parsed if there is an ANR. + */ + public void testParse_anr() throws ParseException { + List<String> lines = Arrays.asList( + "# Tuesday, 04/24/2012 05:23:30 PM - device uptime = 216.48: Monkey command used for this test:", + "adb shell monkey -p com.google.android.youtube -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 993 -v -v -v 10000 ", + "", + ":Monkey: seed=993 count=10000", + ":AllowPackage: com.google.android.youtube", + ":IncludeCategory: android.intent.category.LAUNCHER", + ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;end", + " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.youtube/.app.honeycomb.Shell$HomeActivity } in package com.google.android.youtube", + "Sleeping for 100 milliseconds", + ":Sending Key (ACTION_UP): 21 // KEYCODE_DPAD_LEFT", + "Sleeping for 100 milliseconds", + ":Sending Key (ACTION_DOWN): 22 // KEYCODE_DPAD_RIGHT", + ":Sending Key (ACTION_UP): 22 // KEYCODE_DPAD_RIGHT", + " //[calendar_time:2012-04-25 00:27:27.155 system_uptime:454996]", + " // Sending event #5300", + ":Sending Key (ACTION_UP): 19 // KEYCODE_DPAD_UP", + "Sleeping for 100 milliseconds", + ":Sending Trackball (ACTION_MOVE): 0:(4.0,3.0)", + ":Sending Key (ACTION_DOWN): 20 // KEYCODE_DPAD_DOWN", + ":Sending Key (ACTION_UP): 20 // KEYCODE_DPAD_DOWN", + "// NOT RESPONDING: com.google.android.youtube (pid 3301)", + "ANR in com.google.android.youtube (com.google.android.youtube/.app.honeycomb.phone.WatchActivity)", + "Reason: keyDispatchingTimedOut", + "Load: 1.0 / 1.05 / 0.6", + "CPU usage from 4794ms to -1502ms ago with 99% awake:", + " 18% 3301/com.google.android.youtube: 16% user + 2.3% kernel / faults: 268 minor 9 major", + " 13% 313/system_server: 9.2% user + 4.4% kernel / faults: 906 minor 3 major", + " 10% 117/surfaceflinger: 4.9% user + 5.5% kernel / faults: 1 minor", + " 10% 120/mediaserver: 6.8% user + 3.6% kernel / faults: 1189 minor", + "34% TOTAL: 19% user + 13% kernel + 0.2% iowait + 1% softirq", + "", + "procrank:", + "// procrank status was 0", + "anr traces:", + "", + "", + "----- pid 2887 at 2012-04-25 17:17:08 -----", + "Cmd line: com.google.android.youtube", + "", + "DALVIK THREADS:", + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)", + "", + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)", + "", + "----- end 2887 -----", + "// anr traces status was 0", + "** Monkey aborted due to error.", + "Events injected: 5322", + ":Sending rotation degree=0, persist=false", + ":Dropped: keys=1 pointers=0 trackballs=0 flips=0 rotations=0", + "## Network stats: elapsed time=252942ms (0ms mobile, 252942ms wifi, 0ms not connected)", + "** System appears to have crashed at event 5322 of 10000 using seed 993", + "", + "# Tuesday, 04/24/2012 05:27:44 PM - device uptime = 471.37: Monkey command ran for: 04:14 (mm:ss)", + "", + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------", + ""); + + List<String> expectedStack = Arrays.asList( + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)"); + + MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines); + assertNotNull(monkeyLog); + // FIXME: Add test back once time situation has been worked out. + // assertEquals(parseTime("2012-04-24 17:23:30"), monkeyLog.getStartTime()); + // assertEquals(parseTime("2012-04-24 17:27:44"), monkeyLog.getStopTime()); + assertEquals(1, monkeyLog.getPackages().size()); + assertTrue(monkeyLog.getPackages().contains("com.google.android.youtube")); + assertEquals(1, monkeyLog.getCategories().size()); + assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER")); + assertEquals(100, monkeyLog.getThrottle()); + assertEquals(993, monkeyLog.getSeed().intValue()); + assertEquals(10000, monkeyLog.getTargetCount().intValue()); + assertTrue(monkeyLog.getIgnoreSecurityExceptions()); + assertEquals(4 * 60 * 1000 + 14 * 1000, monkeyLog.getTotalDuration().longValue()); + assertEquals(216480, monkeyLog.getStartUptimeDuration().longValue()); + assertEquals(471370, monkeyLog.getStopUptimeDuration().longValue()); + assertFalse(monkeyLog.getIsFinished()); + assertFalse(monkeyLog.getNoActivities()); + assertEquals(5300, monkeyLog.getIntermediateCount()); + assertEquals(5322, monkeyLog.getFinalCount().intValue()); + assertNotNull(monkeyLog.getCrash()); + assertTrue(monkeyLog.getCrash() instanceof AnrItem); + assertEquals("com.google.android.youtube", monkeyLog.getCrash().getApp()); + assertEquals(3301, monkeyLog.getCrash().getPid().intValue()); + assertEquals("keyDispatchingTimedOut", ((AnrItem) monkeyLog.getCrash()).getReason()); + assertEquals(ArrayUtil.join("\n", expectedStack), + ((AnrItem) monkeyLog.getCrash()).getTrace()); + } + + /** + * Test that a monkey can be parsed if there is a JavaCrash. + */ + public void testParse_java_crash() throws ParseException { + List<String> lines = Arrays.asList( + "# Tuesday, 04/24/2012 05:05:50 PM - device uptime = 232.65: Monkey command used for this test:", + "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ", + "", + ":Monkey: seed=501 count=10000", + ":AllowPackage: com.google.android.apps.maps", + ":IncludeCategory: android.intent.category.LAUNCHER", + ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity;end", + " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity } in package com.google.android.apps.maps", + "Sleeping for 100 milliseconds", + ":Sending Touch (ACTION_DOWN): 0:(332.0,70.0)", + ":Sending Touch (ACTION_UP): 0:(332.55292,76.54678)", + " //[calendar_time:2012-04-25 00:06:38.419 system_uptime:280799]", + " // Sending event #1600", + ":Sending Touch (ACTION_MOVE): 0:(1052.2666,677.64594)", + ":Sending Touch (ACTION_UP): 0:(1054.7593,687.3757)", + "Sleeping for 100 milliseconds", + "// CRASH: com.google.android.apps.maps (pid 3161)", + "// Short Msg: java.lang.Exception", + "// Long Msg: java.lang.Exception: This is the message", + "// Build Label: google/yakju/maguro:JellyBean/JRN24B/338896:userdebug/dev-keys", + "// Build Changelist: 338896", + "// Build Time: 1335309051000", + "// java.lang.Exception: This is the message", + "// \tat class.method1(Class.java:1)", + "// \tat class.method2(Class.java:2)", + "// \tat class.method3(Class.java:3)", + "// ", + "** Monkey aborted due to error.", + "Events injected: 1649", + ":Sending rotation degree=0, persist=false", + ":Dropped: keys=0 pointers=0 trackballs=0 flips=0 rotations=0", + "## Network stats: elapsed time=48897ms (0ms mobile, 48897ms wifi, 0ms not connected)", + "** System appears to have crashed at event 1649 of 10000 using seed 501", + "", + "# Tuesday, 04/24/2012 05:06:40 PM - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)", + "", + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------", + ""); + + MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines); + assertNotNull(monkeyLog); + // FIXME: Add test back once time situation has been worked out. + // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime()); + // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime()); + assertEquals(1, monkeyLog.getPackages().size()); + assertTrue(monkeyLog.getPackages().contains("com.google.android.apps.maps")); + assertEquals(1, monkeyLog.getCategories().size()); + assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER")); + assertEquals(100, monkeyLog.getThrottle()); + assertEquals(501, monkeyLog.getSeed().intValue()); + assertEquals(10000, monkeyLog.getTargetCount().intValue()); + assertTrue(monkeyLog.getIgnoreSecurityExceptions()); + assertEquals(49 * 1000, monkeyLog.getTotalDuration().longValue()); + assertEquals(232650, monkeyLog.getStartUptimeDuration().longValue()); + assertEquals(282530, monkeyLog.getStopUptimeDuration().longValue()); + assertFalse(monkeyLog.getIsFinished()); + assertFalse(monkeyLog.getNoActivities()); + assertEquals(1600, monkeyLog.getIntermediateCount()); + assertEquals(1649, monkeyLog.getFinalCount().intValue()); + assertNotNull(monkeyLog.getCrash()); + assertTrue(monkeyLog.getCrash() instanceof JavaCrashItem); + assertEquals("com.google.android.apps.maps", monkeyLog.getCrash().getApp()); + assertEquals(3161, monkeyLog.getCrash().getPid().intValue()); + assertEquals("java.lang.Exception", ((JavaCrashItem) monkeyLog.getCrash()).getException()); + } + + /** + * Test that a monkey can be parsed if there are no activities to run. + */ + public void testParse_no_activities() throws ParseException { + List<String> lines = Arrays.asList( + "# Wednesday, 04/25/2012 01:37:12 AM - device uptime = 242.13: Monkey command used for this test:", + "adb shell monkey -p com.google.android.browser -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 528 -v -v -v 10000 ", + "", + ":Monkey: seed=528 count=10000", + ":AllowPackage: com.google.android.browser", + ":IncludeCategory: android.intent.category.LAUNCHER", + "** No activities found to run, monkey aborted.", + "", + "# Wednesday, 04/25/2012 01:42:09 AM - device uptime = 539.21: Monkey command ran for: 04:57 (mm:ss)", + "", + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + + MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines); + assertNotNull(monkeyLog); + // FIXME: Add test back once time situation has been worked out. + // assertEquals(parseTime("2012-04-25 01:37:12"), monkeyLog.getStartTime()); + // assertEquals(parseTime("2012-04-25 01:42:09"), monkeyLog.getStopTime()); + assertEquals(1, monkeyLog.getPackages().size()); + assertTrue(monkeyLog.getPackages().contains("com.google.android.browser")); + assertEquals(1, monkeyLog.getCategories().size()); + assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER")); + assertEquals(100, monkeyLog.getThrottle()); + assertEquals(528, monkeyLog.getSeed().intValue()); + assertEquals(10000, monkeyLog.getTargetCount().intValue()); + assertTrue(monkeyLog.getIgnoreSecurityExceptions()); + assertEquals(4 * 60 * 1000 + 57 * 1000, monkeyLog.getTotalDuration().longValue()); + assertEquals(242130, monkeyLog.getStartUptimeDuration().longValue()); + assertEquals(539210, monkeyLog.getStopUptimeDuration().longValue()); + assertFalse(monkeyLog.getIsFinished()); + assertTrue(monkeyLog.getNoActivities()); + assertEquals(0, monkeyLog.getIntermediateCount()); + assertNull(monkeyLog.getFinalCount()); + assertNull(monkeyLog.getDroppedCount(DroppedCategory.KEYS)); + assertNull(monkeyLog.getDroppedCount(DroppedCategory.POINTERS)); + assertNull(monkeyLog.getDroppedCount(DroppedCategory.TRACKBALLS)); + assertNull(monkeyLog.getDroppedCount(DroppedCategory.FLIPS)); + assertNull(monkeyLog.getDroppedCount(DroppedCategory.ROTATIONS)); + assertNull(monkeyLog.getCrash()); + } + + /** + * Test that the other date format can be parsed. + */ + public void testAlternateDateFormat() throws ParseException { + List<String> lines = Arrays.asList( + "# Tue Apr 24 17:05:50 PST 2012 - device uptime = 232.65: Monkey command used for this test:", + "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ", + "", + "# Tue Apr 24 17:06:40 PST 2012 - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)", + "", + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------", + ""); + + MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines); + assertNotNull(monkeyLog); + // FIXME: Add test back once time situation has been worked out. + // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime()); + // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime()); + } + + @SuppressWarnings("unused") + private Date parseTime(String timeStr) throws ParseException { + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return formatter.parse(timeStr); + } +} + diff --git a/tests/src/com/android/loganalysis/parser/NativeCrashParserTest.java b/tests/src/com/android/loganalysis/parser/NativeCrashParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fa6349910fbb7ae16b3d13e6695c7bdbc5b6d35f --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/NativeCrashParserTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2012 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.loganalysis.parser; + +import com.android.loganalysis.item.NativeCrashItem; +import com.android.loganalysis.util.ArrayUtil; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link NativeCrashParser}. + */ +public class NativeCrashParserTest extends TestCase { + + /** + * Test that native crashes are parsed. + */ + public void testParseage() { + List<String> lines = Arrays.asList( + "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "Build fingerprint: 'google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys'", + "pid: 2058, tid: 2523 >>> com.google.android.browser <<<", + "signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000", + " r0 00000000 r1 007d9064 r2 007d9063 r3 00000004", + " r4 006bf518 r5 0091e3b0 r6 00000000 r7 9e3779b9", + " r8 000006c1 r9 000006c3 10 00000000 fp 67d246c1", + " ip d2363b58 sp 50ed71d8 lr 4edfc89b pc 4edfc6a0 cpsr 20000030", + " d0 00640065005f0065 d1 0072006f00740069", + " d2 00730075006e006b d3 0066006900670000", + " d4 00e6d48800e6d3b8 d5 02d517a000e6d518", + " d6 0000270f02d51860 d7 0000000002d51a80", + " d8 41d3dc5261e7893b d9 3fa999999999999a", + " d10 0000000000000000 d11 0000000000000000", + " d12 0000000000000000 d13 0000000000000000", + " d14 0000000000000000 d15 0000000000000000", + " d16 4070000000000000 d17 40c3878000000000", + " d18 412310f000000000 d19 3f91800dedacf040", + " d20 0000000000000000 d21 0000000000000000", + " d22 4010000000000000 d23 0000000000000000", + " d24 3ff0000000000000 d25 0000000000000000", + " d26 0000000000000000 d27 8000000000000000", + " d28 0000000000000000 d29 3ff0000000000000", + " d30 0000000000000000 d31 3ff0000000000000", + " scr 20000013", + "", + " #00 pc 001236a0 /system/lib/libwebcore.so", + " #01 pc 00123896 /system/lib/libwebcore.so", + " #02 pc 00123932 /system/lib/libwebcore.so", + " #03 pc 00123e3a /system/lib/libwebcore.so", + " #04 pc 00123e84 /system/lib/libwebcore.so", + " #05 pc 003db92a /system/lib/libwebcore.so", + " #06 pc 003dd01c /system/lib/libwebcore.so", + " #07 pc 002ffb92 /system/lib/libwebcore.so", + " #08 pc 0031c120 /system/lib/libwebcore.so", + " #09 pc 0031c134 /system/lib/libwebcore.so", + " #10 pc 0013fb98 /system/lib/libwebcore.so", + " #11 pc 0015b026 /system/lib/libwebcore.so", + " #12 pc 0015b164 /system/lib/libwebcore.so", + " #13 pc 0015f4cc /system/lib/libwebcore.so", + " #14 pc 00170472 /system/lib/libwebcore.so", + " #15 pc 0016ecb6 /system/lib/libwebcore.so", + " #16 pc 0027120e /system/lib/libwebcore.so", + " #17 pc 0026efec /system/lib/libwebcore.so", + " #18 pc 0026fcd8 /system/lib/libwebcore.so", + " #19 pc 00122efa /system/lib/libwebcore.so", + "", + "code around pc:", + "4edfc680 4a14b5f7 0601f001 23000849 3004f88d ...J....I..#...0", + "4edfc690 460a9200 3006f8ad e00e4603 3a019f00 ...F...0.F.....:", + "4edfc6a0 5c04f833 f83319ed 042c7c02 2cc7ea84 3..\\..3..|,....,", + "4edfc6b0 0405ea8c 24d4eb04 33049400 d1ed2a00 .......$...3.*..", + "4edfc6c0 f830b126 46681021 ff72f7ff f7ff4668 &.0.!.hF..r.hF..", + "", + "code around lr:", + "4edfc878 f9caf7ff 60209e03 9605e037 5b04f856 ...... `7...V..[", + "4edfc888 d0302d00 d13b1c6b 68a8e02d f7ff6869 .-0.k.;.-..hih..", + "4edfc898 6128fef3 b010f8d5 99022500 ea0146aa ..(a.....%...F..", + "4edfc8a8 9b01080b 0788eb03 3028f853 b9bdb90b ........S.(0....", + "4edfc8b8 3301e015 4638d005 f7ff9905 b970ff15 ...3..8F......p.", + "", + "stack:", + " 50ed7198 01d02c08 [heap]", + " 50ed719c 40045881 /system/lib/libc.so", + " 50ed71a0 400784c8", + " 50ed71a4 400784c8", + " 50ed71a8 02b40c68 [heap]", + " 50ed71ac 02b40c90 [heap]", + " 50ed71b0 50ed7290", + " 50ed71b4 006bf518 [heap]", + " 50ed71b8 00010000", + " 50ed71bc 50ed72a4", + " 50ed71c0 7da5a695", + " 50ed71c4 50ed7290", + " 50ed71c8 00000000", + " 50ed71cc 00000008", + " 50ed71d0 df0027ad", + " 50ed71d4 00000000", + "#00 50ed71d8 9e3779b9", + " 50ed71dc 00002000", + " 50ed71e0 00004000", + " 50ed71e4 006bf518 [heap]", + " 50ed71e8 0091e3b0 [heap]", + " 50ed71ec 01d72588 [heap]", + " 50ed71f0 00000000", + " 50ed71f4 4edfc89b /system/lib/libwebcore.so", + "#01 50ed71f8 01d70a78 [heap]", + " 50ed71fc 02b6afa8 [heap]", + " 50ed7200 00003fff", + " 50ed7204 01d70a78 [heap]", + " 50ed7208 00004000", + " 50ed720c 01d72584 [heap]", + " 50ed7210 00000000", + " 50ed7214 00000006", + " 50ed7218 006bf518 [heap]", + " 50ed721c 50ed72a4", + " 50ed7220 7da5a695", + " 50ed7224 50ed7290", + " 50ed7228 000016b8", + " 50ed722c 00000008", + " 50ed7230 01d70a78 [heap]", + " 50ed7234 4edfc937 /system/lib/libwebcore.so", + "debuggerd committing suicide to free the zombie!", + "debuggerd"); + + NativeCrashItem nc = new NativeCrashParser().parse(lines); + assertNotNull(nc); + assertEquals("com.google.android.browser", nc.getApp()); + assertEquals("google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys", + nc.getFingerprint()); + assertEquals(ArrayUtil.join("\n", lines), nc.getStack()); + } + + /** + * Test that both types of native crash app lines are parsed. + */ + public void testParseApp() { + List<String> lines = Arrays.asList( + "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "Build fingerprint: 'google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys'", + "pid: 2058, tid: 2523 >>> com.google.android.browser <<<", + "signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000"); + + NativeCrashItem nc = new NativeCrashParser().parse(lines); + assertNotNull(nc); + assertEquals("com.google.android.browser", nc.getApp()); + + lines = Arrays.asList( + "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***", + "Build fingerprint: 'google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys'", + "pid: 2058, tid: 2523, name: com.google.android.browser >>> com.google.android.browser <<<", + "signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000"); + + nc = new NativeCrashParser().parse(lines); + assertNotNull(nc); + assertEquals("com.google.android.browser", nc.getApp()); + } +} diff --git a/tests/src/com/android/loganalysis/parser/ProcrankParserTest.java b/tests/src/com/android/loganalysis/parser/ProcrankParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a6c66b78554466528f65f2cc3abac3b7f7f8113f --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/ProcrankParserTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.ProcrankItem; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link ProcrankParser} + */ +public class ProcrankParserTest extends TestCase { + public void testProcRankParser() { + List<String> inputBlock = Arrays.asList( + " PID Vss Rss Pss Uss cmdline", + " 178 87136K 81684K 52829K 50012K system_server", + " 1313 78128K 77996K 48603K 45812K com.google.android.apps.maps", + " 3247 61652K 61492K 33122K 30972K com.android.browser", + " 334 55740K 55572K 29629K 28360K com.android.launcher", + " 2072 51348K 51172K 24263K 22812K android.process.acore", + " 1236 51440K 51312K 22911K 20608K com.android.settings", + " 51312K 22911K 20608K invalid.format", + " ------ ------ ------", + " 203624K 163604K TOTAL", + "RAM: 731448K total, 415804K free, 9016K buffers, 108548K cached", + "[procrank: 1.6s elapsed]"); + ProcrankParser parser = new ProcrankParser(); + ProcrankItem procrank = parser.parse(inputBlock); + + // Ensures that only valid lines are parsed. Only 6 of the 11 lines under the header are + // valid. + assertEquals(6, procrank.getPids().size()); + + // Make sure all expected rows are present, and do a diagonal check of values + assertEquals((Integer) 87136, procrank.getVss(178)); + assertEquals((Integer) 77996, procrank.getRss(1313)); + assertEquals((Integer) 33122, procrank.getPss(3247)); + assertEquals((Integer) 28360, procrank.getUss(334)); + assertEquals("android.process.acore", procrank.getProcessName(2072)); + } +} + diff --git a/tests/src/com/android/loganalysis/parser/SystemPropsParserTest.java b/tests/src/com/android/loganalysis/parser/SystemPropsParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a34ee865508f99e81ee42c73bfcdffd82df92ace --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/SystemPropsParserTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.SystemPropsItem; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link SystemPropsParser} + */ +public class SystemPropsParserTest extends TestCase { + public void testSimpleParse() { + List<String> inputBlock = Arrays.asList( + "[dalvik.vm.dexopt-flags]: [m=y]", + "[dalvik.vm.heapgrowthlimit]: [48m]", + "[dalvik.vm.heapsize]: [256m]", + "[gsm.version.ril-impl]: [android moto-ril-multimode 1.0]"); + SystemPropsParser parser = new SystemPropsParser(); + SystemPropsItem map = parser.parse(inputBlock); + + assertEquals(4, map.size()); + assertEquals("m=y", map.get("dalvik.vm.dexopt-flags")); + assertEquals("48m", map.get("dalvik.vm.heapgrowthlimit")); + assertEquals("256m", map.get("dalvik.vm.heapsize")); + assertEquals("android moto-ril-multimode 1.0", map.get("gsm.version.ril-impl")); + } + + /** + * Make sure that a parse error on one line doesn't prevent the rest of the lines from being + * parsed + */ + public void testParseError() { + List<String> inputBlock = Arrays.asList( + "[dalvik.vm.dexopt-flags]: [m=y]", + "[ends with newline]: [yup", + "]", + "[dalvik.vm.heapsize]: [256m]"); + SystemPropsParser parser = new SystemPropsParser(); + SystemPropsItem map = parser.parse(inputBlock); + + assertEquals(2, map.size()); + assertEquals("m=y", map.get("dalvik.vm.dexopt-flags")); + assertEquals("256m", map.get("dalvik.vm.heapsize")); + } +} + diff --git a/tests/src/com/android/loganalysis/parser/TracesParserTest.java b/tests/src/com/android/loganalysis/parser/TracesParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b5636e040f8d5271cfed695df355818759661a84 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/TracesParserTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2011 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.loganalysis.parser; + +import com.android.loganalysis.item.TracesItem; +import com.android.loganalysis.util.ArrayUtil; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link TracesParser} + */ +public class TracesParserTest extends TestCase { + + /** + * Test that the parser parses the correct stack. + */ + public void testTracesParser() { + List<String> lines = Arrays.asList( + "", + "", + "----- pid 2887 at 2012-05-02 16:43:41 -----", + "Cmd line: com.android.package", + "", + "DALVIK THREADS:", + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)", + "", + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)", + "", + "\"Task_1\" prio=5 tid=27 WAIT", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=4789 nice=10 sched=0/0 cgrp=bg_non_interactive handle=0000000001", + " | schedstat=( 0 0 0 ) utm=0 stm=0 core=0", + " at class.method1(Class.java:1)", + " - waiting on <0x00000001> (a java.lang.Thread) held by tid=27 (Task_1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)", + "", + "\"Task_2\" prio=5 tid=26 NATIVE", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=4343 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=6 stm=3 core=0", + " #00 pc 00001234 /system/lib/lib.so (addr+8)", + " #01 pc 00001235 /system/lib/lib.so (addr+16)", + " #02 pc 00001236 /system/lib/lib.so (addr+24)", + " at class.method1(Class.java:1)", + "", + "----- end 2887 -----", + "", + "", + "----- pid 256 at 2012-05-02 16:43:41 -----", + "Cmd line: system", + "", + "DALVIK THREADS:", + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)", + "", + "\"main\" prio=5 tid=1 NATIVE", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=256 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=175 stm=41 core=0", + " #00 pc 00001234 /system/lib/lib.so (addr+8)", + " #01 pc 00001235 /system/lib/lib.so (addr+16)", + " #02 pc 00001236 /system/lib/lib.so (addr+24)", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)", + "", + "----- end 256 -----", + ""); + + List<String> expectedStack = Arrays.asList( + "\"main\" prio=5 tid=1 SUSPENDED", + " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001", + " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001", + " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0", + " at class.method1(Class.java:1)", + " at class.method2(Class.java:2)", + " at class.method2(Class.java:2)"); + + TracesItem traces = new TracesParser().parse(lines); + assertEquals(2887, traces.getPid().intValue()); + assertEquals("com.android.package", traces.getApp()); + assertEquals(ArrayUtil.join("\n", expectedStack), traces.getStack()); + } +} diff --git a/tests/src/com/android/loganalysis/util/ArrayUtilTest.java b/tests/src/com/android/loganalysis/util/ArrayUtilTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9c682935aeaa8303180737be113a63403dcfdf74 --- /dev/null +++ b/tests/src/com/android/loganalysis/util/ArrayUtilTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 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.loganalysis.util; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link ArrayUtil} + */ +public class ArrayUtilTest extends TestCase { + + /** + * Simple test for {@link ArrayUtil#buildArray(String[]...)} + */ + public void testBuildArray_arrays() { + String[] newArray = ArrayUtil.buildArray(new String[] {"1", "2"}, new String[] {"3"}, + new String[] {"4"}); + assertEquals(4, newArray.length); + for (int i = 0; i < 4; i++) { + assertEquals(Integer.toString(i+1), newArray[i]); + } + } + + /** + * Make sure that Collections aren't double-wrapped + */ + public void testJoinCollection() { + List<String> list = Arrays.asList("alpha", "beta", "gamma"); + final String expected = "alpha, beta, gamma"; + String str = ArrayUtil.join(", ", list); + assertEquals(expected, str); + } + + /** + * Make sure that Arrays aren't double-wrapped + */ + public void testJoinArray() { + String[] ary = new String[] {"alpha", "beta", "gamma"}; + final String expected = "alpha, beta, gamma"; + String str = ArrayUtil.join(", ", (Object[]) ary); + assertEquals(expected, str); + } + + /** + * Make sure that join on varargs arrays work as expected + */ + public void testJoinNormal() { + final String expected = "alpha, beta, gamma"; + String str = ArrayUtil.join(", ", "alpha", "beta", "gamma"); + assertEquals(expected, str); + } +} diff --git a/tests/src/com/android/loganalysis/util/RegexTrieTest.java b/tests/src/com/android/loganalysis/util/RegexTrieTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a124ac2d951d2a06397bf6231e74488ead167f10 --- /dev/null +++ b/tests/src/com/android/loganalysis/util/RegexTrieTest.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2010 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.loganalysis.util; + +import com.android.loganalysis.util.RegexTrie.CompPattern; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Set of unit tests to verify the behavior of the RegexTrie + */ +public class RegexTrieTest extends TestCase { + private RegexTrie<Integer> mTrie = null; + private static final Integer STORED_VAL = 42; + private static final List<String> NULL_LIST = Arrays.asList((String)null); + + @Override + public void setUp() throws Exception { + mTrie = new RegexTrie<Integer>(); + } + + private void dumpTrie(RegexTrie trie) { + System.err.format("Trie is '%s'\n", trie.toString()); + } + + public void testStringPattern() { + mTrie.put(STORED_VAL, "[p]art1", "[p]art2", "[p]art3"); + Integer retrieved = mTrie.retrieve("part1", "part2", "part3"); + assertEquals(STORED_VAL, retrieved); + } + + public void testAlternation_single() { + mTrie.put(STORED_VAL, "alpha|beta"); + Integer retrieved; + retrieved = mTrie.retrieve("alpha"); + assertEquals(STORED_VAL, retrieved); + retrieved = mTrie.retrieve("beta"); + assertEquals(STORED_VAL, retrieved); + retrieved = mTrie.retrieve("alpha|beta"); + assertNull(retrieved); + retrieved = mTrie.retrieve("gamma"); + assertNull(retrieved); + retrieved = mTrie.retrieve("alph"); + assertNull(retrieved); + } + + public void testAlternation_multiple() { + mTrie.put(STORED_VAL, "a|alpha", "b|beta"); + Integer retrieved; + retrieved = mTrie.retrieve("a", "b"); + assertEquals(STORED_VAL, retrieved); + retrieved = mTrie.retrieve("a", "beta"); + assertEquals(STORED_VAL, retrieved); + retrieved = mTrie.retrieve("alpha", "b"); + assertEquals(STORED_VAL, retrieved); + retrieved = mTrie.retrieve("alpha", "beta"); + assertEquals(STORED_VAL, retrieved); + + retrieved = mTrie.retrieve("alpha"); + assertNull(retrieved); + retrieved = mTrie.retrieve("beta"); + assertNull(retrieved); + retrieved = mTrie.retrieve("alpha", "bet"); + assertNull(retrieved); + } + + public void testGroups_fullMatch() { + mTrie.put(STORED_VAL, "a|(alpha)", "b|(beta)"); + Integer retrieved; + List<List<String>> groups = new ArrayList<List<String>>(); + + retrieved = mTrie.retrieve(groups, "a", "b"); + assertEquals(STORED_VAL, retrieved); + assertEquals(2, groups.size()); + assertEquals(NULL_LIST, groups.get(0)); + assertEquals(NULL_LIST, groups.get(1)); + + retrieved = mTrie.retrieve(groups, "a", "beta"); + assertEquals(STORED_VAL, retrieved); + assertEquals(2, groups.size()); + assertEquals(NULL_LIST, groups.get(0)); + assertEquals(Arrays.asList("beta"), groups.get(1)); + + retrieved = mTrie.retrieve(groups, "alpha", "b"); + assertEquals(STORED_VAL, retrieved); + assertEquals(2, groups.size()); + assertEquals(Arrays.asList("alpha"), groups.get(0)); + assertEquals(NULL_LIST, groups.get(1)); + + retrieved = mTrie.retrieve(groups, "alpha", "beta"); + assertEquals(STORED_VAL, retrieved); + assertEquals(2, groups.size()); + assertEquals(Arrays.asList("alpha"), groups.get(0)); + assertEquals(Arrays.asList("beta"), groups.get(1)); + } + + public void testGroups_partialMatch() { + mTrie.put(STORED_VAL, "a|(alpha)", "b|(beta)"); + Integer retrieved; + List<List<String>> groups = new ArrayList<List<String>>(); + + retrieved = mTrie.retrieve(groups, "alpha"); + assertNull(retrieved); + assertEquals(1, groups.size()); + assertEquals(Arrays.asList("alpha"), groups.get(0)); + + retrieved = mTrie.retrieve(groups, "beta"); + assertNull(retrieved); + assertEquals(0, groups.size()); + + retrieved = mTrie.retrieve(groups, "alpha", "bet"); + assertNull(retrieved); + assertEquals(1, groups.size()); + assertEquals(Arrays.asList("alpha"), groups.get(0)); + + retrieved = mTrie.retrieve(groups, "alpha", "betar"); + assertNull(retrieved); + assertEquals(1, groups.size()); + assertEquals(Arrays.asList("alpha"), groups.get(0)); + + retrieved = mTrie.retrieve(groups, "alpha", "beta", "gamma"); + assertNull(retrieved); + assertEquals(2, groups.size()); + assertEquals(Arrays.asList("alpha"), groups.get(0)); + assertEquals(Arrays.asList("beta"), groups.get(1)); + } + + /** + * Make sure that the wildcard functionality works + */ + public void testWildcard() { + mTrie.put(STORED_VAL, "a", null); + Integer retrieved; + List<List<String>> groups = new ArrayList<List<String>>(); + + retrieved = mTrie.retrieve(groups, "a", "b", "c"); + assertEquals(STORED_VAL, retrieved); + assertEquals(3, groups.size()); + assertTrue(groups.get(0).isEmpty()); + assertEquals(Arrays.asList("b"), groups.get(1)); + assertEquals(Arrays.asList("c"), groups.get(2)); + + retrieved = mTrie.retrieve(groups, "a"); + assertNull(retrieved); + assertEquals(1, groups.size()); + assertTrue(groups.get(0).isEmpty()); + } + + /** + * Make sure that if a wildcard and a more specific match could both match, that the more + * specific match takes precedence + */ + public void testWildcard_precedence() { + // Do one before and one after the wildcard to check for ordering effects + mTrie.put(STORED_VAL + 1, "a", "(b)"); + mTrie.put(STORED_VAL, "a", null); + mTrie.put(STORED_VAL + 2, "a", "(c)"); + Integer retrieved; + List<List<String>> groups = new ArrayList<List<String>>(); + + retrieved = mTrie.retrieve(groups, "a", "d"); + assertEquals(STORED_VAL, retrieved); + assertEquals(2, groups.size()); + assertTrue(groups.get(0).isEmpty()); + assertEquals(Arrays.asList("d"), groups.get(1)); + + retrieved = mTrie.retrieve(groups, "a", "b"); + assertEquals((Integer)(STORED_VAL + 1), retrieved); + assertEquals(2, groups.size()); + assertTrue(groups.get(0).isEmpty()); + assertEquals(Arrays.asList("b"), groups.get(1)); + + retrieved = mTrie.retrieve(groups, "a", "c"); + assertEquals((Integer)(STORED_VAL + 2), retrieved); + assertEquals(2, groups.size()); + assertTrue(groups.get(0).isEmpty()); + assertEquals(Arrays.asList("c"), groups.get(1)); + } + + /** + * Verify a bugfix: make sure that no NPE results from calling #retrieve with a wildcard but + * without a place to retrieve captures. + */ + public void testWildcard_noCapture() throws NullPointerException { + mTrie.put(STORED_VAL, "a", null); + String[] key = new String[] {"a", "b", "c"}; + + mTrie.retrieve(key); + mTrie.retrieve(null, key); + // test passes if no exceptions were thrown + } + + public void testMultiChild() { + mTrie.put(STORED_VAL + 1, "a", "b"); + mTrie.put(STORED_VAL + 2, "a", "c"); + dumpTrie(mTrie); + + Object retrieved; + retrieved = mTrie.retrieve("a", "b"); + assertEquals(STORED_VAL + 1, retrieved); + retrieved = mTrie.retrieve("a", "c"); + assertEquals(STORED_VAL + 2, retrieved); + } + + /** + * Make sure that {@link CompPattern#equals} works as expected. Shake a proverbial fist at Java + */ + public void testCompPattern_equality() { + String regex = "regex"; + Pattern p1 = Pattern.compile(regex); + Pattern p2 = Pattern.compile(regex); + Pattern pOther = Pattern.compile("other"); + CompPattern cp1 = new CompPattern(p1); + CompPattern cp2 = new CompPattern(p2); + CompPattern cpOther = new CompPattern(pOther); + + // This is the problem with Pattern as implemented + assertFalse(p1.equals(p2)); + assertFalse(p2.equals(p1)); + + // Make sure that wrapped patterns with the same regex are considered equivalent + assertTrue(cp2.equals(p1)); + assertTrue(cp2.equals(p2)); + assertTrue(cp2.equals(cp1)); + + // And make sure that wrapped patterns with different regexen are still considered different + assertFalse(cp2.equals(pOther)); + assertFalse(cp2.equals(cpOther)); + } + + public void testCompPattern_hashmap() { + HashMap<CompPattern, Integer> map = new HashMap<CompPattern, Integer>(); + String regex = "regex"; + Pattern p1 = Pattern.compile(regex); + Pattern p2 = Pattern.compile(regex); + Pattern pOther = Pattern.compile("other"); + CompPattern cp1 = new CompPattern(p1); + CompPattern cp2 = new CompPattern(p2); + CompPattern cpOther = new CompPattern(pOther); + + map.put(cp1, STORED_VAL); + assertTrue(map.containsKey(cp1)); + assertTrue(map.containsKey(cp2)); + assertFalse(map.containsKey(cpOther)); + + map.put(cpOther, STORED_VAL); + assertEquals(map.size(), 2); + assertTrue(map.containsKey(cp1)); + assertTrue(map.containsKey(cp2)); + assertTrue(map.containsKey(cpOther)); + } +} +