#!/usr/bin/env python3
# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# 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.
#
#
# { David Reyna, david.reyna@windriver.com, },
#
#
#
# Instructions to enable Toaster on your host can be found here:
# https://wiki.yoctoproject.org/wiki/Setting_up_a_production_instance_of_Toaster
# Instructions to create a build with events database attached
# $ . oe-init-build-env
# $ source toaster start webport=127.0.0.1:8800
# $ bitbake core-image-minimal
#
import sys
import sqlite3
import re
from operator import itemgetter, attrgetter, methodcaller
# Note: you can find these tables and their members directly
# from the Toaster database, for example:
# $ sqlite3 toaster.sqlite
# sqlite> .tables
# sqlite> .schema orm_build
# Index into orm_build table
BUILD_ORM_ID=0
BUILD_ORM_MACHINE=1
BUILD_ORM_STARTED_ON=4
BUILD_ORM_COMPLETED_ON=5
BUILD_ORM_OUTCOME=6
BUILD_ORM_PROJECT_ID=9
# Index into orm_target table
TARGET_ORM_TARGET=1
TARGET_ORM_TASK=2
TARGET_ORM_BUILD_ID=6
# Index into orm_project table
PROJECT_ORM_NAME=1
# Index into orm_task table
TASK_ORM_NAME=6
TASK_ORM_START=22
TASK_ORM_STOP=21
TASK_ORM_BUILD_ID=15
TASK_ORM_RECIPE_ID=16
# Index into orm_recipe table
RECIPE_ORM_NAME=2
# Index into taskList
TASK_RECIPE=0
TASK_NAME=1
TASK_START=2
TASK_STOP=3
TASK_OVERCOUNT=4
TASK_OVERLIST=5
# Index into recipeList
RECIPE_NAME=0
RECIPE_START=1
RECIPE_STOP=2
RECIPE_OVERCOUNT=3
RECIPE_OVERLIST=4
# Index into taskTimeList,recipeTimeList
START=1
STOP=2
TIME_STATE=0
TIME_OVERCOUNT=1
TIME_TIME=2
TIME_RECIPE=3
TIME_TASK=4
# The database objects
database_file = None
conn = None
build_cursor = None
project_cursor = None
target_cursor = None
# The data structures
build=None
taskList = []
recipeList = []
taskTimeList = []
recipeTimeList = []
build_data={}
# Statistics for displaying columns
recipe_length_max = 0
task_length_max = 0
task_execute_max = 0
recipe_execute_max = 0
# debug support
RECORD_MAX=None # None for all, else max number of records to read
#################################
# show help
#
def show_help():
print('=== event_overlap.py ===')
print('Commands:')
print(' ? : show help')
print(' b,build [build_id] : show or select builds')
print(' d,data : show histogram data')
print(' t,task [task] : show task database')
print(' r,recipe [recipe] : show recipes database')
print(' e,events [task] : show task time events')
print(' E,Events [recipe] : show recipe time events')
print(' o,overlap [task|0|n] : show task|zero|n_max execution overlaps')
print(' O,Overlap [recipe|0|n] : show recipe|zero|n_max execution overlaps')
print(' g,graph [task] [> file] : graph task execution overlap')
print(' G,Graph [recipe] [> file] : graph recipe execution overlap')
print(' h,html [task] [> file] : HTML graph task execution overlap [to file]')
print(' H,Html [recipe] [> file] : HTML graph recipe execution overlap [to file]')
print(' q,quit : quit')
print('')
print("Examples: ")
print(" * Recipe/task filters accept wild cards, like 'native-*, '*-lib*'")
print(" * Recipe/task filters get an automatic wild card at the end")
print(" * Task names are in the form 'recipe:task', so 'acl*patch' ")
print(" will specifically match the 'acl*:do_patch' task")
print(" * Use 'o 2' for the tasks in the two highest overlap count sets")
print(" * Use 'O 0' for the recipes with zero overlaps")
print(" * Use 'd' to see the distribution of parallel and overlap execution")
print('')
#################################
# set up task/recipe filter
# auto-append wildcard
# convert '*' to regex
# return compiled regex filter
def prepare_filter(filter_string):
# auto wildcard at end
if (0 == len(filter_string)) or ('*' != filter_string[-1]):
filter_string = filter_string + '*'
filter_string = filter_string.replace('*','.*')
return re.compile(filter_string)
#################################
# print to STDOUT or file
output_file=''
output_fd=None
def output_file_action(action,file):
global output_file,output_fd
if '' != file:
if "open" == action:
output_file=file
try:
output_fd=open(output_file, 'w')
except:
print("\nERROR: Could not open file '%s'\n" % output_file)
return False
if "close" == action:
output_fd.close()
print("\nDone: file '%s' created" % output_file)
output_fd=None
else:
output_file=''
output_fd=None
return True
def event_print(line,end=' '):
if '' == output_file:
if '' == end:
print(line,end='')
else:
print(line)
else:
if '' == end:
print(line,end='',file=output_fd)
else:
print(line,file=output_fd)
#################################
# connect to database
#
def connect_database(filename):
global database_file,conn
global build_cursor,project_cursor,target_cursor
database_file = filename
conn = sqlite3.connect(database_file)
if None == conn:
print("ERROR: %s is not an sqlite database" % database_file)
sys.exit(1)
build_cursor = conn.cursor()
project_cursor = conn.cursor()
target_cursor = conn.cursor()
#################################
# show build list from database
#
def build_outcome(x):
return {
'0': 'SUCCEEDED',
'1': 'FAILED',
'2': 'IN_PROGRESS',
'3': 'CANCELLED',
}[x]
def fetch_build_metadata(build):
global build_data
build_data={}
# read this build's meta information
build_data['id']=build[BUILD_ORM_ID]
build_data['machine']=build[BUILD_ORM_MACHINE]
build_data['started_on']=build[BUILD_ORM_STARTED_ON]
build_data['completed_on']=build[BUILD_ORM_COMPLETED_ON]
build_data['outcome']=build[BUILD_ORM_OUTCOME]
project_id=build[BUILD_ORM_PROJECT_ID]
# look up this build's project name
project_cursor.execute("SELECT * FROM orm_project where id = '%s'" % project_id)
project = project_cursor.fetchone()
build_data['project']=project[PROJECT_ORM_NAME]
# look up this build's target information
target_cursor.execute("SELECT * FROM orm_target where build_id = '%s'" % build[BUILD_ORM_ID])
target = target_cursor.fetchone()
build_data['target']=target[TARGET_ORM_TARGET]
build_data['task']=target[TARGET_ORM_TASK]
def show_builds():
print("List of available builds:")
build_cursor.execute("SELECT * FROM orm_build")
build = build_cursor.fetchone()
while build != None:
fetch_build_metadata(build)
print(" BuildId=%s) CompletedOn=%s, Outcome=%s, Project=%s, Target=%s, Task=%s" %
(build_data['id'],build_data['completed_on'],build_outcome(str(build_data['outcome'])),
build_data['project'],build_data['target'],build_data['task']) )
build = build_cursor.fetchone()
return ""
#################################
# Fetch build data from database
#
def fetch_build_data(build_id):
global build
global taskList, recipeList, taskTimeList, recipeTimeList
global recipe_length_max, task_length_max, task_execute_max, recipe_execute_max
taskList = []
recipeList = []
taskTimeList = []
recipeTimeList = []
recipe_length_max = 0
task_length_max = 0
max_records = RECORD_MAX
print("Fetching build #%d" % build_id)
build_cursor.execute("SELECT * FROM orm_build where id = '%s'" % build_id)
build=build_cursor.fetchone()
fetch_build_metadata(build)
c = conn.cursor()
d = conn.cursor()
# Fetch the build's tasks from the database
c.execute("SELECT * FROM orm_task where build_id = '%s'" % build_id)
task = c.fetchone()
if None == task:
build=None
print("ERROR: No build or tasks found for this build id!")
return False
while None != task:
# fetch the task's parent recipe name
d.execute("SELECT * FROM orm_recipe where id = '%s'" % task[TASK_ORM_RECIPE_ID])
recipe = d.fetchone()
# get maximum string lengths
if recipe_length_max < len(recipe[RECIPE_ORM_NAME]):
recipe_length_max = len(recipe[RECIPE_ORM_NAME])
if task_length_max < len(task[TASK_ORM_NAME]):
task_length_max = len(task[TASK_ORM_NAME])
# Fix time data for cached builds (time == None)
task_start = task[TASK_ORM_START];
if task_start == None:
task_start = 'None'
task_stop = task[TASK_ORM_STOP];
if task_stop == None:
task_stop = 'None'
# Add the taskList entry
taskList.append( [recipe[RECIPE_ORM_NAME],task[TASK_ORM_NAME],task_start, task_stop, 0, [] ])
# Append the Task time start and stop entires
taskTimeList.append( [START,0, task_start,recipe[RECIPE_ORM_NAME],task[TASK_ORM_NAME]] )
taskTimeList.append( [STOP ,0, task_stop ,recipe[RECIPE_ORM_NAME],task[TASK_ORM_NAME]] )
# Set the recipe time span
for r in recipeList:
# set the recipe's stop time from its last task
if recipe[RECIPE_ORM_NAME] == r[RECIPE_NAME]:
if r[RECIPE_STOP] < task_stop:
r[RECIPE_STOP] = task_stop
break
else:
# first task for this recipe, set the recipe start time
recipeList.append( [recipe[RECIPE_ORM_NAME], task_start, task_stop, 0, [] ])
# Loop
if None == max_records:
task = c.fetchone()
elif max_records > 0:
max_records -= 1
task = c.fetchone()
else:
task = None
# Compute the overlapping tasks
for t in taskList:
for tt in taskList:
if t != tt:
if (t[TASK_START] < tt[TASK_STOP]) and (t[TASK_STOP] > tt[TASK_START]):
t[TASK_OVERLIST].append(tt[TASK_RECIPE]+':'+tt[TASK_NAME])
t[TASK_OVERCOUNT] += 1
# Compute the overlapping recipes (over the span of the recipe's tasks)
for r in recipeList:
# append the Recipe time start and stop entires
recipeTimeList.append( [START,0,r[RECIPE_START],r[RECIPE_NAME],''] )
recipeTimeList.append( [STOP ,0,r[RECIPE_STOP] ,r[RECIPE_NAME],''] )
for rr in recipeList:
if r != rr:
if (r[RECIPE_START] < rr[RECIPE_STOP]) and (r[RECIPE_STOP] > rr[RECIPE_START]):
r[RECIPE_OVERLIST].append(rr[RECIPE_NAME])
r[RECIPE_OVERCOUNT] += 1
# sort the lists
taskList.sort(key=lambda item: item[TASK_START])
taskList.sort(key=lambda item: item[TASK_RECIPE])
recipeList.sort(key=lambda item: item[RECIPE_NAME])
taskTimeList.sort(key=lambda item: item[TIME_TIME])
recipeTimeList.sort(key=lambda item: item[TIME_TIME])
# count the task's max thread parallelism
count=0
task_execute_max=0
for t in taskTimeList:
if START == t[TIME_STATE]:
count += 1;
if task_execute_max < count:
task_execute_max = count
else:
count -= 1;
t[TIME_OVERCOUNT] = count;
# count the recipe's max thread parallelism
count=0
recipe_execute_max=0
for t in recipeTimeList:
if START == t[TIME_STATE]:
count += 1;
if recipe_execute_max < count:
recipe_execute_max = count
else:
count -= 1;
t[TIME_OVERCOUNT] = count;
print("Build: CompletedOn=%s, Outcome=%s, Project='%s'" %
(build_data['completed_on'],build_outcome(str(build_data['outcome'])),build_data['project']) )
print(" Target='%s', Task='%s', Machine='%s'" %
(build_data['target'],build_data['task'],build_data['machine']) )
print('Success: build #%d, Task Count=%d, Recipe Count=%d' % (build_id, len(taskList),len(recipeList)) )
return True
#####################################
# compute and display histogram data
def compute_histogram (list, key, isFilter, description, is_html=False):
HIST_MAX=1000
hist=[]
hist_top=0
for i in range(HIST_MAX):
hist.append(0)
for t in list:
if not isFilter or (START == t[TIME_STATE]):
hist[t[key]] += 1;
if t[key] > hist_top:
hist_top = t[key]
if not is_html:
print("Histogram:"+description)
print(" ", end='')
for i in range(0,10):
print(" {:3}".format(i), end='')
print('')
print(" ", end='')
for i in range(0,10):
print("----", end='')
for i in range(hist_top+1):
if 0 == (i % 10):
print("")
print("{:3})".format(i), end='')
print(" {:3}".format(hist[i]), end='')
print("") # finish the last line
print("")
else:
event_print('
Histogram:%s
' % description)
event_print('')
event_print(' ')
event_print(' | ', end='')
for i in range(0,10):
event_print("{:3} | ".format(i), end='')
event_print('')
event_print(' ')
event_print(' ')
event_print(' ')
for i in range(hist_top+1):
if 0 == (i % 10):
if 0 != i:
event_print('
')
event_print(" {:3}) | ".format(i), end='')
event_print("{:3} | ".format(hist[i]), end='')
event_print("
")
event_print(' ')
event_print('
')
event_print('
')
def display_statistics(is_html=False):
compute_histogram(taskTimeList, TIME_OVERCOUNT, True,
"For each task, max number of tasks executing in parallel",is_html)
compute_histogram(recipeTimeList, TIME_OVERCOUNT, True,
"For each recipe's task set, max number of recipes executing in parallel",is_html)
compute_histogram(taskList, TASK_OVERCOUNT, False,
"For each task, max number of tasks that overlap its build",is_html)
compute_histogram(recipeList, RECIPE_OVERCOUNT, False,
"For each recipe's task set, max number of recipes that overlap its build",is_html)
#################################
# display task and recipe tables
#
def display_tasks(filter_string,show_overlaps):
# auto wildcard at end
if (0 == len(filter_string)) or ('*' != filter_string[-1]):
filter_string = filter_string + '*'
filter_string = filter_string.replace('*','.*')
prog = re.compile(filter_string)
if show_overlaps:
print('Task Table (Recipe,Task,Start,Stop,Overlap count,Overlap list):')
for t in taskList:
if prog.match(t[TASK_RECIPE]+':'+t[TASK_NAME]):
print(' '+str(t))
else:
print('Task Table (Recipe,Task,Start,Stop,Overlap count):')
for t in taskList:
if prog.match(t[TASK_RECIPE]+':'+t[TASK_NAME]):
print(' %s,%s,%s,%d' % (t[TASK_RECIPE]+':'+t[TASK_NAME],t[TASK_START],t[TASK_STOP],t[TASK_OVERCOUNT]))
def display_recipes(filter_string,show_overlaps):
# auto wildcard at end
if (0 == len(filter_string)) or ('*' != filter_string[-1]):
filter_string = filter_string + '*'
filter_string = filter_string.replace('*','.*')
prog = re.compile(filter_string)
if show_overlaps:
print("Recipe Table (Recipe,Start,Stop,Overlap count,Overlap list):")
for r in recipeList:
if prog.match(r[RECIPE_NAME]):
print(" "+str(r))
else:
print('Task Table (Recipe,Task,Start,Stop,Overlap count):')
for r in recipeList:
if prog.match(r[RECIPE_NAME]):
print(' %s,%s,%s,%d' % (r[RECIPE_NAME],r[RECIPE_START],r[RECIPE_STOP],r[RECIPE_OVERCOUNT]))
#################################
# display time event lists
#
def display_task_events(filter_string):
prog = prepare_filter(filter_string)
print('Task Event List (State,Overlap Count,Time,Recipe,Task):')
for t in taskTimeList:
if START == t[TIME_STATE]:
if prog.match(t[TIME_RECIPE]+':'+t[TIME_TASK]):
print(' '+str(t))
def display_recipe_events(filter_string):
prog = prepare_filter(filter_string)
print('Recipe Event List (State,Overlap Count,Time,Recipe):')
for r in recipeTimeList:
if START == r[TIME_STATE]:
if prog.match(r[TIME_RECIPE]):
print(' '+str(r))
#################################
# display overlap lists
#
# if empty filter_string, print all entries
# if filter_string, print all matching entries
# if filter_string=0, print all entries with zero overlaps
# if filter_string=n, print the n top maximum overlap sets
#
def display_task_overlaps(filter_string,file):
if not output_file_action('open',file):
return
if '0' == filter_string:
event_print('\nTasks with zero overlap:')
for t in taskList:
if 0 == t[TASK_OVERCOUNT]:
event_print(' '+t[TASK_RECIPE]+':'+t[TASK_NAME])
elif filter_string.isdigit():
max_count = int(filter_string)
event_print('\nTasks with maximum overlap:')
maxTasks = sorted(taskList, key=itemgetter(TASK_OVERCOUNT), reverse=True)
last_max=maxTasks[0][TASK_OVERCOUNT]
for t in maxTasks:
if last_max != t[TASK_OVERCOUNT]:
last_max = t[TASK_OVERCOUNT]
max_count -= 1
if 0 == max_count:
break
event_print(t[TASK_RECIPE]+':'+t[TASK_NAME]+', COUNT=' + str(t[TASK_OVERCOUNT]) + ')')
for o in t[TASK_OVERLIST]:
event_print(" %s" % o)
else:
prog = prepare_filter(filter_string)
event_print('\nTask overlap list (by recipe):')
for t in taskList:
if prog.match(t[TASK_RECIPE]+':'+t[TASK_NAME]):
event_print(t[TASK_RECIPE]+':'+t[TASK_NAME]+', COUNT=' + str(t[TASK_OVERCOUNT]) + ')')
if 0 == t[TASK_OVERCOUNT]:
event_print(' Zero overlaps')
else:
for o in t[TASK_OVERLIST]:
event_print(' %s' % o)
if 0 == len(taskList):
event_print(" There are no tasks with zero overlap")
event_print('')
output_file_action('close',file)
def display_recipe_overlaps(filter_string,file):
if not output_file_action('open',file):
return
if '0' == filter_string:
event_print("\nRecipes with zero overlap:")
for r in recipeList:
if 0 == r[RECIPE_OVERCOUNT]:
event_print(' '+r[RECIPE_NAME])
elif filter_string.isdigit():
max_count = int(filter_string)
event_print("\nRecipes with maximum overlap:")
maxRecipes = sorted(recipeList, key=itemgetter(RECIPE_OVERCOUNT), reverse=True)
last_max=maxRecipes[0][RECIPE_OVERCOUNT]
for r in maxRecipes:
if last_max != r[RECIPE_OVERCOUNT]:
last_max = r[RECIPE_OVERCOUNT]
max_count -= 1
if 0 == max_count:
break
event_print(r[RECIPE_NAME]+', COUNT=' + str(r[RECIPE_OVERCOUNT]) + ')')
for o in r[RECIPE_OVERLIST]:
event_print(" %s" % o)
else:
prog = prepare_filter(filter_string)
event_print("\nRecipe overlap list:")
for r in recipeList:
if prog.match(r[RECIPE_NAME]):
event_print(r[RECIPE_NAME]+', COUNT=' + str(r[RECIPE_OVERCOUNT]) + ')')
if 0 == r[RECIPE_OVERCOUNT]:
event_print(" Zero overlaps")
else:
for o in r[RECIPE_OVERLIST]:
event_print(" %s" % o)
if 0 == len(recipeList):
event_print(" There are no recipes with zero overlap")
event_print('')
output_file_action('close',file)
#################################
# graph the overlap data
# Text and HTML output
#
threads=[]
thread_filter=[]
thread_class=[]
def display_html_prolog(columns,isTask):
event_print('')
event_print('')
event_print(' ')
event_print('')
event_print(' ')
event_print('')
event_print('')
event_print('
')
if isTask:
event_print('Task Execution Overlap Table
')
event_print('(derived from the Toaster event database)
')
else:
event_print('Recipe Execution Overlap Table
')
event_print('(derived from the Toaster event database)
')
event_print('
')
event_print("CompletedOn=%s, Outcome=%s, Project='%s', Target='%s', Task='%s', Machine='%s'
" %
(build_data['completed_on'],build_outcome(str(build_data['outcome'])),
build_data['project'],build_data['target'],build_data['task'],build_data['machine']) )
event_print('Task Count=%d, Recipe Count=%d
' %
(len(taskList),len(recipeList)) )
event_print('
')
event_print('')
event_print(' ')
for c in range(columns):
event_print(' %d | ' % (c+1))
if isTask:
event_print(' Recipe:Task | ')
else:
event_print(' Recipe | ')
event_print(' ')
event_print(' ')
def display_html_line(columns,task,action,position,is_filtered_task):
if '-' != action:
event_print(' ',end='')
for i in range(columns):
if i == position:
if is_filtered_task:
content='['+action+']'
else:
content=action
if '+' == action:
# transition to first
if 'oax' == thread_class[i]:
thread_class[i] = 'oaf'
elif 'obx' == thread_class[i]:
thread_class[i] = 'obf'
elif 'eax' == thread_class[i]:
thread_class[i] = 'eaf'
elif 'ebx' == thread_class[i]:
thread_class[i] = 'ebf'
else:
# transition to toggled none
if thread_class[i] in ('oaf','oac'):
thread_class[i] = 'obx'
elif thread_class[i] in ('obf','obc'):
thread_class[i] = 'oax'
elif thread_class[i] in ('eaf','eac'):
thread_class[i] = 'ebx'
else:
thread_class[i] = 'eax'
elif '' != threads[i]:
content=thread_filter[i]
# if first transition to continued
if 'oaf' == thread_class[i]:
thread_class[i] = 'oac'
elif 'obf' == thread_class[i]:
thread_class[i] = 'obc'
elif 'eaf' == thread_class[i]:
thread_class[i] = 'eac'
elif 'ebf' == thread_class[i]:
thread_class[i] = 'ebc'
else:
content=''
if '-' != action:
event_print('%s | ' % (thread_class[i],content), end='')
if '-' != action:
if is_filtered_task:
event_print(''+task+' |
')
else:
event_print(''+task+' | ')
def display_html_epilog():
event_print(' ')
event_print('
')
event_print('
')
display_statistics(True)
event_print('')
def display_thread_line(columns,task,action,position,is_filtered_task):
event_print(' |',end='')
for i in range(columns):
if i == position:
event_print(''+action+'|',end='')
elif '' != threads[i]:
event_print('%s|' % thread_filter[i],end='')
else:
event_print(' |',end='')
if is_filtered_task:
event_print(' *'+task)
else:
event_print(' '+task)
def graph_task_overlaps(is_html,filter_string,file):
global threads,thread_filter
if not output_file_action('open',file):
return
if is_html:
display_html_prolog(task_execute_max,True)
else:
event_print('\nGraph Task Overlaps:')
threads=[]
thread_filter=[]
for i in range(task_execute_max):
threads.append('')
thread_filter.append('')
if 1 == (i % 2):
thread_class.append('eax')
else:
thread_class.append('oax')
# setup the filter, if any
match_tasks=[]
match_overlaps=[]
if filter_string:
prog = prepare_filter(filter_string)
for t in taskList:
if prog.match(t[TASK_RECIPE]+":"+t[TASK_NAME]):
match_tasks.append(t[TASK_RECIPE]+":"+t[TASK_NAME])
match_overlaps.append(t[TASK_RECIPE]+":"+t[TASK_NAME])
match_overlaps.extend(t[TASK_OVERLIST])
for t in taskTimeList:
task=t[TIME_RECIPE]+":"+t[TIME_TASK]
position=0
action=' '
try:
is_filtered_task = (0 <= match_tasks.index(task))
except:
is_filtered_task=False
if filter_string and not task in match_overlaps:
continue
if START == t[TIME_STATE]:
for i in range(task_execute_max):
if '' == threads[i]:
threads[i]=task
if is_filtered_task:
thread_filter[i]='*'
else:
thread_filter[i]='"'
position=i
action='+'
break
else:
for i in range(task_execute_max):
if task == threads[i]:
threads[i]=''
position=i
action='-'
break
if is_html:
display_html_line(task_execute_max,task,action,position,is_filtered_task)
else:
display_thread_line(task_execute_max,task,action,position,is_filtered_task)
if is_html:
display_html_epilog()
output_file_action('close',file)
event_print('')
def graph_recipe_overlaps(is_html,filter_string,file):
global threads,thread_filter
if not output_file_action('open',file):
return
if is_html:
display_html_prolog(recipe_execute_max,False)
else:
event_print("\nGraph Recipe Overlaps:")
threads=[]
for i in range(recipe_execute_max):
threads.append('')
thread_filter.append('')
if 1 == (i % 2):
thread_class.append('eax')
else:
thread_class.append('oax')
# setup the filter, if any
match_recipes=[]
match_overlaps=[]
if filter_string:
prog = prepare_filter(filter_string)
for r in recipeList:
if prog.match(r[RECIPE_NAME]):
match_recipes.append(r[RECIPE_NAME])
match_overlaps.append(r[RECIPE_NAME])
match_overlaps.extend(r[RECIPE_OVERLIST])
for t in recipeTimeList:
recipe=t[TIME_RECIPE]
position=0
action=' '
try:
is_filtered_task = (0 <= match_recipes.index(recipe))
except:
is_filtered_task=False
if filter_string and not recipe in match_overlaps:
continue
if START == t[TIME_STATE]:
for i in range(recipe_execute_max):
if '' == threads[i]:
threads[i]=recipe
if is_filtered_task:
thread_filter[i]='*'
else:
thread_filter[i]='"'
position=i
action='+'
break
else:
for i in range(recipe_execute_max):
if recipe == threads[i]:
threads[i]=''
thread_filter[i]=''
position=i
action='-'
break
if is_html:
display_html_line(recipe_execute_max,recipe,action,position,is_filtered_task)
else:
display_thread_line(recipe_execute_max,recipe,action,position,is_filtered_task)
if is_html:
display_html_epilog()
output_file_action('close',file)
event_print('')
#################################
# main loop
#
def main(argv):
global build
print("\nWelcome to event_overlap.py: enter '?' for help\n")
# connect to the database
if 0 < len(argv):
filename=argv[0]
else:
filename='toaster.sqlite'
connect_database(filename)
# fetch the default build data
try:
build_cursor.execute('SELECT * FROM orm_build')
build=build_cursor.fetchone()
except:
print("ERROR: the database '%s' does not have build data" % database_file)
exit(1)
fetch_build_data(build[BUILD_ORM_ID])
while True:
# get next command
command = ''
arg=''
file=''
commands = input('Command: ').split()
if 0 == len(commands):
continue
for i in range(len(commands)):
if 0 == i:
command = commands[i]
continue
if '>' == commands[i]:
if (i+1) < len(commands):
file = commands[i+1]
break;
arg = commands[i]
# process commands
if '?' == command[0]:
show_help()
continue
elif 'q' == command[0]:
print("Quitting!")
break
elif 'b' == command[0]:
if 0 == len(arg):
show_builds()
else:
if not fetch_build_data(int(arg)):
build = None
continue
if 0 == len(command):
continue
# require build data for the remaining commands
if None == build:
print('ERROR: Open a build first to execute this command')
continue
if 'd' == command[0]:
display_statistics(False)
elif 't' == command[0]:
display_tasks(arg,False)
elif 'r' == command[0]:
display_recipes(arg,False)
elif 'T' == command[0]:
display_tasks(arg,True)
elif 'R' == command[0]:
display_recipes(arg,True)
elif 'e' == command[0]:
display_task_events(arg)
elif 'E' == command[0]:
display_recipe_events(arg)
elif 'o' == command[0]:
display_task_overlaps(arg,file)
elif 'O' == command[0]:
display_recipe_overlaps(arg,file)
elif 'g' == command[0]:
graph_task_overlaps(False,arg,file)
elif 'G' == command[0]:
graph_recipe_overlaps(False,arg,file)
elif 'h' == command[0]:
graph_task_overlaps(True,arg,file)
elif 'H' == command[0]:
graph_recipe_overlaps(True,arg,file)
# clean up and finish
conn.close()
if __name__ == '__main__':
main(sys.argv[1:])