def makeUnit(expname, exptype, directory, printSubsequence, detail, allcrash):
    apelog_fname = os.path.join(directory, 'ape_stdout_stderr.txt')
    logcat_fname = os.path.join(directory, 'logcat.txt')
    mtdata_directories = glob.glob(os.path.join(directory, 'mt_data', '*'))

    assert os.path.isfile(apelog_fname), apelog_fname
    assert os.path.isfile(logcat_fname), logcat_fname

    modelobjects = glob.glob(os.path.join(directory, 'ape', '*', 'sataModel.obj'))
    if len(modelobjects) < 1:
        print("There is no model object in {}".format(directory))
        with open(apelog_fname, 'rt') as f:
            num_lines = int(run_cmd('wc -l {}'.format(apelog_fname)).split()[0])

            for i, line in enumerate(f):
                if i >= num_lines - 10:
                    print(' {}: {}'.format(i, line.rstrip()))

        return None

    assert len(modelobjects) == 1, modelobjects
    modelobject_fname = modelobjects[0]

    warningCounter = Counter()
    waitCounter = Counter()
    crashLongMessagesCounter = Counter()
    time_elapsed = -1
    strategy_cnt = {}
    first_timestamp = -1
    strategy_changed_timestamp = -1
    first_met_timestamp = -1
    timestamp = -1
    with open(apelog_fname, 'rt') as f:
        it = iter(f)
        for line in it:
            line = line.rstrip()
            if line.startswith('[APE_MT_WARNING]'):
                warningCounter.append(line)

            gp = re.match(r'\[APE\] ([a-zA-Z]+) Strategy: buffer size \(([0-9]+)\)', line)
            if gp:
                nums = [re.match(r'\[APE\] *([0-9]+)  (.*)', next(it).rstrip()) for strategy in strategies[gp.group(1)]]
                strategy_cnt = dict(map(lambda gp:(gp.group(2), int(gp.group(1))), nums))
                continue

            elif line.startswith('## Network stats: elapsed time='):
                gp = re.match(r'## Network stats: elapsed time=([0-9]+)ms \(([0-9]+)ms mobile, ([0-9]+)ms wifi, ([0-9]+)ms not connected\)', line)
                assert gp, line
                total, mob, wifi, total2 = gp.groups()
                assert total == total2 and mob == wifi, line
                time_elapsed = int(total)

            elif line == "[APE] *** INFO *** We are still waiting for activity loading. Let's wait for another 100ms...":
                waitCounter.append(line)

            elif line.startswith("[APE] // Long Msg: "):
                crashLongMessagesCounter.append(line[len("[APE] // Long Msg: "):])

                if allcrash:
                    print(line)
                    while line.startswith("[APE] //"):
                        line = next(it).rstrip()
                        print(line)
                    assert line.startswith('[APE] *** INFO *** Appending crash [')
                    current_timestamp = int(line.split('@')[1])
                    print('*** Time elapsed {}'.format(current_timestamp - first_timestamp))
                    print()

            elif line == "[APE] *** INFO *** Half time/counter consumed":
                strategy_changed_timestamp = timestamp

            elif line.startswith("[MonkeyServer] idle fetch"):
                gp = re.match(r'\[MonkeyServer\] idle fetch ([-]{0,1}[0-9]+)', line)
                assert gp, line
                timestamp = int(gp.group(1))
                if first_timestamp == -1 and timestamp not in [0, -1]:
                    first_timestamp = timestamp
            elif line == '[APE_MT] Lastlast transition met target':
                if first_met_timestamp == -1:
                    first_met_timestamp = timestamp
            elif 'MET TARGET' in line: # for old version
                if first_met_timestamp == -1:
                    first_met_timestamp = timestamp


    if time_elapsed == -1:
        print("Time elapsed not found")
        with open(apelog_fname, 'rt') as f:
            num_lines = int(run_cmd('wc -l {}'.format(apelog_fname)).split()[0])

            for i, line in enumerate(f):
                if i >= num_lines - 10:
                    print(' {}: {}'.format(i, line.rstrip()))
        return None
    targetMethods = []
    lazy_counter = Counter()
    registered_counter = Counter()
    registered_lazily_counter = Counter()
    with open(logcat_fname, 'rt') as f:
        for line in f:
            if 'MiniTrace' not in line:
                continue

            line = line.rstrip()
            line = line[line.index('MiniTrace'):]

            gp = re.match(r'MiniTrace: TargetMethod (.*):(.*)\[(.*)\] ([a-z ]+)', line)
            if gp:
                clsname, mtdname, signature, status = gp.groups()
                if status == 'lazy':
                    clsname = 'L{};'.format(clsname)
                    if (clsname, mtdname, signature) not in targetMethods:
                        targetMethods.append((clsname, mtdname, signature))
                    lazy_counter.append((clsname, mtdname, signature))
                elif status == 'registered':
                    clsname = 'L{};'.format(clsname)
                    if (clsname, mtdname, signature) not in targetMethods:
                        targetMethods.append((clsname, mtdname, signature))
                    lazy_counter.append((clsname, mtdname, signature))
                else:
                    assert status == 'registered lazily', status
                    assert clsname[0] == 'L' and clsname[-1] == ';', clsname
                    registered_lazily_counter.append((clsname, mtdname, signature))
    method_register_status = {}
    for method in targetMethods:
        if registered_counter[method] == 0:
            data =  '{}/{}'.format(registered_lazily_counter[method], lazy_counter[method])
        else:
            assert registered_lazily_counter[method] == 0, registered_lazily_counter[method]
            assert lazy_counter[method] == 0, lazy_counter[method]
            data = '{}/{}'.format(registered_counter[method], registered_counter[method])
        method_register_status[method] = data

    # self.exptype = exptype
    # self.expname = expname
    # self.directory = directory

    # marked call
    class MtdCounter(object):
        def __init__(self):
            self.cnt = 0
            self.main_tid = -1
            self.main_cnt = 0
        def setTid(self, main_tid):
            self.main_tid = main_tid
        def inc(self, value):
            self.main_cnt += 1
        def tidInc(self, tid, value):
            assert self.main_tid != -1
            if tid == self.main_tid:
                self.main_cnt += 1
            self.cnt += 1

    class ExecutionData:
        def __init__(self, string):
            assert all(c in ['0', '1'] for c in string), string
            self.string = string

        def union(self, new_string):
            assert len(self.string) == len(new_string)
            new_string = ''.join(['1' if a == '1' or b == '1' else '0' \
                for a, b in zip(self.string, new_string)])
            self.string = new_string

        def __or__(self, other):
            assert len(self.string) == len(other.string)
            new_string = ''.join(['1' if a == '1' or b == '1' else '0' \
                for a, b in zip(self.string, other.string)])
            return ExecutionData(new_string)

        def __repr__(self):
            return '<ExecutionData string={}>'.format(self.string)

        def ratio(self):
            return self.string.count('1') / len(self.string)

    sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../crashgen'))
    from consumer import parse_data, Threads, Methods

    execution_data = {}
    mtdCounter = MtdCounter()
    for mtdata_directory in mtdata_directories:
        binary_fname = os.path.join(mtdata_directory, 'data_0.bin')
        thread_fname = os.path.join(mtdata_directory, 'info_t.log')
        method_fname = os.path.join(mtdata_directory, 'info_m.log')

        if any(not os.path.isfile(fname) for fname in [binary_fname, thread_fname, method_fname]):
            continue
        mtdCounter.setTid(Threads(thread_fname).get_main_tid())
        parse_data(binary_fname, {10: mtdCounter.inc, 11: mtdCounter.inc, 12: mtdCounter.inc,
            13: mtdCounter.tidInc, 14: mtdCounter.tidInc, 15: mtdCounter.tidInc}, verbose=False)

        methods = Methods(method_fname)
        execf = os.path.join(mtdata_directory, 'exec.txt')
        with open(execf, 'rt') as f:
            for line in f:
                line = line.rstrip()
                if line.startswith('\0'):
                    line = line[1:]
                if line == '':
                    break
                if not line.startswith('Timestamp'):
                    mtdptr, execdata = line.split()
                    mtdptr = int(mtdptr, 16)
                    clsname, mtdname, signature, defclass = methods[mtdptr]
                    try:
                        execution_data[(clsname, mtdname, signature)].union(execdata)
                    except KeyError:
                        execution_data[(clsname, mtdname, signature)] = ExecutionData(execdata)


    string = '{},{}'.format(expname, exptype)
    string += ',{},{},{},'.format(time_elapsed, warningCounter.total(), waitCounter.total())

    assert all(method in targetMethods for method in execution_data)
    method_data = []
    for method in targetMethods:
        if method not in execution_data:
            data = 0.0
        else:
            data = execution_data[method].ratio()
        method_data.append('%s:%.3f' % (method_register_status[method], data))
    string += '/'.join(method_data)
    string += ',{},{}'.format(strategy_cnt.get('TARGET', 0) + strategy_cnt.get('MCMC', 0), sum(strategy_cnt.values()))
    string += ',{},{}'.format(mtdCounter.main_cnt, mtdCounter.cnt)

    # strategy changed timestamp
    if strategy_changed_timestamp == -1:
        string += ',NaN'
    else:
        string += ',{}'.format(strategy_changed_timestamp - first_timestamp)
    # first met transition timestmap
    if first_met_timestamp == -1:
        string += ',NaN'
    else:
        string += ',{}'.format(first_met_timestamp - first_timestamp)

    warningCounter.pretty()
    if not detail:
        string += ',{}'.format(crashLongMessagesCounter.total())
        string += ',{}'.format('/'.join(map(lambda tup:'{} {} [{}]'.format(*tup), targetMethods)))
        return string

    # analysis for sataModel.obj
    # crashes with targets
    # GUITreeTransition marked/total
    # State marked/total
    # StateTransition marked/total
    # unique subsequences (>=3 times / at least once)
    # @TODO State score
    # @TODO StateTransition score
    data = []
    import javaobj
    from common import classReadJavaList, readJavaList
    from tree import GUITree
    from model import Model, Graph, StateTransition
    try:
        with open(modelobject_fname, 'rb') as f:
            model = Model(javaobj.loads(f.read()))
    except Exception:
        return string + ',javaobjError'

    graph = Graph.init(model.graph)

    # crashes
    crashWithTargetMethodsCounter = Counter("crashes with target")
    crashWithoutTargetMethodsCounter = Counter("crashes without target")
    firstMoment = -1
    firstMomentTargetMethods = -1
    records = model.actionHistory
    for record in readJavaList(records):
        if firstMoment == -1:
            firstMoment = record.clockTimestamp
        if not record.guiAction:
            action = record.modelAction
            constant = action.type.constant
            if constant == 'PHANTOM_CRASH':
                # check stackTrace
                stackTrace = action.crash.stackTrace
                metTarget = False
                for line in stackTrace.split('\n'):
                    if metTarget:
                        break
                    for (clsname, mtdname, signature) in targetMethods:
                        if mtdname in line and clsname[1:-1].split('/')[-1] in line:
                            metTarget = True
                            break
                if metTarget:
                    crashWithTargetMethodsCounter.append(action.crash.stackTrace)
                    if firstMomentTargetMethods == -1:
                        firstMomentTargetMethods = record.clockTimestamp
                else:
                    crashWithoutTargetMethodsCounter.append(action.crash.longMsg)

    crashWithTargetMethodsCounter.pretty()
    crashWithoutTargetMethodsCounter.pretty()
    if firstMomentTargetMethods == -1:
        data.append('NaN')
    else:
        data.append(firstMomentTargetMethods - firstMoment)
    data.append(crashWithTargetMethodsCounter.total())
    data.append(crashLongMessagesCounter.total())

    treeHistory = graph.treeTransitionHistory
    marked_gtransitions = []
    marked_transitions = set()
    for gtransition in treeHistory:
        if gtransition.hasMetTargetMethod:
            marked_gtransitions.append(gtransition)
            marked_transitions.add(gtransition.stateTransition)

    data.append(len(marked_gtransitions))
    data.append(len(treeHistory))

    targetGUITrees = classReadJavaList(graph.metTargetMethodGUITrees, GUITree)
    targetStates = set(map(lambda t:t.getCurrentState(), targetGUITrees))
    targetStateIds = set(map(lambda t:id(t.currentState), targetGUITrees))
    assert len(targetStates) == len(targetStateIds), (len(targetStates), len(targetStateIds))

    data.append(len(targetStates))
    data.append(graph.size())

    # Split with marked State (old) -> Split with marked StateTransition
    from parseobj import getSubsequences, TargetSubsequence
    subsequences = getSubsequences(model, graph)
    subseqCounter = Counter()
    targetSubsequences = []
    for seq in subsequences:
        targetSubsequence = TargetSubsequence(seq[0], seq.getTimestampDelta(0))
        for i in range(1, len(seq)):
            tr = seq[i]
            timestamp = seq.getTimestampDelta(i)
            if tr.hasMetTargetMethod == True:
            # if id(tr.source.currentState) in targetStateIds:
                subseqCounter.append(targetSubsequence)
                targetSubsequences.append(targetSubsequence)
                targetSubsequence = TargetSubsequence(tr, timestamp)
            else:
                targetSubsequence.append(tr, timestamp)
        # subseqCounter.append(targetSubsequence)
        targetSubsequence.setActionRecords(seq.actionRecordsAtEnd)
        targetSubsequences.append(targetSubsequence)

    data.append(len([s for s in subseqCounter.values() if s >= 3]))
    data.append(len(subseqCounter))
    if printSubsequence:
        currentRunningSubsequences = []
        for subsequence in targetSubsequences:
            currentRunningSubsequences.append(subsequence)
            # if subsequence.containsTarget():
            crash = subsequence.getCrash()
            if crash:
                print('Subsequence newly emrged# {}'.format(subsequence.timestamps[0]))
                print(crash['stackTrace'])
                for subsequence in currentRunningSubsequences:
                    print(subsequence.seqdetail())
                print()
                currentRunningSubsequences = []
            # print('{}: {}'.format(subsequence.timestamps[0], subsequence.seqdetail()))

    string += ',' + ','.join(map(str, data))
    if len(subseqCounter) == 0:
        string += ',NaN,NaN,NaN,NaN,NaN,NaN'
    else:
        # #subseq with len <=1, <=2, <=3, <=4, <=5
        keys = subseqCounter.keys()
        cnts = []
        for sz in [1, 2, 3, 4, 5]:
            cnts.append(len([s for s in keys if len(s) <= sz]))
        cnts = tuple(cnts)
        string += ',%.2f,%d,%d,%d,%d,%d' % ((subseqCounter.total() / len(subseqCounter),) + cnts)

    # statistics for state / transition
    state_scores = []
    for state in targetStates:
        state_scores.append(graph.metTargetScore(state))
    if state_scores != []:
        state_scores = np.array(state_scores)
        string += ',%d,%.3f,%.3f,%.3f,%.3f' % (
            len(state_scores),
            np.min(state_scores),
            np.max(state_scores),
            np.average(state_scores),
            np.std(state_scores))
    else:
        string += ',0,NaN,NaN,NaN,NaN'

    transition_scores = []
    for transition in marked_transitions:
        transition_scores.append(StateTransition.init(transition).metTargetRatio())
    if transition_scores != []:
        transition_scores = np.array(transition_scores)
        string += ',%d,%.3f,%.3f,%.3f,%.3f' % (
            len(transition_scores),
            np.min(transition_scores),
            np.max(transition_scores),
            np.average(transition_scores),
            np.std(transition_scores))
    else:
        string += ',0,NaN,NaN,NaN,NaN'
    string += ',{}'.format('/'.join(map(lambda tup:'{} {} [{}]'.format(*tup), targetMethods)))

    return string
    if args.st:
        # print state / statetransitions with met target
        for directory in args.directories:
            print('Experiment {}'.format(directory))
            import javaobj
            from common import classReadJavaList, readJavaList
            from tree import GUITree
            from model import Model, Graph, StateTransition
            with open(glob.glob(os.path.join(directory, "ape", "*", "sataModel.obj"))[0], 'rb') as f:
                try:
                    model = Model(javaobj.loads(f.read()))
                except Exception as e:
                    print(e)
                    continue

            graph = Graph.init(model.graph)
            targetTransitions = set()
            for gt in graph.treeTransitionHistory:
                if gt.hasMetTargetMethod:
                    st = StateTransition.init(gt.stateTransition)
                    targetTransitions.add(st)

            for st in sorted(list(targetTransitions), key=lambda t:repr(t)):
                print(st)


    elif args.detail:
        results = []
        for exp in args.directories:
            while exp.endswith('/'):
                exp = exp[:-1]