diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..915652f --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,540 @@ +Copyright LAAS-CNRS (2012-2013) + +maxime.cheramy@laas.fr + +This software is a computer program whose purpose is to provide a +simulator for Multiprocessor Real-Time Scheduling with Overheads. + +This software is governed by the CeCILL license under French law and +abiding by the rules of distribution of free software. You can use, +modify and/ or redistribute the software under the terms of the CeCILL +license as circulated by CEA, CNRS and INRIA at the following URL +"http://www.cecill.info". + +As a counterpart to the access to the source code and rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors have only limited +liability. + +In this respect, the user's attention is drawn to the risks associated +with loading, using, modifying and/or developing or reproducing the +software by the user in light of its specific status of free software, +that may mean that it is complicated to manipulate, and that also +therefore means that it is reserved for developers and experienced +professionals having in-depth computer knowledge. Users are therefore +encouraged to load and test the software's suitability as regards their +requirements in conditions enabling the security of their systems and/or +data to be ensured and, more generally, to use and operate it in the +same conditions as regards security. + +The fact that you are presently reading this means that you have had +knowledge of the CeCILL license and that you accept its terms. + +========== + +CeCILL FREE SOFTWARE LICENSE AGREEMENT + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to + users, + * secondly, the election of a governing law, French law, with which + it is conformant, both as regards the law of torts and + intellectual property law, and the protection that it offers to + both authors and holders of the economic rights over software. + +The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +license are: + +Commissariat à l'Energie Atomique - CEA, a public scientific, technical +and industrial research establishment, having its principal place of +business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange, 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +The purpose of this Free Software license agreement is to grant users +the right to modify and redistribute the software governed by this +license within the framework of an open source distribution model. + +The exercising of these rights is conditional upon certain obligations +for users so as to preserve this status for all subsequent redistributions. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +suitability of the software as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Contribution: means any or all modifications, corrections, translations, +adaptations and/or new functions integrated into the Software by any or +all Contributors, as well as any or all Internal Modules. + +Module: means a set of sources files including their documentation that +enables supplementary functions or services in addition to those offered +by the Software. + +External Module: means any or all Modules, not derived from the +Software, so that this Module and the Software run in separate address +spaces, with one calling the other when they are run. + +Internal Module: means any or all Module, connected to the Software so +that they both execute in the same address space. + +GNU GPL: means the GNU General Public License version 2 or any +subsequent version, as published by the Free Software Foundation Inc. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 hereinafter for the whole term of the +protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical + medium; + * (ii) the first time the Licensee exercises any of the rights + granted hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +hereinabove, and the Licensee hereby acknowledges that it has read and +understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or + all medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission + or storage operation as regards the Software, that it is entitled + to carry out hereunder. + + + 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS + +The right to make Contributions includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting software. + +The Licensee is authorized to make any or all Contributions to the +Software provided that it includes an explicit notice that it is the +author of said Contribution and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows future Licensees unhindered access to +the full Source Code of the Software by indicating how to access it, it +being understood that the additional cost of acquiring the Source Code +shall not exceed the cost of transferring the data. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +When the Licensee makes a Contribution to the Software, the terms and +conditions for the distribution of the resulting Modified Software +become subject to all the provisions of this Agreement. + +The Licensee is authorized to distribute the Modified Software, in +source code or object code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the object code of the Modified +Software is redistributed, the Licensee allows future Licensees +unhindered access to the full source code of the Modified Software by +indicating how to access it, it being understood that the additional +cost of acquiring the source code shall not exceed the cost of +transferring the data. + + + 5.3.3 DISTRIBUTION OF EXTERNAL MODULES + +When the Licensee has developed an External Module, the terms and +conditions of this Agreement do not apply to said External Module, that +may be distributed under a separate license agreement. + + + 5.3.4 COMPATIBILITY WITH THE GNU GPL + +The Licensee can include a code that is subject to the provisions of one +of the versions of the GNU GPL in the Modified or unmodified Software, +and distribute that entire code under the terms of the same version of +the GNU GPL. + +The Licensee can include the Modified or unmodified Software in a code +that is subject to the provisions of one of the versions of the GNU GPL, +and distribute that entire code under the terms of the same version of +the GNU GPL. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by this Agreement, for the duration set forth in Article 4.2. + + + 6.2 OVER THE CONTRIBUTIONS + +The Licensee who develops a Contribution is the owner of the +intellectual property rights over this Contribution as defined by +applicable law. + + + 6.3 OVER THE EXTERNAL MODULES + +The Licensee who develops an External Module is the owner of the +intellectual property rights over this External Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution. + + + 6.4 JOINT PROVISIONS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies + of the Software modified or not. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights of the Holder and/or Contributors on the +Software and to take, where applicable, vis-à-vis its staff, any and all +measures required to ensure respect of said intellectual property rights +of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the suitability of the product for its requirements, its good working +order, and for ensuring that it shall not cause damage to either persons +or properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 and, in particular, without any warranty +as to its commercial value, its secured, safe, innovative or relevant +nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +assistance for its defense. Such technical and legal assistance shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version, subject to the provisions of Article 5.3.4. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. + + +Version 2.0 dated 2006-09-05. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d34d867 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include COPYING.txt +recursive-include docs *.html *.css *.js *.png *.gif *.inv diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..76002fd --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,28 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = . + +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean doc + +default: doc + +doc: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dist: doc + scp -r html/* mcheramy@homepages.laas.fr:~/simso/doc/ + +clean: + -rm -rf $(BUILDDIR)/html + diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..1134b43 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# +# SimSo documentation build configuration file, created by +# sphinx-quickstart on Sat Dec 7 12:49:07 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] +add_module_names=False +autoclass_content='both' + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'SimSo' +copyright = u'2013, Maxime Chéramy' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'nature' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = {'nosidebar': True} +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = "SimSo documentation" + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = False + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'SimSodoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'SimSo.tex', u'SimSo Documentation', + u'Maxime Chéramy', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'simso', u'SimSo Documentation', + [u'Maxime Chéramy'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'SimSo', u'SimSo Documentation', + u'Maxime Chéramy', 'SimSo', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +sys.path.append('..') diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle new file mode 100644 index 0000000..67e4852 Binary files /dev/null and b/docs/doctrees/environment.pickle differ diff --git a/docs/doctrees/faq.doctree b/docs/doctrees/faq.doctree new file mode 100644 index 0000000..b4949ad Binary files /dev/null and b/docs/doctrees/faq.doctree differ diff --git a/docs/doctrees/index.doctree b/docs/doctrees/index.doctree new file mode 100644 index 0000000..43287de Binary files /dev/null and b/docs/doctrees/index.doctree differ diff --git a/docs/doctrees/introduction.doctree b/docs/doctrees/introduction.doctree new file mode 100644 index 0000000..88e2aa1 Binary files /dev/null and b/docs/doctrees/introduction.doctree differ diff --git a/docs/doctrees/licenses.doctree b/docs/doctrees/licenses.doctree new file mode 100644 index 0000000..022bcf8 Binary files /dev/null and b/docs/doctrees/licenses.doctree differ diff --git a/docs/doctrees/modules.doctree b/docs/doctrees/modules.doctree new file mode 100644 index 0000000..17633fb Binary files /dev/null and b/docs/doctrees/modules.doctree differ diff --git a/docs/doctrees/text_mode.doctree b/docs/doctrees/text_mode.doctree new file mode 100644 index 0000000..465ff93 Binary files /dev/null and b/docs/doctrees/text_mode.doctree differ diff --git a/docs/doctrees/write_scheduler.doctree b/docs/doctrees/write_scheduler.doctree new file mode 100644 index 0000000..6eacf5c Binary files /dev/null and b/docs/doctrees/write_scheduler.doctree differ diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 0000000..0638e31 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,27 @@ +Frequently Asked Questions +========================== + +(please send me your questions in order to complete this list.) + +Does it work on my operating system? +------------------------------------ + +SimSo is fully written in Python and depends on libraries that are available for Linux, Mac OS and Windows. If you install the dependencies, you can install SimSo using the source code. Otherwise, I also provide a debian/ubuntu package and a windows installer. + +Can I use my own task generator? +-------------------------------- + +If you are using SimSo from a Python script, you can create the tasks easily with the characteristics of your choice. + +If you are using the graphical user interface, you can generate an XML file compatible with SimSo. The XSD schema is shipped with the source code (it is incomplete though). + + +Does SimSo support sporadic tasks? +---------------------------------- + +This was implemented in July 2014. With sporadic tasks, you must specify the list of activation dates. This allows you to use an external generator to control the arrival of the jobs. + +Do you handle uniform and/or heterogeneous processors? +------------------------------------------------------ + +It is possible to set a speed for each processor (uniform) but you can't specify task execution speed in function of the processor (heterogeneous). However, this could be done by adding a new Execution Time Model. diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo new file mode 100644 index 0000000..0e9f691 --- /dev/null +++ b/docs/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 0f051803cb376160792863ce353e53c6 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/_sources/faq.txt b/docs/html/_sources/faq.txt new file mode 100644 index 0000000..0638e31 --- /dev/null +++ b/docs/html/_sources/faq.txt @@ -0,0 +1,27 @@ +Frequently Asked Questions +========================== + +(please send me your questions in order to complete this list.) + +Does it work on my operating system? +------------------------------------ + +SimSo is fully written in Python and depends on libraries that are available for Linux, Mac OS and Windows. If you install the dependencies, you can install SimSo using the source code. Otherwise, I also provide a debian/ubuntu package and a windows installer. + +Can I use my own task generator? +-------------------------------- + +If you are using SimSo from a Python script, you can create the tasks easily with the characteristics of your choice. + +If you are using the graphical user interface, you can generate an XML file compatible with SimSo. The XSD schema is shipped with the source code (it is incomplete though). + + +Does SimSo support sporadic tasks? +---------------------------------- + +This was implemented in July 2014. With sporadic tasks, you must specify the list of activation dates. This allows you to use an external generator to control the arrival of the jobs. + +Do you handle uniform and/or heterogeneous processors? +------------------------------------------------------ + +It is possible to set a speed for each processor (uniform) but you can't specify task execution speed in function of the processor (heterogeneous). However, this could be done by adding a new Execution Time Model. diff --git a/docs/html/_sources/index.txt b/docs/html/_sources/index.txt new file mode 100644 index 0000000..3a61449 --- /dev/null +++ b/docs/html/_sources/index.txt @@ -0,0 +1,22 @@ +SimSo documentation +=================== + +.. toctree:: + :maxdepth: 2 + + introduction.rst + faq.rst + write_scheduler.rst + text_mode.rst + modules.rst + licenses.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + + diff --git a/docs/html/_sources/introduction.txt b/docs/html/_sources/introduction.txt new file mode 100644 index 0000000..7e3005d --- /dev/null +++ b/docs/html/_sources/introduction.txt @@ -0,0 +1,92 @@ +Introduction +============ + +What is SimSo? +-------------- + +SimSo is a scheduling simulator for real-time multiprocessor architectures that takes into account some scheduling overheads (scheduling decisions, context switches) and the impact of caches through statistical models. Based on a Discrete-Event Simulator (SimPy), it allows quick simulations and a fast prototyping of scheduling policies using Python. + +SimSo is an open source software, available under the `CeCILL license `_, a GPL compatible license. + +Download +-------- + +You can find the last version of SimSo on the `SimSo Website`_. + +.. _Simso Website: http://homepages.laas.fr/mcheramy/simso/ + +Installation +------------ + +SimSo is available for the main platforms and so is its source code. The archive containing the source code is more often updated and should be used when possible. + +In order to install SimSo from the souce code, the dependences must be installed first. Then, type "python setup.py install" to install SimSo. + +Dependencies +"""""""""""" + +When using SimSo from the sources, the following softwares and librairies are required: + + - Python 2.7+ + - SimPy 2.3.1 (not compatible with SimPy 3) + - NumPy 1.6+ + - PyQt4 4.9+ + +If you are using a binary, everything should be packed in the binary. + +First step +---------- + +SimSo is provided with a graphical user interface that aims to be very easy to use. This is a good way to develop and test a scheduler. See `How to write a scheduling policy `_. + +It is also possible to use SimSo as a library. This allows in particular to run simulations in text mode with a maximum of flexibility. + +Available Schedulers +-------------------- + +Currently, the following schedulers are available: + +**Uniprocessor schedulers** + + - Earliest Deadline First (EDF) + - Rate Monotonic (RM) + - Fixed Priority (FP) + - Static-EDF (A DVFS EDF) + - CC-EDF: Real-Time dynamic voltage scaling for low-power embedded operating systems by P. Pillai et al. + +**Uniprocessor schedulers adapted to multiprocessor** + - Global-EDF + - Global-RM + - Earliest Deadline Zero Laxity (EDZL) + - Least Laxity First (LLF) + - Modified Least Laxity First (MLLF): A Modified Least-Laxity-First Scheduling Algorithm for Real-Time Tasks by S.-H. Oh and S.-M. Yang. + - PriD: Real-time scheduling on multiprocessors by J., Baruah, S., & Funk, S. + - EDF-US + - G-FL: Fair lateness scheduling: Reducing maximum lateness in G-EDF-like scheduling by Erickson and Anderson. + +**Partitionned** + Any uniprocessor scheduler using a partitionning algorithm. The following heuristics are provided: + + - First-Fit and Decreasing-First-Fit + - Next-Fit and Decreasing-Next-Fit + - Best-Fit and Decreasing-Best-Fit + - Worst-Fit and Decreasing-Worst-Fit + +**PFair** + - Earliest Pseudo-Deadline First (EPDF) + - PD2 and ER-PD2: Early-Release Fair Scheduling. In Proceedings of the Euromicro Conference on Real-Time Systems by J. H. Anderson et al. + +**DPFair** + - LLREF: An Optimal Real-Time Scheduling Algorithm for Multiprocessors by Cho et al. + - LRE-TL: An Optimal Multiprocessor Scheduling Algorithm for Sporadic Task Sets by S. Funk et al. + - DP-WRAP: DP-FAIR: A Simple Model for Understanding Optimal Multiprocessor Scheduling by Levin et al. + - BF: Multiple-resource periodic scheduling problem: how much fairness is necessary? by Zhu et al. + - NVNLF: Work-Conversing Optimal Real-Time Scheduling on Multiprocessors by Funaoka et al. + +**Semi-partitionned** + - EKG: Multiprocessor Scheduling with Few Preemptions by B. Andersson and E. Tovar. + - EDHS: Semi-Partitioning Technique for Multiprocessor Real-Time Scheduling by Kato et al. + +**Other** + - RUN: Optimal Multiprocessor Real-Time Scheduling via Reduction to Uniprocessor by Regnier et al. + - U-EDF: an Unfair but Optimal Multiprocessor Scheduling Algorithm for Sporadic Tasks by Nelissen et al. diff --git a/docs/html/_sources/licenses.txt b/docs/html/_sources/licenses.txt new file mode 100644 index 0000000..d6945ec --- /dev/null +++ b/docs/html/_sources/licenses.txt @@ -0,0 +1,63 @@ +Licenses +======== + +SimSo +----- + +SimSo is distributed under the CeCILL license:: + + Copyright LAAS-CNRS (2012-2015) + + maxime.cheramy@laas.fr + + This software is a computer program whose purpose is to provide a + simulator for Multiprocessor Real-Time Scheduling with Overheads. + + This software is governed by the CeCILL license under French law and + abiding by the rules of distribution of free software. You can use, + modify and/ or redistribute the software under the terms of the CeCILL + license as circulated by CEA, CNRS and INRIA at the following URL + "http://www.cecill.info". + + As a counterpart to the access to the source code and rights to copy, + modify and redistribute granted by the license, users are provided only + with a limited warranty and the software's author, the holder of the + economic rights, and the successive licensors have only limited + liability. + + In this respect, the user's attention is drawn to the risks associated + with loading, using, modifying and/or developing or reproducing the + software by the user in light of its specific status of free software, + that may mean that it is complicated to manipulate, and that also + therefore means that it is reserved for developers and experienced + professionals having in-depth computer knowledge. Users are therefore + encouraged to load and test the software's suitability as regards their + requirements in conditions enabling the security of their systems and/or + data to be ensured and, more generally, to use and operate it in the + same conditions as regards security. + + The fact that you are presently reading this means that you have had + knowledge of the CeCILL license and that you accept its terms. + + + +Dependencies +------------ + +SimPy +^^^^^ + +SimPy is an object-oriented, process-based discrete-event simulation language for Python. +SimPy 2 is released under the GNU Lesser GPL (LGPL) license. + +NumPy +^^^^^ + +Numpy is licensed under the BSD license, enabling reuse with few restrictions. + +PyQt +^^^^ + +The Graphical User Interface of SimSo relies on the GPL version of PyQt. Qt itself is available +in GPL and LGPL versions. + diff --git a/docs/html/_sources/modules.txt b/docs/html/_sources/modules.txt new file mode 100644 index 0000000..6cd588e --- /dev/null +++ b/docs/html/_sources/modules.txt @@ -0,0 +1,86 @@ +Main modules +============ + +.. contents:: Table of Contents + +simso.core module +----------------- + +.. automodule:: simso.core + +Scheduler +^^^^^^^^^ + +.. automodule:: simso.core.Scheduler + :members: + +Task +^^^^ + +.. automodule:: simso.core.Task + :members: + +Job +^^^ + +.. automodule:: simso.core.Job + :members: + + +Model +^^^^^ + +.. automodule:: simso.core.Model + :members: + + +Processor +^^^^^^^^^ + +.. automodule:: simso.core.Processor + :members: + +Timer +^^^^^ + +.. automodule:: simso.core.Timer + :members: + +Logger +^^^^^^ + +.. automodule:: simso.core.Logger + :members: + +results +^^^^^^^ + +.. automodule:: simso.core.results + :members: + + +simso.configuration module +-------------------------- + +.. automodule:: simso.configuration + +Configuration +^^^^^^^^^^^^^ + +.. automodule:: simso.configuration.Configuration + :members: + +simso.generator module +---------------------- + +.. automodule:: simso.generator.task_generator + :members: + +simso.utils module +------------------ + +PartitionedScheduler +^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: simso.utils.PartitionedScheduler + :members: diff --git a/docs/html/_sources/text_mode.txt b/docs/html/_sources/text_mode.txt new file mode 100644 index 0000000..1ff13cd --- /dev/null +++ b/docs/html/_sources/text_mode.txt @@ -0,0 +1,137 @@ +Using SimSo in script mode +========================== + +SimSo can be used as a library in order to automatize wide experimentations and have a maximum of flexibility on the analysis of the results. In this tutorial, a few examples are provided. + +.. contents:: Table of Contents + + +Loading a configuration using a simulation file +----------------------------------------------- + +A :class:`Configuration ` can be initialized with a file passed to its constructor:: + + configuration = Configuration(argv[0]) + +The configuration could also be partial and completed by the script. Finally, the configuration can be checked for correctness using the :meth:`check_all ` method:: + + configuration.check_all() + +This method will raise an exception if something is not correct. + +Creating a configuration from scratch +------------------------------------- + +It is also possible to create a new configuration from an empty configuration. This is done by instantiating a :class:`Configuration ` object without argument. Then, its attributes can be changed:: + + configuration = Configuration() + + configuration.duration = 100 * configuration.cycles_per_ms + +It is also possible to add tasks:: + + configuration.add_task(name="T1", identifier=1, period=7, + activation_date=0, wcet=3, deadline=7) + +And of course processors:: + + configuration.add_processor(name="CPU 1", identifier=1) + +Finally, a scheduler is also required:: + + configuration.scheduler_info.set_name("examples/RM.py", + configuration.cur_dir) + +Creating the Model +------------------ + +A :class:`configuration ` is an object grouping every characteristics of the system (tasks, processors, schedulers, etc). Such a configuration can be passed to the :class:`Model ` constructor in order to create the simulation:: + + model = Model(configuration) + +And the simulation can be run with the :meth:`run_model ` method:: + + model.run_model() + +Some basic logs can be get through the :meth:`logs ` attribute:: + + for log in model.logs: + print(log) + +First Example +------------- + +The following script simulate a system loading from a simulation file or configured from scratch:: + + import sys + from simso.core import Model + from simso.configuration import Configuration + + def main(argv): + if len(argv) == 1: + # Configuration load from a file. + configuration = Configuration(argv[0]) + else: + # Manual configuration: + configuration = Configuration() + + configuration.duration = 420 * configuration.cycles_per_ms + + # Add tasks: + configuration.add_task(name="T1", identifier=1, period=7, + activation_date=0, wcet=3, deadline=7) + configuration.add_task(name="T2", identifier=2, period=12, + activation_date=0, wcet=3, deadline=12) + configuration.add_task(name="T3", identifier=3, period=20, + activation_date=0, wcet=5, deadline=20) + + # Add a processor: + configuration.add_processor(name="CPU 1", identifier=1) + + # Add a scheduler: + configuration.scheduler_info.set_name("examples/RM.py", + configuration.cur_dir) + + # Check the config before trying to run it. + configuration.check_all() + + # Init a model from the configuration. + model = Model(configuration) + + # Execute the simulation. + model.run_model() + + # Print logs. + for log in model.logs: + print(log) + + +More details +------------ + +It is possible to get more information from the tasks using :class:`Results ` class. For example we could get the computation time of the jobs:: + + for task in model.results.tasks: + print(task.name + ":") + for job in task.jobs: + print("%s %.3f ms" % (job.name, job.computation_time)) + +Or the number of preemptions per task:: + + for task in model.results.task_list: + print("%s %d" % (task.name, sum([job.preemption_count for job in task.jobs]))) + +You can get all the metrics provided in the :class:`TaskR ` and :class:`JobR ` objects. Read the documentation of these classes to know exactly what is directly accessible. + +It is also possible to get the monitor object from each processors. This is a very detail history of the system. For example, you can count the number of context switches, where a context switch is something that happen when the previous task running on the same processor is different:: + + cxt = 0 + for processor in model.processors: + prev = None + for evt in processor.monitor: + if evt[1].event == ProcEvent.RUN: + if prev is not None and prev != evt[1].args.identifier: + cxt += 1 + prev = evt[1].args.identifier + + print("Number of context switches (without counting the OS): " + str(cxt)) diff --git a/docs/html/_sources/write_scheduler.txt b/docs/html/_sources/write_scheduler.txt new file mode 100644 index 0000000..8773838 --- /dev/null +++ b/docs/html/_sources/write_scheduler.txt @@ -0,0 +1,193 @@ +How to write a scheduling policy +================================ + +This tutorial explains through minimalist examples how to write a scheduler. + +.. contents:: Table of Contents + +Example 1: uniprocessor EDF +--------------------------- + +This example shows how to write an Earliest Deadline First scheduler for a single processor. As a reminder, the Earliest Deadline First prioritizes the tasks with the closest absolute deadline among all the ready tasks. A task is ready when it is activated and not finished. + +Creation of the file +"""""""""""""""""""" + +A scheduler for SimSo is a Python class that inherits from the :class:`simso.core.Scheduler` class. The first step is to write the skeleton of our scheduler. Create a file named "EDF_mono.py" and write the following code:: + + from simso.core import Scheduler + + class EDF_mono(Scheduler): + def init(self): + pass + + def on_activate(self, job): + pass + + def on_terminated(self, job): + pass + + def schedule(self, cpu): + pass + +It is mandatory for the class name to be identical to the file name. + +Explanation of the skeleton +""""""""""""""""""""""""""" + +The first thing done here is importing the :class:`Scheduler ` class. Then we define the `EDF_mono` class as a subclass of the `Scheduler`. + +Four methods are redifined: + +- The :meth:`init ` method is called when the simulation is ready to start, this is where the structures used by the scheduler should be initialized. The usual Python constructor is not guaranteed to be called before each simulation run and the :class:`Task ` and :class:`Processors ` are not instantiated yet when the scheduler is created. + +- The :meth:`on_activate ` method is called on task activations. + +- The :meth:`on_terminated ` method is called when a job finished its execution. + +- The :meth:`schedule ` method is called by the processor when it needs to run the scheduler. This method should not be called directly. + +Implementation +"""""""""""""" + +In a nutshell, the algorithm is the following: a list of ready jobs is kept up-to-date using the `on_activate` and `on_terminated` methods. When the schedule method is called, the ready job with the closest absolute deadline is chosen. + +So, the first step is to define a `ready_list`, and to append the jobs and remove them respectively when the jobs are activated and when they finish. The code should looks like that:: + + from core import Scheduler + + class EDF_mono(Scheduler): + def init(self): + self.ready_list = [] + + def on_activate(self, job): + self.ready_list.append(job) + + def on_terminated(self, job): + self.ready_list.remove(job) + + def schedule(self, cpu): + pass + + +The second step is to write the schedule logic. Selecting the job with the closest absolute deadline is pretty easy. But we need to be sure that there is at least one ready job. One possible implementation is:: + + def schedule(self, cpu): + if self.ready_list: # If at least one job is ready: + # job with the highest priority + job = min(self.ready_list, key=lambda x: x.absolute_deadline) + else: + job = None + + return (job, cpu) + +At this point, we are still missing a very important thing: calling the scheduler! This is not done by invoking the `schedule` method. As a reminder, that's the processor which is responsible to call the `scheduler`. The reason is that if an overhead must be applied, it is done on the processor running the scheduler. The good way to call the scheduler is by sending a message to the processor using the :meth:`resched ` method. + +Any job is affected to a processor. This is the last processor on which the task was running or an arbitrary processor on the first execution. The scheduler can be called indirectly using ``job.cpu.resched()`` when a scheduling event occurs. We could also use ``self.processors[0].resched`` to run the scheduler on the first (and only) processor of the system. + +This is the full code:: + + from simso.core import Scheduler + + + class EDF_mono(Scheduler): + def init(self): + self.ready_list = [] + + def on_activate(self, job): + self.ready_list.append(job) + job.cpu.resched() + + def on_terminated(self, job): + self.ready_list.remove(job) + job.cpu.resched() + + def schedule(self, cpu): + if self.ready_list: # If at least one job is ready: + # job with the highest priority + job = min(self.ready_list, key=lambda x: x.absolute_deadline) + else: + job = None + + return (job, cpu) + +Example 2: Partitionned EDF +--------------------------- + +The simplest method to handle multiprocessor architectures is to use partitionning. This approach consists in allocating the tasks to the processors and executing a mono-processor scheduler on each processor. + +In order to ease the work for the developer of a scheduler, an helping class, named :class:`PartitionedScheduler `, is provided. + +Initializing the scheduler +"""""""""""""""""""""""""" + +The :class:`PartitionedScheduler ` is defined in the `simso.utils` module. It is also necessary to load the :class:`SchedulerInfo ` class in order to give to the `PartitionedScheduler ` the mono-processor scheduler to use. The first thing to do is importing these classes:: + + from simso.utils import PartitionedScheduler + from simso.core.Scheduler import SchedulerInfo + +Then the Scheduler can be initialized like this:: + + class P_EDF(PartitionedScheduler): + def init(self): + PartitionedScheduler.init(self, SchedulerInfo("EDF_mono", EDF_mono)) + + +Defining the packing +"""""""""""""""""""" + +A First-Fit bin-packing can be used to affect the tasks to the processors. For that, the :meth:`packer` must be overriden:: + + def packer(self): + # First Fit + cpus = [[cpu, 0] for cpu in self.processors] + for task in self.task_list: + j = 0 + # Find a processor with free space. + while cpus[j][1] + float(task.wcet) / task.period > 1.0: + j += 1 + if j >= len(self.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + self.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + return True + + +Complete example +"""""""""""""""" + +Complete source code:: + + from simso.core.Scheduler import SchedulerInfo + from EDF_mono import EDF_mono + from simso.utils import PartitionedScheduler + + + class P_EDF(PartitionedScheduler): + def init(self): + PartitionedScheduler.init(self, SchedulerInfo("EDF_mono", EDF_mono)) + + def packer(self): + # First Fit + cpus = [[cpu, 0] for cpu in self.processors] + for task in self.task_list: + j = 0 + # Find a processor with free space. + while cpus[j][1] + float(task.wcet) / task.period > 1.0: + j += 1 + if j >= len(self.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + self.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + return True + diff --git a/docs/html/_static/ajax-loader.gif b/docs/html/_static/ajax-loader.gif new file mode 100644 index 0000000..61faf8c Binary files /dev/null and b/docs/html/_static/ajax-loader.gif differ diff --git a/docs/html/_static/basic.css b/docs/html/_static/basic.css new file mode 100644 index 0000000..967e36c --- /dev/null +++ b/docs/html/_static/basic.css @@ -0,0 +1,537 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/html/_static/comment-bright.png b/docs/html/_static/comment-bright.png new file mode 100644 index 0000000..551517b Binary files /dev/null and b/docs/html/_static/comment-bright.png differ diff --git a/docs/html/_static/comment-close.png b/docs/html/_static/comment-close.png new file mode 100644 index 0000000..09b54be Binary files /dev/null and b/docs/html/_static/comment-close.png differ diff --git a/docs/html/_static/comment.png b/docs/html/_static/comment.png new file mode 100644 index 0000000..92feb52 Binary files /dev/null and b/docs/html/_static/comment.png differ diff --git a/docs/html/_static/doctools.js b/docs/html/_static/doctools.js new file mode 100644 index 0000000..c5455c9 --- /dev/null +++ b/docs/html/_static/doctools.js @@ -0,0 +1,238 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/html/_static/down-pressed.png b/docs/html/_static/down-pressed.png new file mode 100644 index 0000000..6f7ad78 Binary files /dev/null and b/docs/html/_static/down-pressed.png differ diff --git a/docs/html/_static/down.png b/docs/html/_static/down.png new file mode 100644 index 0000000..3003a88 Binary files /dev/null and b/docs/html/_static/down.png differ diff --git a/docs/html/_static/file.png b/docs/html/_static/file.png new file mode 100644 index 0000000..d18082e Binary files /dev/null and b/docs/html/_static/file.png differ diff --git a/docs/html/_static/jquery.js b/docs/html/_static/jquery.js new file mode 100644 index 0000000..417eb67 --- /dev/null +++ b/docs/html/_static/jquery.js @@ -0,0 +1,9404 @@ +/*! + * jQuery JavaScript Library v1.7.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Fri Aug 29 09:46:34 UTC 2014 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, pass ) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; + + // Sets many values + if ( key && typeof key === "object" ) { + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); + } + chainable = 1; + + // Sets one value + } else if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = pass === undefined && jQuery.isFunction( value ); + + if ( bulk ) { + // Bulk operations only iterate when executing function values + if ( exec ) { + exec = fn; + fn = function( elem, key, value ) { + return exec.call( jQuery( elem ), value ); + }; + + // Otherwise they run against the entire set + } else { + fn.call( elems, value ); + fn = null; + } + } + + if ( fn ) { + for (; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + } + + chainable = 1; + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + fired = true; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + pixelMargin: true + }; + + // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead + jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for ( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, + paddingMarginBorderVisibility, paddingMarginBorder, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + paddingMarginBorder = "padding:0;margin:0;border:"; + positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; + paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; + style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; + html = "
" + + "" + + "
"; + + container = document.createElement("div"); + container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + div.innerHTML = ""; + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.width = div.style.padding = "1px"; + div.style.border = 0; + div.style.overflow = "hidden"; + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + } + + div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + if ( window.getComputedStyle ) { + div.style.marginTop = "1%"; + support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; + } + + if ( typeof container.style.zoom !== "undefined" ) { + container.style.zoom = 1; + } + + body.removeChild( container ); + marginDiv = div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split( ".", 2 ); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); + + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); + + self.triggerHandler( "setData" + part, parts ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + part, parts ); + }); + }, null, value, arguments.length > 1, null, false ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise( object ); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, isBool, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( isBool && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true, + coords: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: selector && quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + + // Don't process events on disabled elements (#6911, #8165) + if ( cur.disabled !== true ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} +// Expose origPOS +// "global" as in regardless of relation to brackets/parens +Expr.match.globalPOS = origPOS; + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.globalPOS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*", "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + + + + +
+
+
+ +
+

Frequently Asked Questions¶

+

(please send me your questions in order to complete this list.)

+
+

Does it work on my operating system?¶

+

SimSo is fully written in Python and depends on libraries that are available for Linux, Mac OS and Windows. If you install the dependencies, you can install SimSo using the source code. Otherwise, I also provide a debian/ubuntu package and a windows installer.

+
+
+

Can I use my own task generator?¶

+

If you are using SimSo from a Python script, you can create the tasks easily with the characteristics of your choice.

+

If you are using the graphical user interface, you can generate an XML file compatible with SimSo. The XSD schema is shipped with the source code (it is incomplete though).

+
+
+

Does SimSo support sporadic tasks?¶

+

This was implemented in July 2014. With sporadic tasks, you must specify the list of activation dates. This allows you to use an external generator to control the arrival of the jobs.

+
+
+

Do you handle uniform and/or heterogeneous processors?¶

+

It is possible to set a speed for each processor (uniform) but you can’t specify task execution speed in function of the processor (heterogeneous). However, this could be done by adding a new Execution Time Model.

+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/genindex.html b/docs/html/genindex.html new file mode 100644 index 0000000..7865a11 --- /dev/null +++ b/docs/html/genindex.html @@ -0,0 +1,736 @@ + + + + + + + + + Index — SimSo documentation + + + + + + + + + + + + + +
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | I + | J + | L + | M + | N + | O + | P + | R + | S + | T + | W + +
+

A

+ + + +
+ +
abort() (Job method) +
+ + +
aborted (Job attribute) +
+ + +
absolute_deadline (Job attribute) +
+ + +
activation_date (Job attribute) +
+ +
+ +
actual_computation_time_cycles (Job attribute) +
+ + +
add_processor() (Configuration method) +
+ +
+ +
(Scheduler method) +
+ +
+ +
add_task() (Configuration method) +
+ +
+ +
(Scheduler method) +
+ +
+ +
ATask (class in simso.core.Task) +
+ +
+ +

B

+ + +
+ +
best_fit() (in module simso.utils.PartitionedScheduler) +
+ +
+ +

C

+ + + +
+ +
calc_load() (Results method) +
+ + +
check_all() (Configuration method) +
+ + +
Configuration (class in simso.configuration.Configuration) +
+ + +
cpu (Job attribute) +
+ +
+ +
create_job() (GenericTask method) +
+ + +
csdp (TaskInfo attribute) +
+ + +
cycles_per_ms (Model attribute) +
+ +
+ +

D

+ + + +
+ +
data (GenericTask attribute) +
+ +
+ +
(Job attribute) +
+ +
+ +
deadline (GenericTask attribute) +
+ +
+ +
(Job attribute) +
+ +
+ +
decreasing_best_fit() (in module simso.utils.PartitionedScheduler) +
+ + +
decreasing_first_fit() (in module simso.utils.PartitionedScheduler) +
+ +
+ +
decreasing_next_fit() (in module simso.utils.PartitionedScheduler) +
+ + +
decreasing_worst_fit() (in module simso.utils.PartitionedScheduler) +
+ + +
duration (Model attribute) +
+ +
+ +

E

+ + + +
+ +
end_date (Job attribute) +
+ + +
etm (Model attribute) +
+ +
+ +
exceeded_deadline (Job attribute) +
+ +
+ +

F

+ + + +
+ +
filename (SchedulerInfo attribute) +
+ + +
first_fit() (in module simso.utils.PartitionedScheduler) +
+ +
+ +
followed_by (GenericTask attribute) +
+ +
+ +

G

+ + + +
+ +
gen_kato_utilizations() (in module simso.generator.task_generator) +
+ + +
gen_periods_discrete() (in module simso.generator.task_generator) +
+ + +
gen_periods_loguniform() (in module simso.generator.task_generator) +
+ + +
gen_periods_uniform() (in module simso.generator.task_generator) +
+ + +
gen_randfixedsum() (in module simso.generator.task_generator) +
+ + +
gen_ripoll() (in module simso.generator.task_generator) +
+ + +
gen_tasksets() (in module simso.generator.task_generator) +
+ +
+ +
gen_uunifastdiscard() (in module simso.generator.task_generator) +
+ + +
GenericTask (class in simso.core.Task) +
+ + +
get_cls() (SchedulerInfo method) +
+ + +
get_hyperperiod() (Configuration method) +
+ + +
get_lock() (Scheduler method) +
+ + +
get_observation_window() (Results method) +
+ +
+ +

I

+ + + +
+ +
identifier (GenericTask attribute) +
+ + +
init() (PartitionedScheduler method) +
+ +
+ +
(Scheduler method) +
+ +
+ +
instantiate() (SchedulerInfo method) +
+ +
+ +
internal_id (Processor attribute) +
+ + +
is_active() (Job method) +
+ + +
is_running() (Job method) +
+ +
+ +
(Processor method) +
+ +
+
+ +

J

+ + + +
+ +
Job (class in simso.core.Job) +
+ + +
JobR (class in simso.core.results) +
+ +
+ +
jobs (GenericTask attribute) +
+ +
+ +

L

+ + + +
+ +
log() (Logger method) +
+ + +
Logger (class in simso.core.Logger) +
+ +
+ +
logs (Logger attribute) +
+ +
+ +
(Model attribute) +
+ +
+
+ +

M

+ + + +
+ +
Model (class in simso.core.Model) +
+ +
+ +
monitor (GenericTask attribute) +
+ +
+ +

N

+ + + +
+ +
name (SchedulerInfo attribute) +
+ +
+ +
next_fit() (in module simso.utils.PartitionedScheduler) +
+ +
+ +

O

+ + + +
+ +
observation_window (Results attribute) +
+ + +
on_activate() (Scheduler method) +
+ +
+ +
on_terminated() (Scheduler method) +
+ +
+ +

P

+ + + +
+ +
PartitionedScheduler (class in simso.utils.PartitionedScheduler) +
+ + +
period (GenericTask attribute) +
+ +
+ +
(Job attribute) +
+ +
+ +
proc_info_list (Configuration attribute) +
+ + +
Processor (class in simso.core.Processor) +
+ +
+ +
ProcessorR (class in simso.core.results) +
+ + +
processors (Model attribute) +
+ + +
PTask (class in simso.core.Task) +
+ +
+ +

R

+ + + +
+ +
release_lock() (Scheduler method) +
+ + +
resched() (Processor method) +
+ + +
Results (class in simso.core.results) +
+ +
+ +
ret (Job attribute) +
+ + +
run_model() (Model method) +
+ + +
running (Processor attribute) +
+ +
+ +

S

+ + + +
+ +
save() (Configuration method) +
+ + +
schedule() (Scheduler method) +
+ + +
Scheduler (class in simso.core.Scheduler) +
+ + +
scheduler_info (Configuration attribute) +
+ + +
SchedulerInfo (class in simso.core.Scheduler) +
+ + +
SchedulerR (class in simso.core.results) +
+ + +
set_name() (SchedulerInfo method) +
+ + +
set_observation_window() (Results method) +
+ + +
set_stack_file() (TaskInfo method) +
+ + +
simso.configuration (module) +
+ + +
simso.configuration.Configuration (module) +
+ + +
simso.core (module) +
+ + +
simso.core.Job (module) +
+ + +
simso.core.Logger (module) +
+ +
+ +
simso.core.Model (module) +
+ + +
simso.core.Processor (module) +
+ + +
simso.core.results (module) +
+ + +
simso.core.Scheduler (module) +
+ + +
simso.core.Task (module) +
+ + +
simso.core.Timer (module) +
+ + +
simso.generator.task_generator (module) +
+ + +
simso.utils.PartitionedScheduler (module) +
+ + +
SporadicTask (class in simso.core.Task) +
+ + +
stack_file (TaskInfo attribute) +
+ + +
StaffordRandFixedSum() (in module simso.generator.task_generator) +
+ + +
start() (Timer method) +
+ + +
start_date (Job attribute) +
+ + +
stop() (Timer method) +
+ +
+ +

T

+ + + +
+ +
task (Job attribute) +
+ + +
Task() (in module simso.core.Task) +
+ + +
task_info_list (Configuration attribute) +
+ + +
task_list (Model attribute) +
+ +
+ +
TaskInfo (class in simso.core.Task) +
+ + +
TaskR (class in simso.core.results) +
+ + +
tasks_event() (Results method) +
+ + +
Timer (class in simso.core.Timer) +
+ +
+ +

W

+ + + +
+ +
wcet (GenericTask attribute) +
+ +
+ +
(Job attribute) +
+ +
+
+ +
worst_fit() (in module simso.utils.PartitionedScheduler) +
+ +
+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/index.html b/docs/html/index.html new file mode 100644 index 0000000..89638f3 --- /dev/null +++ b/docs/html/index.html @@ -0,0 +1,129 @@ + + + + + + + + SimSo documentation — SimSo documentation + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/html/introduction.html b/docs/html/introduction.html new file mode 100644 index 0000000..d2e0cda --- /dev/null +++ b/docs/html/introduction.html @@ -0,0 +1,180 @@ + + + + + + + + Introduction — SimSo documentation + + + + + + + + + + + + + + + +
+
+
+ +
+

Introduction¶

+
+

What is SimSo?¶

+

SimSo is a scheduling simulator for real-time multiprocessor architectures that takes into account some scheduling overheads (scheduling decisions, context switches) and the impact of caches through statistical models. Based on a Discrete-Event Simulator (SimPy), it allows quick simulations and a fast prototyping of scheduling policies using Python.

+

SimSo is an open source software, available under the CeCILL license, a GPL compatible license.

+
+
+

Download¶

+

You can find the last version of SimSo on the SimSo Website.

+
+
+

Installation¶

+

SimSo is available for the main platforms and so is its source code. The archive containing the source code is more often updated and should be used when possible.

+

In order to install SimSo from the souce code, the dependences must be installed first. Then, type “python setup.py install” to install SimSo.

+
+

Dependencies¶

+

When using SimSo from the sources, the following softwares and librairies are required:

+
+
    +
  • Python 2.7+
  • +
  • SimPy 2.3.1 (not compatible with SimPy 3)
  • +
  • NumPy 1.6+
  • +
  • PyQt4 4.9+
  • +
+
+

If you are using a binary, everything should be packed in the binary.

+
+
+
+

First step¶

+

SimSo is provided with a graphical user interface that aims to be very easy to use. This is a good way to develop and test a scheduler. See How to write a scheduling policy.

+

It is also possible to use SimSo as a library. This allows in particular to run simulations in text mode with a maximum of flexibility.

+
+
+

Available Schedulers¶

+

Currently, the following schedulers are available:

+

Uniprocessor schedulers

+
+
    +
  • Earliest Deadline First (EDF)
  • +
  • Rate Monotonic (RM)
  • +
  • Fixed Priority (FP)
  • +
  • Static-EDF (A DVFS EDF)
  • +
  • CC-EDF: Real-Time dynamic voltage scaling for low-power embedded operating systems by P. Pillai et al.
  • +
+
+
+
Uniprocessor schedulers adapted to multiprocessor
+
    +
  • Global-EDF
  • +
  • Global-RM
  • +
  • Earliest Deadline Zero Laxity (EDZL)
  • +
  • Least Laxity First (LLF)
  • +
  • Modified Least Laxity First (MLLF): A Modified Least-Laxity-First Scheduling Algorithm for Real-Time Tasks by S.-H. Oh and S.-M. Yang.
  • +
  • PriD: Real-time scheduling on multiprocessors by J., Baruah, S., & Funk, S.
  • +
  • EDF-US
  • +
  • G-FL: Fair lateness scheduling: Reducing maximum lateness in G-EDF-like scheduling by Erickson and Anderson.
  • +
+
+
Partitionned
+

Any uniprocessor scheduler using a partitionning algorithm. The following heuristics are provided:

+
    +
  • First-Fit and Decreasing-First-Fit
  • +
  • Next-Fit and Decreasing-Next-Fit
  • +
  • Best-Fit and Decreasing-Best-Fit
  • +
  • Worst-Fit and Decreasing-Worst-Fit
  • +
+
+
PFair
+
    +
  • Earliest Pseudo-Deadline First (EPDF)
  • +
  • PD2 and ER-PD2: Early-Release Fair Scheduling. In Proceedings of the Euromicro Conference on Real-Time Systems by J. H. Anderson et al.
  • +
+
+
DPFair
+
    +
  • LLREF: An Optimal Real-Time Scheduling Algorithm for Multiprocessors by Cho et al.
  • +
  • LRE-TL: An Optimal Multiprocessor Scheduling Algorithm for Sporadic Task Sets by S. Funk et al.
  • +
  • DP-WRAP: DP-FAIR: A Simple Model for Understanding Optimal Multiprocessor Scheduling by Levin et al.
  • +
  • BF: Multiple-resource periodic scheduling problem: how much fairness is necessary? by Zhu et al.
  • +
  • NVNLF: Work-Conversing Optimal Real-Time Scheduling on Multiprocessors by Funaoka et al.
  • +
+
+
Semi-partitionned
+
    +
  • EKG: Multiprocessor Scheduling with Few Preemptions by B. Andersson and E. Tovar.
  • +
  • EDHS: Semi-Partitioning Technique for Multiprocessor Real-Time Scheduling by Kato et al.
  • +
+
+
Other
+
    +
  • RUN: Optimal Multiprocessor Real-Time Scheduling via Reduction to Uniprocessor by Regnier et al.
  • +
  • U-EDF: an Unfair but Optimal Multiprocessor Scheduling Algorithm for Sporadic Tasks by Nelissen et al.
  • +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/licenses.html b/docs/html/licenses.html new file mode 100644 index 0000000..72b4e1a --- /dev/null +++ b/docs/html/licenses.html @@ -0,0 +1,132 @@ + + + + + + + + Licenses — SimSo documentation + + + + + + + + + + + + + + +
+
+
+ +
+

Licenses¶

+
+

SimSo¶

+

SimSo is distributed under the CeCILL license:

+
Copyright LAAS-CNRS (2012-2015)
+
+maxime.cheramy@laas.fr
+
+This software is a computer program whose purpose is to provide a
+simulator for Multiprocessor Real-Time Scheduling with Overheads.
+
+This software is governed by the CeCILL license under French law and
+abiding by the rules of distribution of free software.  You can  use,
+modify and/ or redistribute the software under the terms of the CeCILL
+license as circulated by CEA, CNRS and INRIA at the following URL
+"http://www.cecill.info".
+
+As a counterpart to the access to the source code and  rights to copy,
+modify and redistribute granted by the license, users are provided only
+with a limited warranty  and the software's author,  the holder of the
+economic rights,  and the successive licensors  have only  limited
+liability.
+
+In this respect, the user's attention is drawn to the risks associated
+with loading,  using,  modifying and/or developing or reproducing the
+software by the user in light of its specific status of free software,
+that may mean  that it is complicated to manipulate,  and  that  also
+therefore means  that it is reserved for developers  and  experienced
+professionals having in-depth computer knowledge. Users are therefore
+encouraged to load and test the software's suitability as regards their
+requirements in conditions enabling the security of their systems and/or
+data to be ensured and,  more generally, to use and operate it in the
+same conditions as regards security.
+
+The fact that you are presently reading this means that you have had
+knowledge of the CeCILL license and that you accept its terms.
+
+
+
+
+

Dependencies¶

+
+

SimPy¶

+

SimPy is an object-oriented, process-based discrete-event simulation language for Python. +SimPy 2 is released under the GNU Lesser GPL (LGPL) license.

+
+
+

NumPy¶

+

Numpy is licensed under the BSD license, enabling reuse with few restrictions.

+
+
+

PyQt¶

+

The Graphical User Interface of SimSo relies on the GPL version of PyQt. Qt itself is available +in GPL and LGPL versions.

+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/modules.html b/docs/html/modules.html new file mode 100644 index 0000000..762c74f --- /dev/null +++ b/docs/html/modules.html @@ -0,0 +1,1253 @@ + + + + + + + + Main modules — SimSo documentation + + + + + + + + + + + + + + + +
+
+
+ +
+

Main modules¶

+ +
+

simso.core module¶

+

The core module include all the classes needed for the simulation.

+
+

Scheduler¶

+
+
+class Scheduler(sim, scheduler_info, **kwargs)¶
+

The implementation of a scheduler is done by subclassing this abstract +class.

+

The scheduling events are modeled by method calls which take as arguments +the jobs and the processors.

+

The following methods should be redefined in order to interact with the +simulation:

+
+
    +
  • init() Called when the simulation is ready. The scheduler logic should be initialized here.
  • +
  • on_activate() Called upon a job activation.
  • +
  • on_terminated() Called when a job is terminated.
  • +
  • schedule() Take the scheduling decision. This method should not be called directly. A call to the resched method is required.
  • +
+
+

By default, the scheduler can only run on a single processor at the same +simulation time. It is also possible to override this behavior by +overriding the get_lock() and release_lock() methods.

+

Args:

+ +

Attributes:

+
    +
  • sim: Model instance. Useful to get current time with sim.now_ms() (in ms) or sim.now() (in cycles).
  • +
  • processors: List of processors handled by this scheduler.
  • +
  • task_list: List of tasks handled by this scheduler.
  • +
+

Methods:

+
+
+add_processor(cpu)¶
+

Add a processor to the list of processors handled by this scheduler.

+
+
Args:
+
+
+
+
+ +
+
+add_task(task)¶
+

Add a task to the list of tasks handled by this scheduler.

+
+
Args:
+
    +
  • task: The task to add.
  • +
+
+
+
+ +
+
+get_lock()¶
+

Implement a lock mechanism. Override it to remove the lock or change +its behavior.

+
+ +
+
+init()¶
+

This method is called when the system is ready to run. This method +should be used in lieu of the __init__ method. This method is +guaranteed to be called when the simulation starts, after the tasks +are instantiated

+
+ +
+
+on_activate(job)¶
+

This method is called upon a job activation.

+
+
Args:
+
    +
  • job: The activated job.
  • +
+
+
+
+ +
+
+on_terminated(job)¶
+

This method is called when a job finish (termination or abortion).

+
+
Args:
+
    +
  • job: The job that terminates .
  • +
+
+
+
+ +
+
+release_lock()¶
+

Release the lock. Goes in pair with get_lock().

+
+ +
+
+schedule(cpu)¶
+

The schedule method must be redefined by the simulated scheduler. +It takes as argument the cpu on which the scheduler runs.

+
+
Args:
+
    +
  • cpu: The processor on which the scheduler runs.
  • +
+
+
+

Returns a decision or a list of decisions. A decision is a couple +(job, cpu).

+
+ +
+ +
+
+class SchedulerInfo(name='', cls=None, overhead=0, overhead_activate=0, overhead_terminate=0, fields=None)¶
+

SchedulerInfo groups the data that characterize a Scheduler (such as the +scheduling overhead) and do the dynamic loading of the scheduler.

+
+
Args:
+
    +
  • name: Name of the scheduler.
  • +
  • cls: Class associated to this scheduler.
  • +
  • overhead: Overhead associated to a scheduling decision.
  • +
+
+
+

Methods:

+
+
+filename¶
+

Path of the scheduler (absolute or relative to simso’s working +directory).

+
+ +
+
+get_cls()¶
+

Get the class of this scheduler.

+
+ +
+
+instantiate(model)¶
+

Instantiate the Scheduler class.

+
+
Args:
+
    +
  • model: The Model object that is passed to the constructor.
  • +
+
+
+
+ +
+
+name¶
+

The name of the Scheduler (its relative path to the XML).

+
+ +
+
+set_name(filename, cur_dir=None)¶
+

Set the scheduler from a file.

+
+
Args:
+
    +
  • filename: relative path to the Python source containing the Scheduler.
  • +
  • cur_dir: current directory. Used to set the name relatively to the simulation file.
  • +
+
+
+
+ +
+ +
+
+

Task¶

+
+
+class ATask(sim, task_info)¶
+

Non-periodic Task process. Inherits from GenericTask. The job is +created by another task.

+

Args:

+
    +
  • sim: Model instance.
  • +
  • task_info: A TaskInfo representing the Task.
  • +
+ +++ + +
+
+ +
+
+class GenericTask(sim, task_info)¶
+

Abstract class for Tasks. ATask and PTask inherits from +this class.

+

These classes simulate the behavior of the simulated task. It controls the +release of the jobs and is able to abort the jobs that exceed their +deadline.

+

The majority of the task_info attributes are available through this class +too. A set of metrics such as the number of preemptions and migrations are +available for analysis.

+

Args:

+
    +
  • sim: Model instance.
  • +
  • task_info: A TaskInfo representing the Task.
  • +
+ +++ + +
+
+
+create_job(pred=None)¶
+

Create a new job from this task. This should probably not be used +directly by a scheduler.

+
+ +
+
+data¶
+

Extra data to characterize the task. Only used by the scheduler.

+
+ +
+
+deadline¶
+

Deadline in milliseconds.

+
+ +
+
+followed_by¶
+

Task that is activated by the end of a job from this task.

+
+ +
+
+identifier¶
+

Identifier of the task.

+
+ +
+
+jobs¶
+

List of the jobs.

+
+ +
+
+monitor¶
+

The monitor for this Task. Similar to a log mechanism (see Monitor in +SimPy doc).

+
+ +
+
+period¶
+

Period of the task.

+
+ +
+
+wcet¶
+

Worst-Case Execution Time in milliseconds.

+
+ +
+ +
+
+class PTask(sim, task_info)¶
+

Periodic Task process. Inherits from GenericTask. The jobs are +created periodically.

+

Args:

+
    +
  • sim: Model instance.
  • +
  • task_info: A TaskInfo representing the Task.
  • +
+ +++ + +
+
+ +
+
+class SporadicTask(sim, task_info)¶
+

Sporadic Task process. Inherits from GenericTask. The jobs are +created using a list of activation dates.

+

Args:

+
    +
  • sim: Model instance.
  • +
  • task_info: A TaskInfo representing the Task.
  • +
+ +++ + +
+
+ +
+
+Task(sim, task_info)¶
+

Task factory. Return and instantiate the correct class according to the +task_info.

+
+ +
+
+class TaskInfo(name, identifier, task_type, abort_on_miss, period, activation_date, n_instr, mix, stack_file, wcet, acet, et_stddev, deadline, base_cpi, followed_by, list_activation_dates, preemption_cost, data)¶
+

TaskInfo is mainly a container class grouping the data that characterize +a Task. A list of TaskInfo objects are passed to the Model so that +Task instances can be created.

+ +++ + +
+
+
+csdp¶
+

Accumulated Stack Distance Profile. Used by the cache models instead of +the Stack Distance Profile for optimization matters.

+
+ +
+
+set_stack_file(stack_file, cur_dir)¶
+

Set the stack distance profile.

+
+ +
+
+stack_file¶
+

Stack distance profile input file.

+
+ +
+ +
+
+

Job¶

+
+
+class Job(task, name, pred, monitor, etm, sim)¶
+

The Job class simulate the behavior of a real Job. This should only be +instantiated by a Task.

+
+
Args:
+
    +
  • task: The parent task.
  • +
  • name: The name for this job.
  • +
  • pred: If the task is not periodic, pred is the job that released this one.
  • +
  • monitor: A monitor is an object that log in time.
  • +
  • etm: The execution time model.
  • +
  • sim: Model instance.
  • +
+
+
+ +++ + +
+
+
+abort()¶
+

Abort this job. Warning, this is currently only used by the Task when +the job exceeds its deadline. It has not be tested from outside, such +as from the scheduler.

+
+ +
+
+aborted¶
+

True if the job has been aborted.

+ +++ + + + +
Return type:bool
+
+ +
+
+absolute_deadline¶
+

Absolute deadline in milliseconds for this job. This is the activation +date + the relative deadline.

+
+ +
+
+activation_date¶
+

Activation date in milliseconds for this job.

+
+ +
+
+actual_computation_time_cycles¶
+

Computation time as if the processor speed was 1.0 during the whole +execution.

+
+ +
+
+cpu¶
+

The processor on which the +job is attached. Equivalent to self.task.cpu.

+
+ +
+
+data¶
+

The extra data specified for the task. Equivalent to +self.task.data.

+
+ +
+
+deadline¶
+

Relative deadline in milliseconds. +Equivalent to self.task.deadline.

+
+ +
+
+end_date¶
+

Date (in ms) when this job finished its execution.

+
+ +
+
+exceeded_deadline¶
+

True if the end_date is greater than the deadline or if the job was +aborted.

+
+ +
+
+is_active()¶
+

Return True if the job is still active.

+
+ +
+
+is_running()¶
+

Return True if the job is currently running on a processor. +Equivalent to self.cpu.running == self.

+ +++ + + + +
Return type:bool
+
+ +
+
+period¶
+

Period in milliseconds. Equivalent to self.task.period.

+
+ +
+
+ret¶
+

Remaining execution time.

+
+ +
+
+start_date¶
+

Date (in ms) when this job started executing +(different than the activation).

+
+ +
+
+task¶
+

The task for this job.

+
+ +
+
+wcet¶
+

Worst-Case Execution Time in milliseconds. +Equivalent to self.task.wcet.

+
+ +
+ +
+
+

Model¶

+
+
+class Model(configuration, callback=None)¶
+

Main class for the simulation. It instantiate the various components +required by the simulation and run it.

+
+
Args:
+
    +
  • callback: A callback can be specified. This function will be called to report the advance of the simulation (useful for a progression bar).
  • +
  • configuration: The configuration of the simulation.
  • +
+
+
+

Methods:

+
+
+cycles_per_ms¶
+

Number of cycles per milliseconds. A cycle is the internal unit used +by SimSo. However, the tasks are defined using milliseconds.

+
+ +
+
+duration¶
+

Duration of the simulation.

+
+ +
+
+etm¶
+

Execution Time Model

+
+ +
+
+logs¶
+

All the logs from the Logger.

+
+ +
+
+processors¶
+

List of all the processors.

+
+ +
+
+run_model()¶
+

Execute the simulation.

+
+ +
+
+task_list¶
+

List of all the tasks.

+
+ +
+ +
+
+

Processor¶

+
+
+class Processor(model, proc_info)¶
+

A processor is responsible of deciding whether the simulated processor +should execute a job or execute the scheduler. There is one instance of +Processor per simulated processor. Those are responsible to call the +scheduler methods.

+

When a scheduler needs to take a scheduling decision, it must invoke the +resched() method. This is typically done in the on_activate, on_terminated or in a timer handler.

+
+
+internal_id¶
+

A unique, internal, id.

+
+ +
+
+is_running()¶
+

Return True if a job is currently running on that processor.

+
+ +
+
+resched()¶
+

Add a resched event to the list of events to handle.

+
+ +
+
+running¶
+

The job currently running on that processor. None if no job is +currently running on the processor.

+
+ +
+ +
+
+

Timer¶

+
+
+class Timer(sim, function, args, delay, one_shot=True, prior=False, cpu=None, in_ms=True, overhead=0)¶
+

Allow to declare a timer. A timer is a mechanism that allows to call a +function after a certain amount of time, periodically or single shot.

+

A Timer can be used with or without specifying a processor. If a processor +is specified, when the timer fire, if a job was running on the processor, +it is temporarly interrupted. This is more realistic, even if for the +moment there is no overhead associated to this action. A scheduler using a +timer should define on which processor the callback will execute.

+

The delay is expressed in milliseconds by default but it can also be given +in cycles.

+
+
Args:
+
    +
  • sim: The model object.
  • +
  • function: Callback function, called when the delay expires.
  • +
  • args: Arguments passed to the callback function.
  • +
  • delay: Time to wait before calling the function.
  • +
  • one_shot: True if the timer should execute only once.
  • +
  • prior: If true, for the same date, the simulation should start by handling the timer (should probably not be True).
  • +
  • cpu: On which processor the function is virtually executing.
  • +
  • in_ms: True if the delay is expressed in millisecond. In cycles otherwise.
  • +
+
+
+

Methods:

+
+
+start()¶
+

Start the timer.

+
+ +
+
+stop()¶
+

Stop the timer.

+
+ +
+ +
+
+

Logger¶

+
+
+class Logger(sim)¶
+

Simple logger. Every message is logged with its date.

+

Args: +- sim: The model object.

+
+
+log(msg, kernel=False)¶
+

Log the message msg.

+
+
Args:
+
    +
  • msg: The message to log.
  • +
  • kernel: Allows to make a distinction between a message from the core of the simulation or from the scheduler.
  • +
+
+
+
+ +
+
+logs¶
+

The logs, a SimPy Monitor object.

+
+ +
+ +
+
+

results¶

+
+
+class JobR(date, job)¶
+

Add a set of metrics to a job. Such metrics include: preemption count, +migration count, response time, etc.

+
+ +
+
+class ProcessorR¶
+

Add information about a processor such as the number of CxtSave and +CxtLoad and their total overhead.

+
+ +
+
+class Results(model)¶
+

This class embeds and analyzes all the results from the simulation. +This allows to retrieve the usual metrics.

+
+
The Results instance object contains the following attributes:
+
    +
  • tasks: a dictionary of TaskR where the key is the original Task.
  • +
  • scheduler: a SchedulerR instance.
  • +
  • processors: a dictionary of ProcessorR where the key is the original Processor.
  • +
+
+
+

.

+
+
+calc_load()¶
+

Yield a tuple (proc, load, overhead) for each processor.

+
+ +
+
+get_observation_window()¶
+

Get the observation window.

+
+ +
+
+observation_window¶
+

Get the observation window.

+
+ +
+
+set_observation_window(window)¶
+

Set the observation window. The events that occurs outside of the +observation window are discarded.

+
+ +
+
+tasks_event()¶
+

Generator of the tasks events sorted by their date.

+
+ +
+ +
+
+class SchedulerR¶
+

Add information about the scheduler such as the number of scheduling +events and their total overhead.

+
+ +
+
+class TaskR(task, delta_preemption=100)¶
+

Add a set of metrics to a task. These metrics include: task_migrations, +abortion count, etc.

+

The attribute jobs contains a list of JobR, sorted by activation date.

+
+ +
+
+
+

simso.configuration module¶

+
+

Configuration¶

+
+
+class Configuration(filename=None)¶
+

The configuration class store all the details about a system. An instance +of this class will be passed to the constructor of the +Model class.

+

Args: +- filename A file can be used to initialize the configuration.

+
+
+add_processor(name, identifier, cs_overhead=0, cl_overhead=0, migration_overhead=0, speed=1.0)¶
+

Helper method to create a ProcInfo and add it to the list of +processors.

+
+ +
+
+add_task(name, identifier, task_type='Periodic', abort_on_miss=False, period=10, activation_date=0, n_instr=0, mix=0.5, stack_file='', wcet=0, acet=0, et_stddev=0, deadline=10, base_cpi=1.0, followed_by=None, list_activation_dates=[], preemption_cost=0, data=None)¶
+

Helper method to create a TaskInfo and add it to the list of tasks.

+
+ +
+
+check_all()¶
+

Check the correctness of the configuration (without simulating it).

+
+ +
+
+get_hyperperiod()¶
+

Compute and return the hyperperiod of the tasks.

+
+ +
+
+proc_info_list¶
+

List of processors (ProcInfo objects).

+
+ +
+
+save(simulation_file=None)¶
+

Save the current configuration in a file. If no file is given as +argument, the previous file used to write or read the configuration is +used again.

+
+ +
+
+scheduler_info¶
+

SchedulerInfo object.

+
+ +
+
+task_info_list¶
+

List of tasks (TaskInfo objects).

+
+ +
+ +
+
+
+

simso.generator module¶

+

Tools for generating task sets.

+
+
+StaffordRandFixedSum(n, u, nsets)¶
+

Copyright 2010 Paul Emberson, Roger Stafford, Robert Davis. +All rights reserved.

+

Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met:

+
    +
  1. +
    Redistributions of source code must retain the above copyright notice,
    +

    this list of conditions and the following disclaimer.

    +
    +
    +
  2. +
  3. +
    Redistributions in binary form must reproduce the above copyright notice,
    +

    this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution.

    +
    +
    +
  4. +
+

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS’’ AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+

The views and conclusions contained in the software and documentation are +those of the authors and should not be interpreted as representing official +policies, either expressed or implied, of Paul Emberson, Roger Stafford or +Robert Davis.

+

Includes Python implementation of Roger Stafford’s randfixedsum implementation +http://www.mathworks.com/matlabcentral/fileexchange/9700 +Adapted specifically for the purpose of taskset generation with fixed +total utilisation value

+

Please contact paule@rapitasystems.com or robdavis@cs.york.ac.uk if you have +any questions regarding this software.

+
+ +
+
+gen_kato_utilizations(nsets, umin, umax, target_util)¶
+

Kato et al. tasksets generator.

+
+
Args:
+
    +
  • nsets: Number of tasksets to generate.
  • +
  • umin: Minimum task utilization.
  • +
  • umax: Maximum task utilization.
  • +
  • target_util:
  • +
+
+
+
+ +
+
+gen_periods_discrete(n, nsets, periods)¶
+

Generate a matrix of (nsets x n) random periods chosen randomly in the +list of periods.

+
+
Args:
+
    +
  • n: The number of tasks in a task set.
  • +
  • nsets: Number of sets to generate.
  • +
  • periods: A list of available periods.
  • +
+
+
+
+ +
+
+gen_periods_loguniform(n, nsets, min_, max_, round_to_int=False)¶
+

Generate a list of nsets sets containing each n random periods using a +loguniform distribution.

+
+
Args:
+
    +
  • n: The number of tasks in a task set.
  • +
  • nsets: Number of sets to generate.
  • +
  • min_: Period min.
  • +
  • max_: Period max.
  • +
+
+
+
+ +
+
+gen_periods_uniform(n, nsets, min_, max_, round_to_int=False)¶
+

Generate a list of nsets sets containing each n random periods using a +uniform distribution.

+
+
Args:
+
    +
  • n: The number of tasks in a task set.
  • +
  • nsets: Number of sets to generate.
  • +
  • min_: Period min.
  • +
  • max_: Period max.
  • +
+
+
+
+ +
+
+gen_randfixedsum(nsets, u, n)¶
+

Stafford’s RandFixedSum algorithm implementated in Python.

+

Based on the Python implementation given by Paul Emberson, Roger Stafford, +and Robert Davis. Available under the Simplified BSD License.

+
+
Args:
+
    +
  • n: The number of tasks in a task set.
  • +
  • u: Total utilization of the task set.
  • +
  • nsets: Number of sets to generate.
  • +
+
+
+
+ +
+
+gen_ripoll(nsets, compute, deadline, period, target_util)¶
+

Ripoll et al. tasksets generator.

+
+
Args:
+
    +
  • nsets: Number of tasksets to generate.
  • +
  • compute: Maximum computation time of a task.
  • +
  • deadline: Maximum slack time.
  • +
  • period: Maximum delay after the deadline.
  • +
  • target_util: Total utilization to reach.
  • +
+
+
+
+ +
+
+gen_tasksets(utilizations, periods)¶
+

Take a list of task utilization sets and a list of task period sets and +return a list of couples (c, p) sets. The computation times are truncated +at a precision of 10^-10 to avoid floating point precision errors.

+
+
Args:
+
    +
  • utilization: The list of task utilization sets. For example:

    +
    [[0.3, 0.4, 0.8], [0.1, 0.9, 0.5]]
    +
    +
    +
  • +
  • periods: The list of task period sets. For examples:

    +
    [[100, 50, 1000], [200, 500, 10]]
    +
    +
    +
  • +
+
+
Returns:
+

For the above example, it returns:

+
[[(30.0, 100), (20.0, 50), (800.0, 1000)],
+ [(20.0, 200), (450.0, 500), (5.0, 10)]]
+
+
+
+
+
+ +
+
+gen_uunifastdiscard(nsets, u, n)¶
+

The UUniFast algorithm was proposed by Bini for generating task +utilizations on uniprocessor architectures.

+

The UUniFast-Discard algorithm extends it to multiprocessor by +discarding task sets containing any utilization that exceeds 1.

+

This algorithm is easy and widely used. However, it suffers from very +long computation times when n is close to u. Stafford’s algorithm is +faster.

+
+
Args:
+
    +
  • n: The number of tasks in a task set.
  • +
  • u: Total utilization of the task set.
  • +
  • nsets: Number of sets to generate.
  • +
+
+
+

Returns nsets of n task utilizations.

+
+ +
+
+

simso.utils module¶

+
+

PartitionedScheduler¶

+
+
+class PartitionedScheduler(sim, scheduler_info, **kwargs)¶
+

The PartitionedScheduler class provide facilities to create a new +Partitioned Scheduler. Only the packing phase is not done and should +be overriden.

+

Args:

+
    +
  • sim: Model instance.
  • +
  • scheduler_info: A SchedulerInfo representing the scheduler.
  • +
+

Attributes:

+
    +
  • sim: Model instance. Useful to get current time with sim.now_ms() (in ms) or sim.now() (in cycles).
  • +
  • processors: List of processors handled by this scheduler.
  • +
  • task_list: List of tasks handled by this scheduler.
  • +
+

Methods:

+
+
+init(scheduler_info, packer=None)¶
+
+
Args:
+
    +
  • scheduler_info: A SchedulerInfo object. One scheduler from this SchedulerInfo will be instantiated for each processor.
  • +
+
+
+
+ +
+ +
+
+best_fit(scheduler, task_list=None)¶
+

Best-Fit heuristic. Put the tasks somewhere it fits but with the least +spare place.

+
+ +
+
+decreasing_best_fit(scheduler)¶
+

Best-Fit with tasks inversely sorted by their u_i.

+
+ +
+
+decreasing_first_fit(scheduler)¶
+

First-Fit with tasks inversely sorted by their u_i.

+
+ +
+
+decreasing_next_fit(scheduler)¶
+

Next-Fit with tasks inversely sorted by their u_i.

+
+ +
+
+decreasing_worst_fit(scheduler)¶
+

Worst-Fit with tasks inversely sorted by their u_i.

+
+ +
+
+first_fit(scheduler, task_list=None)¶
+

First-Fit heuristic. Put each task on the first processor with enough +space.

+
+ +
+
+next_fit(scheduler, task_list=None)¶
+

Next-Fit heuristic. Put each task on the next processor with enough space.

+
+ +
+
+worst_fit(scheduler, task_list=None)¶
+

Worst-Fit heuristic. Put the tasks somewhere it fits with the largest +spare place.

+
+ +
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/objects.inv b/docs/html/objects.inv new file mode 100644 index 0000000..d38ba25 Binary files /dev/null and b/docs/html/objects.inv differ diff --git a/docs/html/py-modindex.html b/docs/html/py-modindex.html new file mode 100644 index 0000000..92030e0 --- /dev/null +++ b/docs/html/py-modindex.html @@ -0,0 +1,153 @@ + + + + + + + + Python Module Index — SimSo documentation + + + + + + + + + + + + + + + + +
+
+
+ + +

Python Module Index

+ +
+ s +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ s
+ simso +
    + simso.configuration +
    + simso.configuration.Configuration +
    + simso.core +
    + simso.core.Job +
    + simso.core.Logger +
    + simso.core.Model +
    + simso.core.Processor +
    + simso.core.results +
    + simso.core.Scheduler +
    + simso.core.Task +
    + simso.core.Timer +
    + simso.generator.task_generator +
    + simso.utils.PartitionedScheduler +
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/search.html b/docs/html/search.html new file mode 100644 index 0000000..1b25f92 --- /dev/null +++ b/docs/html/search.html @@ -0,0 +1,97 @@ + + + + + + + + Search — SimSo documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js new file mode 100644 index 0000000..923a42e --- /dev/null +++ b/docs/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({envversion:42,terms:{all:[0,5,4],code:[2,5,6,3,4],edf:[],reproduc:[6,4],scratch:[],edh:2,global:2,affect_task_to_processor:5,is_act:4,worst_fit:4,subclass:[5,4],kato:[2,4],tovar:2,follow:[0,2,6,5,4],hyperperiod:4,llf:2,abid:6,millisecond:4,whose:6,decid:4,procur:4,depend:[],specif:[6,4],send:[5,3],init:[0,5,4],program:6,decis:[2,4],under:[2,6,4],set_stack_fil:4,aris:4,stafford:4,neglig:4,adapt:[2,4],merchant:4,sourc:[2,5,6,3,4],lre:2,risk:6,fals:[5,4],account:2,util:[],worst:[2,4],whether:4,govern:6,veri:[0,2,5,4],appar:[],four:5,computation_tim:0,observation_window:4,proc:4,button:[],list:[5,3,4],factori:4,"try":0,schedulerinfo:[5,4],progress:4,team:[],quick:2,evt:0,pleas:[3,4],impli:4,direct:4,rate:2,pass:[0,5,4],download:[],click:[],append:5,compat:[2,3],index:1,what:[],stack_fil:4,sum:0,abl:4,invok:[5,4],current:[2,4],experiment:0,encourag:6,"new":[0,3,4],abort_on_miss:4,method:[0,5,4],laxiti:2,xml:[3,4],full:5,utilis:4,deriv:[],french:6,dpfair:2,met:4,lgpl:6,modif:4,free:[5,6],ubuntu:3,path:4,modifi:[2,6],interpret:4,wait:4,search:1,convers:2,overhead_termin:4,ekg:2,precis:4,prior:4,amount:4,overriden:[5,4],action:4,implement:[],uunifast:4,overrid:4,taskr:[0,4],via:2,extra:4,appli:5,modul:[],put:4,heurist:[2,4],task_typ:4,instal:[],total:4,unit:4,from:[],proceed:2,distinct:4,pred:4,doubl:[],cho:2,websit:2,few:[0,2,6],handler:4,overhead:[2,5,6,4],taken:[],prev:0,type:[2,4],more:[],sort:4,list_activation_d:4,notic:4,start_dat:4,warn:4,exce:4,prototyp:2,particular:[2,4],decreasing_next_fit:4,cach:[2,4],must:[2,5,3,4],none:[0,5,4],graphic:[2,6,3],retriev:4,left:[],setup:2,uniqu:4,histori:0,remain:4,minimum:4,purpos:[6,4],exceeded_deadlin:4,def:[0,5],control:[3,4],give:5,process:[6,4],lock:4,add_processor:[0,4],accept:6,had:6,abort:4,umin:4,everi:[0,4],occur:[5,4],delai:4,cours:0,multipl:2,ripol:4,anoth:4,write:[],pair:4,cur_dir:[0,4],instead:4,voltag:2,config:0,sim:4,updat:[2,5],product:[],resourc:2,still:[5,4],max:4,decreasing_first_fit:4,after:4,laa:6,befor:[0,5,4],mac:3,attent:6,mai:6,end:4,law:6,associ:[6,4],alloc:5,bini:4,third:[],is_run:4,light:6,secur:6,explicit:[],caus:4,callback:4,"switch":[0,2],allow:[2,3,4],mechan:4,lambda:5,order:[0,2,3,4,5],gen_periods_discret:4,help:5,softwar:[2,6,4],offici:4,gen_uunifastdiscard:4,cxtsave:4,through:[0,2,5,4],is_period:[],affect:5,uniprocessor:[],suffer:4,mainli:4,dynam:[2,4],busi:4,group:[0,4],obviou:[],fit:[2,5,4],chosen:[5,4],fix:[2,4],et_stddev:4,late:2,platform:2,matlabcentr:4,main:[],non:4,good:[2,5,4],"return":[5,4],greater:4,cea:6,python:[2,5,6,3,4],epdf:2,inria:6,simpi:[],interrupt:4,now:4,nor:[],introduct:[],choic:3,term:6,somewher:4,name:[0,5,4],delta_preempt:4,edit:[],simpl:[2,4],easili:3,mode:[],each:[0,5,3,4],fulli:3,complet:[],truncat:4,mean:6,monoton:2,ensur:6,contributor:4,redistribut:[6,4],finish:[5,4],"static":2,p_edf:5,our:5,happen:0,reduct:2,special:4,out:4,fileexchang:4,matrix:4,space:[5,4],profit:4,miss:5,robert:4,access:[0,6],ret:4,profil:4,uunifastdiscard:[],suitabl:6,rel:4,print:[0,5],correct:[0,4],statist:2,prioriti:[2,5],advanc:4,migrat:4,manipul:6,given:4,argv:0,reason:5,base:[2,6,4],theori:4,dictionari:4,releas:[2,6,4],earliest:[2,5],could:[0,5,3],llref:2,counterpart:6,thing:5,place:4,outsid:4,retain:4,interact:4,end_dat:4,first:[],origin:4,endors:[],major:4,simulation_fil:4,internal_id:4,onc:4,csdp:4,number:[0,4],proc_info:4,restrict:6,date:[5,3,4],task_info:4,done:[0,5,3,4],messag:[5,4],owner:[],round_to_int:4,open:2,gpl:[2,6],differ:[0,4],script:[],data:[6,4],licens:[],anderson:2,least:[2,5,4],stack:4,attach:4,atask:4,too:4,termin:4,white:[],"final":0,store:4,schema:3,juli:3,copi:6,specifi:[3,4],pyqt4:2,mathwork:4,cxtload:4,exactli:0,holder:[6,4],than:4,mllf:2,wide:[0,4],erickson:2,slack:4,provid:[0,2,3,4,5,6],andersson:2,second:5,structur:5,project:[],matter:4,reus:6,redifin:5,str:0,deadlin:[0,2,5,4],randomli:4,"function":[3,4],comput:[0,6,4],max_:4,robdavi:4,arg:[0,4],argument:[0,4],packag:3,expir:4,have:[0,6,4],reserv:[6,4],need:[5,4],cecil:[2,6],loguniform:4,get_cl:4,techniqu:2,zero:2,inform:[0,4],self:[5,4],euromicro:2,now_m:4,contact:4,also:[0,2,3,4,5,6],discret:[2,6],take:[2,4],advis:4,min_:4,tasks_ev:4,tool:4,singl:[5,4],even:4,sure:5,distribut:[6,4],n_instr:4,though:3,object:[0,6,4],reach:4,decreasing_best_fit:4,phase:4,followed_bi:4,everyth:2,schedulerr:4,url:6,doc:4,yang:2,resch:[5,4],usual:[5,4],fact:6,run_model:[0,4],preemption_count:0,gen_periods_uniform:4,shot:4,show:5,text:2,random:4,prid:2,directli:[0,5,4],permiss:[],find:[2,5],impact:2,absolut:[5,4],onli:[5,6,4],just:[],facil:4,iep:[],explain:5,next_fit:4,activ:[5,3,4],written:3,should:[2,5,4],configur:[],version:[2,6],analyz:4,info:6,variou:4,get:[0,4],express:4,stop:4,aim:2,ptask:4,procev:0,report:4,on_termin:[5,4],event:[0,2,6,5,4],neither:[],requir:[0,2,6,4],first_fit:4,overhead_activ:4,add_task:[0,4],bar:4,enabl:6,emb:4,yield:4,migration_overhead:4,roger:4,decreasing_worst_fit:4,partit:[],contain:[2,4],where:[0,5,4],remov:[5,4],view:4,user:[2,6,3],kernel:4,fair:2,packer:[5,4],knowledg:6,see:[2,4],mandatori:5,respons:[5,4],fail:5,close:4,best:[2,4],subject:[],statu:6,flexibl:[0,2],preemption_cost:4,kei:[5,4],someth:0,pd2:2,cs_overhead:4,enough:4,between:4,"import":[0,5],ready_list:5,approach:5,best_fit:4,attribut:[0,4],accord:4,pretti:5,jobr:[0,4],extend:4,nelissen:2,cycl:4,job:[],spare:4,here:[5,4],nset:4,problem:2,monitor:[0,4],disclaim:4,last:[2,5],cycles_per_m:[0,4],mono:5,howev:[3,4],incident:4,contract:4,len:[0,5],polici:[],instanc:4,present:6,context:[0,2],logic:[5,4],get_hyperperiod:4,whole:4,etm:4,load:[],among:5,cxt:0,author:[6,4],point:[5,4],instanti:[0,5,4],priorit:5,pfair:2,scheduler_info:[0,4],period:[0,2,5,4],exemplari:4,linux:3,respect:[5,6],damag:4,liabil:[6,4],coupl:4,tort:4,numpi:[],window:[3,4],been:4,compon:4,accumul:4,much:2,valu:4,basic:0,calc_load:4,gen_kato_util:4,"abstract":4,partial:0,on_activ:[5,4],field:4,emberson:4,fire:4,consequenti:4,ani:[2,5,4],understand:2,input:4,those:4,real:[2,6,4],"case":4,ident:5,look:5,gnu:6,servic:4,durat:[0,4],lesser:6,defin:[],"while":5,abov:4,error:4,edzl:2,observ:4,pack:[],earli:2,helper:4,readi:[5,4],metric:[0,4],therefor:6,them:5,nvnlf:2,kwarg:4,gen_randfixedsum:4,"__init__":4,scienc:[],parent:4,develop:[2,5,6],thei:5,grant:6,parti:[],make:4,econom:6,same:[0,6,4],check:[0,4],binari:[2,4],tutori:[0,5],funk:2,largest:4,probabl:4,taskinfo:4,edf_mono:5,archiv:2,closest:5,optim:[2,4],target_util:4,permit:4,upon:4,moment:4,rais:0,initi:[],extern:3,typic:4,funaoka:2,redefin:4,kept:5,equival:4,com:4,sporadictask:4,min:[5,4],itself:6,inherit:[5,4],without:[0,4],exampl:[],thi:[0,2,3,4,5,6],model:[],propos:4,twent:[],distanc:4,identifi:[0,4],fast:2,execut:[0,5,3,4],unfair:2,paul:4,speed:[3,4],yet:5,languag:6,york:4,gen_periods_loguniform:4,easi:[2,5,4],mix:4,baruah:2,character:4,except:0,add:[0,4],other:[2,4],els:[0,5],save:4,task_list:[0,5,4],bin:5,cherami:6,which:[5,4],minimalist:5,read:[0,6,4],arriv:3,know:0,librairi:2,circul:6,characterist:[0,3],get_observation_window:4,like:[2,5],loss:4,success:6,semi:2,arbitrari:5,generictask:4,manual:0,pillai:2,necessari:[2,5],either:4,xsd:3,page:1,actual_computation_time_cycl:4,www:[6,4],right:[6,4],often:2,simplifi:4,in_m:4,some:[0,2],maxim:6,intern:4,wcet:[0,5,4],guarante:[5,4],indirect:4,librari:[0,2,3],u_i:4,scale:2,cnr:6,remind:5,avoid:4,shall:4,per:[0,4],substitut:4,select:5,condit:[6,4],complic:6,core:[],set_nam:[0,4],previou:[0,4],run:[0,2,5,4],randfixedsum:4,check_al:[0,4],processorr:4,confer:2,step:[],nutshel:5,promot:[],taskset:4,drawn:6,about:4,materi:4,http:[6,4],simul:[],includ:4,constructor:[0,5,4],discard:4,univers:[],base_cpi:4,cl_overhead:4,"float":[5,4],profession:6,automat:0,invers:4,warranti:[6,4],bsd:[6,4],one_shot:4,empti:0,liabl:4,lieu:4,wrap:2,chang:[0,4],next:[2,4],your:3,preemption:[0,2,4],log:[0,4],wai:[2,5,4],"long":4,"class":[0,5,4],avail:[],start:[5,4],reli:6,interfac:[2,6,3],editor:[],call:[5,4],strict:4,etc:[0,4],analysi:[0,4],form:4,tupl:4,regard:[6,4],proc_info_list:4,msg:4,multiprocessor:[2,5,6,4],low:2,gen_ripol:4,eas:5,highest:5,"true":[5,4],conclus:4,count:[0,4],zhu:2,consist:5,possibl:[0,2,3,4,5],"default":4,levin:2,realist:4,maximum:[0,2,4],release_lock:4,limit:[6,4],otherwis:[3,4],embed:2,similar:4,dvf:2,procinfo:4,creat:[],certain:4,dure:4,filenam:4,repres:4,incomplet:3,decreas:2,file:[],behavior:4,umax:4,ship:3,simplest:5,acet:4,again:4,when:[0,2,5,4],detail:[],virtual:4,orient:6,power:2,gen_taskset:4,create_job:4,declar:4,bool:4,copyright:[6,4],task_info_list:4,test:[2,6,4],regnier:2,davi:4,staffordrandfixedsum:4,task_migr:4,architectur:[2,5,4],souc:2,absolute_deadlin:[5,4],get_lock:4,debian:3,reduc:2,set_observation_window:4,experienc:6,faster:4,algorithm:[2,5,4],directori:4,temporarli:4,pseudo:2,indirectli:5,rule:6,goe:4,depth:6,activation_d:[0,4],time:[0,2,6,3,4],rapitasystem:4,licensor:6,cpu:[0,5,4],oop:5},objtypes:{"0":"py:module","1":"py:attribute","2":"py:method","3":"py:function","4":"py:class"},objnames:{"0":["py","module","Python module"],"1":["py","attribute","Python attribute"],"2":["py","method","Python method"],"3":["py","function","Python function"],"4":["py","class","Python class"]},filenames:["text_mode","index","introduction","faq","modules","write_scheduler","licenses"],titles:["Using SimSo in script mode","SimSo documentation","Introduction","Frequently Asked Questions","Main modules","How to write a scheduling policy","Licenses"],objects:{"simso.utils":{PartitionedScheduler:[4,0,0,"-"]},"simso.core.Scheduler.SchedulerInfo":{get_cls:[4,2,1,""],set_name:[4,2,1,""],instantiate:[4,2,1,""],name:[4,1,1,""],filename:[4,1,1,""]},"simso.core.Model.Model":{run_model:[4,2,1,""],logs:[4,1,1,""],cycles_per_ms:[4,1,1,""],task_list:[4,1,1,""],duration:[4,1,1,""],etm:[4,1,1,""],processors:[4,1,1,""]},"simso.configuration":{Configuration:[4,0,0,"-"]},"simso.generator.task_generator":{gen_periods_uniform:[4,3,1,""],gen_periods_loguniform:[4,3,1,""],gen_uunifastdiscard:[4,3,1,""],gen_kato_utilizations:[4,3,1,""],gen_tasksets:[4,3,1,""],gen_periods_discrete:[4,3,1,""],gen_randfixedsum:[4,3,1,""],gen_ripoll:[4,3,1,""],StaffordRandFixedSum:[4,3,1,""]},"simso.core.Task.GenericTask":{jobs:[4,1,1,""],monitor:[4,1,1,""],followed_by:[4,1,1,""],period:[4,1,1,""],create_job:[4,2,1,""],deadline:[4,1,1,""],wcet:[4,1,1,""],identifier:[4,1,1,""],data:[4,1,1,""]},"simso.core.Task.TaskInfo":{csdp:[4,1,1,""],stack_file:[4,1,1,""],set_stack_file:[4,2,1,""]},"simso.core.Timer.Timer":{start:[4,2,1,""],stop:[4,2,1,""]},"simso.generator":{task_generator:[4,0,0,"-"]},"simso.configuration.Configuration.Configuration":{save:[4,2,1,""],add_task:[4,2,1,""],check_all:[4,2,1,""],scheduler_info:[4,1,1,""],add_processor:[4,2,1,""],proc_info_list:[4,1,1,""],task_info_list:[4,1,1,""],get_hyperperiod:[4,2,1,""]},"simso.core.Logger":{Logger:[4,4,1,""]},"simso.utils.PartitionedScheduler.PartitionedScheduler":{init:[4,2,1,""]},"simso.core.results.Results":{set_observation_window:[4,2,1,""],tasks_event:[4,2,1,""],calc_load:[4,2,1,""],get_observation_window:[4,2,1,""],observation_window:[4,1,1,""]},"simso.core.Scheduler.Scheduler":{add_task:[4,2,1,""],schedule:[4,2,1,""],on_terminated:[4,2,1,""],add_processor:[4,2,1,""],init:[4,2,1,""],release_lock:[4,2,1,""],on_activate:[4,2,1,""],get_lock:[4,2,1,""]},"simso.core.Scheduler":{SchedulerInfo:[4,4,1,""],Scheduler:[4,4,1,""]},"simso.core.Processor.Processor":{running:[4,1,1,""],resched:[4,2,1,""],is_running:[4,2,1,""],internal_id:[4,1,1,""]},"simso.core.Job.Job":{task:[4,1,1,""],end_date:[4,1,1,""],is_running:[4,2,1,""],is_active:[4,2,1,""],period:[4,1,1,""],ret:[4,1,1,""],exceeded_deadline:[4,1,1,""],abort:[4,2,1,""],deadline:[4,1,1,""],start_date:[4,1,1,""],aborted:[4,1,1,""],activation_date:[4,1,1,""],absolute_deadline:[4,1,1,""],data:[4,1,1,""],cpu:[4,1,1,""],actual_computation_time_cycles:[4,1,1,""],wcet:[4,1,1,""]},"simso.core.Logger.Logger":{logs:[4,1,1,""],log:[4,2,1,""]},"simso.core.Job":{Job:[4,4,1,""]},"simso.core.Processor":{Processor:[4,4,1,""]},"simso.core":{Task:[4,0,0,"-"],results:[4,0,0,"-"],Timer:[4,0,0,"-"],Processor:[4,0,0,"-"],Job:[4,0,0,"-"],Scheduler:[4,0,0,"-"],Model:[4,0,0,"-"],Logger:[4,0,0,"-"]},"simso.configuration.Configuration":{Configuration:[4,4,1,""]},"simso.utils.PartitionedScheduler":{PartitionedScheduler:[4,4,1,""],decreasing_first_fit:[4,3,1,""],next_fit:[4,3,1,""],decreasing_best_fit:[4,3,1,""],worst_fit:[4,3,1,""],decreasing_worst_fit:[4,3,1,""],decreasing_next_fit:[4,3,1,""],best_fit:[4,3,1,""],first_fit:[4,3,1,""]},simso:{core:[4,0,0,"-"],configuration:[4,0,0,"-"]},"simso.core.Task":{Task:[4,3,1,""],GenericTask:[4,4,1,""],SporadicTask:[4,4,1,""],PTask:[4,4,1,""],ATask:[4,4,1,""],TaskInfo:[4,4,1,""]},"simso.core.Model":{Model:[4,4,1,""]},"simso.core.results":{TaskR:[4,4,1,""],Results:[4,4,1,""],ProcessorR:[4,4,1,""],SchedulerR:[4,4,1,""],JobR:[4,4,1,""]},"simso.core.Timer":{Timer:[4,4,1,""]}},titleterms:{oper:3,load:0,set:[],edf:5,skeleton:5,creat:0,can:3,schedul:[2,5,4],modul:4,creation:5,indic:1,system:3,sporad:3,result:4,file:[0,5],tabl:[0,1,5,4],download:2,instal:2,doe:3,partitionedschedul:4,avail:2,what:2,processor:[3,4],depend:[2,6],script:0,polici:5,parti:[],support:3,configur:[0,4],question:3,detail:0,pyqt:6,uniform:3,content:[0,5,4],how:5,exampl:[0,5],explan:5,scratch:0,simpi:6,you:3,main:4,complet:5,core:4,handl:3,gener:[3,4],first:[0,2],initi:5,model:[0,4],util:4,step:2,numpi:6,ask:3,from:0,heterogen:3,logger:4,introduct:2,task:[3,4],simso:[0,1,2,3,4,6],third:[],document:1,uniprocessor:5,work:3,job:4,timer:4,simul:0,defin:5,codeeditor:[],write:5,mode:0,partit:5,own:3,implement:5,more:0,licens:6,frequent:3,pack:5}}) \ No newline at end of file diff --git a/docs/html/text_mode.html b/docs/html/text_mode.html new file mode 100644 index 0000000..a7f232a --- /dev/null +++ b/docs/html/text_mode.html @@ -0,0 +1,225 @@ + + + + + + + + Using SimSo in script mode — SimSo documentation + + + + + + + + + + + + + + + +
+
+
+ +
+

Using SimSo in script mode¶

+

SimSo can be used as a library in order to automatize wide experimentations and have a maximum of flexibility on the analysis of the results. In this tutorial, a few examples are provided.

+ +
+

Loading a configuration using a simulation file¶

+

A Configuration can be initialized with a file passed to its constructor:

+
configuration = Configuration(argv[0])
+
+
+

The configuration could also be partial and completed by the script. Finally, the configuration can be checked for correctness using the check_all method:

+
configuration.check_all()
+
+
+

This method will raise an exception if something is not correct.

+
+
+

Creating a configuration from scratch¶

+

It is also possible to create a new configuration from an empty configuration. This is done by instantiating a Configuration object without argument. Then, its attributes can be changed:

+
configuration = Configuration()
+
+configuration.duration = 100 * configuration.cycles_per_ms
+
+
+

It is also possible to add tasks:

+
configuration.add_task(name="T1", identifier=1, period=7,
+                       activation_date=0, wcet=3, deadline=7)
+
+
+

And of course processors:

+
configuration.add_processor(name="CPU 1", identifier=1)
+
+
+

Finally, a scheduler is also required:

+
configuration.scheduler_info.set_name("examples/RM.py",
+        configuration.cur_dir)
+
+
+
+
+

Creating the Model¶

+

A configuration is an object grouping every characteristics of the system (tasks, processors, schedulers, etc). Such a configuration can be passed to the Model constructor in order to create the simulation:

+
model = Model(configuration)
+
+
+

And the simulation can be run with the run_model method:

+
model.run_model()
+
+
+

Some basic logs can be get through the logs attribute:

+
for log in model.logs:
+    print(log)
+
+
+
+
+

First Example¶

+

The following script simulate a system loading from a simulation file or configured from scratch:

+
import sys
+from simso.core import Model
+from simso.configuration import Configuration
+
+def main(argv):
+    if len(argv) == 1:
+        # Configuration load from a file.
+        configuration = Configuration(argv[0])
+    else:
+        # Manual configuration:
+        configuration = Configuration()
+
+        configuration.duration = 420 * configuration.cycles_per_ms
+
+        # Add tasks:
+        configuration.add_task(name="T1", identifier=1, period=7,
+                               activation_date=0, wcet=3, deadline=7)
+        configuration.add_task(name="T2", identifier=2, period=12,
+                               activation_date=0, wcet=3, deadline=12)
+        configuration.add_task(name="T3", identifier=3, period=20,
+                               activation_date=0, wcet=5, deadline=20)
+
+        # Add a processor:
+        configuration.add_processor(name="CPU 1", identifier=1)
+
+        # Add a scheduler:
+        configuration.scheduler_info.set_name("examples/RM.py",
+                configuration.cur_dir)
+
+    # Check the config before trying to run it.
+    configuration.check_all()
+
+    # Init a model from the configuration.
+    model = Model(configuration)
+
+    # Execute the simulation.
+    model.run_model()
+
+    # Print logs.
+    for log in model.logs:
+        print(log)
+
+
+
+
+

More details¶

+

It is possible to get more information from the tasks using Results class. For example we could get the computation time of the jobs:

+
for task in model.results.tasks:
+    print(task.name + ":")
+    for job in task.jobs:
+        print("%s %.3f ms" % (job.name, job.computation_time))
+
+
+

Or the number of preemptions per task:

+
for task in model.results.task_list:
+    print("%s %d" % (task.name, sum([job.preemption_count for job in task.jobs])))
+
+
+

You can get all the metrics provided in the TaskR and JobR objects. Read the documentation of these classes to know exactly what is directly accessible.

+

It is also possible to get the monitor object from each processors. This is a very detail history of the system. For example, you can count the number of context switches, where a context switch is something that happen when the previous task running on the same processor is different:

+
cxt = 0
+for processor in model.processors:
+    prev = None
+    for evt in processor.monitor:
+        if evt[1].event == ProcEvent.RUN:
+            if prev is not None and prev != evt[1].args.identifier:
+                cxt += 1
+            prev = evt[1].args.identifier
+
+print("Number of context switches (without counting the OS): " + str(cxt))
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/html/write_scheduler.html b/docs/html/write_scheduler.html new file mode 100644 index 0000000..b00085f --- /dev/null +++ b/docs/html/write_scheduler.html @@ -0,0 +1,277 @@ + + + + + + + + How to write a scheduling policy — SimSo documentation + + + + + + + + + + + + + + + +
+
+
+ +
+

How to write a scheduling policy¶

+

This tutorial explains through minimalist examples how to write a scheduler.

+ +
+

Example 1: uniprocessor EDF¶

+

This example shows how to write an Earliest Deadline First scheduler for a single processor. As a reminder, the Earliest Deadline First prioritizes the tasks with the closest absolute deadline among all the ready tasks. A task is ready when it is activated and not finished.

+
+

Creation of the file¶

+

A scheduler for SimSo is a Python class that inherits from the simso.core.Scheduler class. The first step is to write the skeleton of our scheduler. Create a file named “EDF_mono.py” and write the following code:

+
from simso.core import Scheduler
+
+class EDF_mono(Scheduler):
+    def init(self):
+        pass
+
+    def on_activate(self, job):
+        pass
+
+    def on_terminated(self, job):
+        pass
+
+    def schedule(self, cpu):
+        pass
+
+
+

It is mandatory for the class name to be identical to the file name.

+
+
+

Explanation of the skeleton¶

+

The first thing done here is importing the Scheduler class. Then we define the EDF_mono class as a subclass of the Scheduler.

+

Four methods are redifined:

+
    +
  • The init method is called when the simulation is ready to start, this is where the structures used by the scheduler should be initialized. The usual Python constructor is not guaranteed to be called before each simulation run and the Task and Processors are not instantiated yet when the scheduler is created.
  • +
  • The on_activate method is called on task activations.
  • +
  • The on_terminated method is called when a job finished its execution.
  • +
  • The schedule method is called by the processor when it needs to run the scheduler. This method should not be called directly.
  • +
+
+
+

Implementation¶

+

In a nutshell, the algorithm is the following: a list of ready jobs is kept up-to-date using the on_activate and on_terminated methods. When the schedule method is called, the ready job with the closest absolute deadline is chosen.

+

So, the first step is to define a ready_list, and to append the jobs and remove them respectively when the jobs are activated and when they finish. The code should looks like that:

+
from core import Scheduler
+
+class EDF_mono(Scheduler):
+    def init(self):
+        self.ready_list = []
+
+    def on_activate(self, job):
+        self.ready_list.append(job)
+
+    def on_terminated(self, job):
+        self.ready_list.remove(job)
+
+    def schedule(self, cpu):
+        pass
+
+
+

The second step is to write the schedule logic. Selecting the job with the closest absolute deadline is pretty easy. But we need to be sure that there is at least one ready job. One possible implementation is:

+
def schedule(self, cpu):
+    if self.ready_list:  # If at least one job is ready:
+        # job with the highest priority
+        job = min(self.ready_list, key=lambda x: x.absolute_deadline)
+    else:
+        job = None
+
+    return (job, cpu)
+
+
+

At this point, we are still missing a very important thing: calling the scheduler! This is not done by invoking the schedule method. As a reminder, that’s the processor which is responsible to call the scheduler. The reason is that if an overhead must be applied, it is done on the processor running the scheduler. The good way to call the scheduler is by sending a message to the processor using the resched method.

+

Any job is affected to a processor. This is the last processor on which the task was running or an arbitrary processor on the first execution. The scheduler can be called indirectly using job.cpu.resched() when a scheduling event occurs. We could also use self.processors[0].resched to run the scheduler on the first (and only) processor of the system.

+

This is the full code:

+
from simso.core import Scheduler
+
+
+class EDF_mono(Scheduler):
+    def init(self):
+        self.ready_list = []
+
+    def on_activate(self, job):
+        self.ready_list.append(job)
+        job.cpu.resched()
+
+    def on_terminated(self, job):
+        self.ready_list.remove(job)
+        job.cpu.resched()
+
+    def schedule(self, cpu):
+        if self.ready_list:  # If at least one job is ready:
+            # job with the highest priority
+            job = min(self.ready_list, key=lambda x: x.absolute_deadline)
+        else:
+            job = None
+
+        return (job, cpu)
+
+
+
+
+
+

Example 2: Partitionned EDF¶

+

The simplest method to handle multiprocessor architectures is to use partitionning. This approach consists in allocating the tasks to the processors and executing a mono-processor scheduler on each processor.

+

In order to ease the work for the developer of a scheduler, an helping class, named PartitionedScheduler, is provided.

+
+

Initializing the scheduler¶

+

The PartitionedScheduler is defined in the simso.utils module. It is also necessary to load the SchedulerInfo class in order to give to the PartitionedScheduler <simso.utils.PartitionedScheduler> the mono-processor scheduler to use. The first thing to do is importing these classes:

+
from simso.utils import PartitionedScheduler
+from simso.core.Scheduler import SchedulerInfo
+
+
+

Then the Scheduler can be initialized like this:

+
class P_EDF(PartitionedScheduler):
+    def init(self):
+        PartitionedScheduler.init(self, SchedulerInfo("EDF_mono", EDF_mono))
+
+
+
+
+

Defining the packing¶

+

A First-Fit bin-packing can be used to affect the tasks to the processors. For that, the packer() must be overriden:

+
def packer(self):
+    # First Fit
+    cpus = [[cpu, 0] for cpu in self.processors]
+    for task in self.task_list:
+        j = 0
+        # Find a processor with free space.
+        while cpus[j][1] + float(task.wcet) / task.period > 1.0:
+            j += 1
+            if j >= len(self.processors):
+                print("oops bin packing failed.")
+                return False
+
+        # Affect it to the task.
+        self.affect_task_to_processor(task, cpus[j][0])
+
+        # Update utilization.
+        cpus[j][1] += float(task.wcet) / task.period
+    return True
+
+
+
+
+

Complete example¶

+

Complete source code:

+
from simso.core.Scheduler import SchedulerInfo
+from EDF_mono import EDF_mono
+from simso.utils import PartitionedScheduler
+
+
+class P_EDF(PartitionedScheduler):
+    def init(self):
+        PartitionedScheduler.init(self, SchedulerInfo("EDF_mono", EDF_mono))
+
+    def packer(self):
+        # First Fit
+        cpus = [[cpu, 0] for cpu in self.processors]
+        for task in self.task_list:
+            j = 0
+            # Find a processor with free space.
+            while cpus[j][1] + float(task.wcet) / task.period > 1.0:
+                j += 1
+                if j >= len(self.processors):
+                    print("oops bin packing failed.")
+                    return False
+
+            # Affect it to the task.
+            self.affect_task_to_processor(task, cpus[j][0])
+
+            # Update utilization.
+            cpus[j][1] += float(task.wcet) / task.period
+        return True
+
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..3a61449 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +SimSo documentation +=================== + +.. toctree:: + :maxdepth: 2 + + introduction.rst + faq.rst + write_scheduler.rst + text_mode.rst + modules.rst + licenses.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + + diff --git a/docs/introduction.rst b/docs/introduction.rst new file mode 100644 index 0000000..7e3005d --- /dev/null +++ b/docs/introduction.rst @@ -0,0 +1,92 @@ +Introduction +============ + +What is SimSo? +-------------- + +SimSo is a scheduling simulator for real-time multiprocessor architectures that takes into account some scheduling overheads (scheduling decisions, context switches) and the impact of caches through statistical models. Based on a Discrete-Event Simulator (SimPy), it allows quick simulations and a fast prototyping of scheduling policies using Python. + +SimSo is an open source software, available under the `CeCILL license `_, a GPL compatible license. + +Download +-------- + +You can find the last version of SimSo on the `SimSo Website`_. + +.. _Simso Website: http://homepages.laas.fr/mcheramy/simso/ + +Installation +------------ + +SimSo is available for the main platforms and so is its source code. The archive containing the source code is more often updated and should be used when possible. + +In order to install SimSo from the souce code, the dependences must be installed first. Then, type "python setup.py install" to install SimSo. + +Dependencies +"""""""""""" + +When using SimSo from the sources, the following softwares and librairies are required: + + - Python 2.7+ + - SimPy 2.3.1 (not compatible with SimPy 3) + - NumPy 1.6+ + - PyQt4 4.9+ + +If you are using a binary, everything should be packed in the binary. + +First step +---------- + +SimSo is provided with a graphical user interface that aims to be very easy to use. This is a good way to develop and test a scheduler. See `How to write a scheduling policy `_. + +It is also possible to use SimSo as a library. This allows in particular to run simulations in text mode with a maximum of flexibility. + +Available Schedulers +-------------------- + +Currently, the following schedulers are available: + +**Uniprocessor schedulers** + + - Earliest Deadline First (EDF) + - Rate Monotonic (RM) + - Fixed Priority (FP) + - Static-EDF (A DVFS EDF) + - CC-EDF: Real-Time dynamic voltage scaling for low-power embedded operating systems by P. Pillai et al. + +**Uniprocessor schedulers adapted to multiprocessor** + - Global-EDF + - Global-RM + - Earliest Deadline Zero Laxity (EDZL) + - Least Laxity First (LLF) + - Modified Least Laxity First (MLLF): A Modified Least-Laxity-First Scheduling Algorithm for Real-Time Tasks by S.-H. Oh and S.-M. Yang. + - PriD: Real-time scheduling on multiprocessors by J., Baruah, S., & Funk, S. + - EDF-US + - G-FL: Fair lateness scheduling: Reducing maximum lateness in G-EDF-like scheduling by Erickson and Anderson. + +**Partitionned** + Any uniprocessor scheduler using a partitionning algorithm. The following heuristics are provided: + + - First-Fit and Decreasing-First-Fit + - Next-Fit and Decreasing-Next-Fit + - Best-Fit and Decreasing-Best-Fit + - Worst-Fit and Decreasing-Worst-Fit + +**PFair** + - Earliest Pseudo-Deadline First (EPDF) + - PD2 and ER-PD2: Early-Release Fair Scheduling. In Proceedings of the Euromicro Conference on Real-Time Systems by J. H. Anderson et al. + +**DPFair** + - LLREF: An Optimal Real-Time Scheduling Algorithm for Multiprocessors by Cho et al. + - LRE-TL: An Optimal Multiprocessor Scheduling Algorithm for Sporadic Task Sets by S. Funk et al. + - DP-WRAP: DP-FAIR: A Simple Model for Understanding Optimal Multiprocessor Scheduling by Levin et al. + - BF: Multiple-resource periodic scheduling problem: how much fairness is necessary? by Zhu et al. + - NVNLF: Work-Conversing Optimal Real-Time Scheduling on Multiprocessors by Funaoka et al. + +**Semi-partitionned** + - EKG: Multiprocessor Scheduling with Few Preemptions by B. Andersson and E. Tovar. + - EDHS: Semi-Partitioning Technique for Multiprocessor Real-Time Scheduling by Kato et al. + +**Other** + - RUN: Optimal Multiprocessor Real-Time Scheduling via Reduction to Uniprocessor by Regnier et al. + - U-EDF: an Unfair but Optimal Multiprocessor Scheduling Algorithm for Sporadic Tasks by Nelissen et al. diff --git a/docs/licenses.rst b/docs/licenses.rst new file mode 100644 index 0000000..d6945ec --- /dev/null +++ b/docs/licenses.rst @@ -0,0 +1,63 @@ +Licenses +======== + +SimSo +----- + +SimSo is distributed under the CeCILL license:: + + Copyright LAAS-CNRS (2012-2015) + + maxime.cheramy@laas.fr + + This software is a computer program whose purpose is to provide a + simulator for Multiprocessor Real-Time Scheduling with Overheads. + + This software is governed by the CeCILL license under French law and + abiding by the rules of distribution of free software. You can use, + modify and/ or redistribute the software under the terms of the CeCILL + license as circulated by CEA, CNRS and INRIA at the following URL + "http://www.cecill.info". + + As a counterpart to the access to the source code and rights to copy, + modify and redistribute granted by the license, users are provided only + with a limited warranty and the software's author, the holder of the + economic rights, and the successive licensors have only limited + liability. + + In this respect, the user's attention is drawn to the risks associated + with loading, using, modifying and/or developing or reproducing the + software by the user in light of its specific status of free software, + that may mean that it is complicated to manipulate, and that also + therefore means that it is reserved for developers and experienced + professionals having in-depth computer knowledge. Users are therefore + encouraged to load and test the software's suitability as regards their + requirements in conditions enabling the security of their systems and/or + data to be ensured and, more generally, to use and operate it in the + same conditions as regards security. + + The fact that you are presently reading this means that you have had + knowledge of the CeCILL license and that you accept its terms. + + + +Dependencies +------------ + +SimPy +^^^^^ + +SimPy is an object-oriented, process-based discrete-event simulation language for Python. +SimPy 2 is released under the GNU Lesser GPL (LGPL) license. + +NumPy +^^^^^ + +Numpy is licensed under the BSD license, enabling reuse with few restrictions. + +PyQt +^^^^ + +The Graphical User Interface of SimSo relies on the GPL version of PyQt. Qt itself is available +in GPL and LGPL versions. + diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 0000000..6cd588e --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,86 @@ +Main modules +============ + +.. contents:: Table of Contents + +simso.core module +----------------- + +.. automodule:: simso.core + +Scheduler +^^^^^^^^^ + +.. automodule:: simso.core.Scheduler + :members: + +Task +^^^^ + +.. automodule:: simso.core.Task + :members: + +Job +^^^ + +.. automodule:: simso.core.Job + :members: + + +Model +^^^^^ + +.. automodule:: simso.core.Model + :members: + + +Processor +^^^^^^^^^ + +.. automodule:: simso.core.Processor + :members: + +Timer +^^^^^ + +.. automodule:: simso.core.Timer + :members: + +Logger +^^^^^^ + +.. automodule:: simso.core.Logger + :members: + +results +^^^^^^^ + +.. automodule:: simso.core.results + :members: + + +simso.configuration module +-------------------------- + +.. automodule:: simso.configuration + +Configuration +^^^^^^^^^^^^^ + +.. automodule:: simso.configuration.Configuration + :members: + +simso.generator module +---------------------- + +.. automodule:: simso.generator.task_generator + :members: + +simso.utils module +------------------ + +PartitionedScheduler +^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: simso.utils.PartitionedScheduler + :members: diff --git a/docs/text_mode.rst b/docs/text_mode.rst new file mode 100644 index 0000000..1ff13cd --- /dev/null +++ b/docs/text_mode.rst @@ -0,0 +1,137 @@ +Using SimSo in script mode +========================== + +SimSo can be used as a library in order to automatize wide experimentations and have a maximum of flexibility on the analysis of the results. In this tutorial, a few examples are provided. + +.. contents:: Table of Contents + + +Loading a configuration using a simulation file +----------------------------------------------- + +A :class:`Configuration ` can be initialized with a file passed to its constructor:: + + configuration = Configuration(argv[0]) + +The configuration could also be partial and completed by the script. Finally, the configuration can be checked for correctness using the :meth:`check_all ` method:: + + configuration.check_all() + +This method will raise an exception if something is not correct. + +Creating a configuration from scratch +------------------------------------- + +It is also possible to create a new configuration from an empty configuration. This is done by instantiating a :class:`Configuration ` object without argument. Then, its attributes can be changed:: + + configuration = Configuration() + + configuration.duration = 100 * configuration.cycles_per_ms + +It is also possible to add tasks:: + + configuration.add_task(name="T1", identifier=1, period=7, + activation_date=0, wcet=3, deadline=7) + +And of course processors:: + + configuration.add_processor(name="CPU 1", identifier=1) + +Finally, a scheduler is also required:: + + configuration.scheduler_info.set_name("examples/RM.py", + configuration.cur_dir) + +Creating the Model +------------------ + +A :class:`configuration ` is an object grouping every characteristics of the system (tasks, processors, schedulers, etc). Such a configuration can be passed to the :class:`Model ` constructor in order to create the simulation:: + + model = Model(configuration) + +And the simulation can be run with the :meth:`run_model ` method:: + + model.run_model() + +Some basic logs can be get through the :meth:`logs ` attribute:: + + for log in model.logs: + print(log) + +First Example +------------- + +The following script simulate a system loading from a simulation file or configured from scratch:: + + import sys + from simso.core import Model + from simso.configuration import Configuration + + def main(argv): + if len(argv) == 1: + # Configuration load from a file. + configuration = Configuration(argv[0]) + else: + # Manual configuration: + configuration = Configuration() + + configuration.duration = 420 * configuration.cycles_per_ms + + # Add tasks: + configuration.add_task(name="T1", identifier=1, period=7, + activation_date=0, wcet=3, deadline=7) + configuration.add_task(name="T2", identifier=2, period=12, + activation_date=0, wcet=3, deadline=12) + configuration.add_task(name="T3", identifier=3, period=20, + activation_date=0, wcet=5, deadline=20) + + # Add a processor: + configuration.add_processor(name="CPU 1", identifier=1) + + # Add a scheduler: + configuration.scheduler_info.set_name("examples/RM.py", + configuration.cur_dir) + + # Check the config before trying to run it. + configuration.check_all() + + # Init a model from the configuration. + model = Model(configuration) + + # Execute the simulation. + model.run_model() + + # Print logs. + for log in model.logs: + print(log) + + +More details +------------ + +It is possible to get more information from the tasks using :class:`Results ` class. For example we could get the computation time of the jobs:: + + for task in model.results.tasks: + print(task.name + ":") + for job in task.jobs: + print("%s %.3f ms" % (job.name, job.computation_time)) + +Or the number of preemptions per task:: + + for task in model.results.task_list: + print("%s %d" % (task.name, sum([job.preemption_count for job in task.jobs]))) + +You can get all the metrics provided in the :class:`TaskR ` and :class:`JobR ` objects. Read the documentation of these classes to know exactly what is directly accessible. + +It is also possible to get the monitor object from each processors. This is a very detail history of the system. For example, you can count the number of context switches, where a context switch is something that happen when the previous task running on the same processor is different:: + + cxt = 0 + for processor in model.processors: + prev = None + for evt in processor.monitor: + if evt[1].event == ProcEvent.RUN: + if prev is not None and prev != evt[1].args.identifier: + cxt += 1 + prev = evt[1].args.identifier + + print("Number of context switches (without counting the OS): " + str(cxt)) diff --git a/docs/write_scheduler.rst b/docs/write_scheduler.rst new file mode 100644 index 0000000..8773838 --- /dev/null +++ b/docs/write_scheduler.rst @@ -0,0 +1,193 @@ +How to write a scheduling policy +================================ + +This tutorial explains through minimalist examples how to write a scheduler. + +.. contents:: Table of Contents + +Example 1: uniprocessor EDF +--------------------------- + +This example shows how to write an Earliest Deadline First scheduler for a single processor. As a reminder, the Earliest Deadline First prioritizes the tasks with the closest absolute deadline among all the ready tasks. A task is ready when it is activated and not finished. + +Creation of the file +"""""""""""""""""""" + +A scheduler for SimSo is a Python class that inherits from the :class:`simso.core.Scheduler` class. The first step is to write the skeleton of our scheduler. Create a file named "EDF_mono.py" and write the following code:: + + from simso.core import Scheduler + + class EDF_mono(Scheduler): + def init(self): + pass + + def on_activate(self, job): + pass + + def on_terminated(self, job): + pass + + def schedule(self, cpu): + pass + +It is mandatory for the class name to be identical to the file name. + +Explanation of the skeleton +""""""""""""""""""""""""""" + +The first thing done here is importing the :class:`Scheduler ` class. Then we define the `EDF_mono` class as a subclass of the `Scheduler`. + +Four methods are redifined: + +- The :meth:`init ` method is called when the simulation is ready to start, this is where the structures used by the scheduler should be initialized. The usual Python constructor is not guaranteed to be called before each simulation run and the :class:`Task ` and :class:`Processors ` are not instantiated yet when the scheduler is created. + +- The :meth:`on_activate ` method is called on task activations. + +- The :meth:`on_terminated ` method is called when a job finished its execution. + +- The :meth:`schedule ` method is called by the processor when it needs to run the scheduler. This method should not be called directly. + +Implementation +"""""""""""""" + +In a nutshell, the algorithm is the following: a list of ready jobs is kept up-to-date using the `on_activate` and `on_terminated` methods. When the schedule method is called, the ready job with the closest absolute deadline is chosen. + +So, the first step is to define a `ready_list`, and to append the jobs and remove them respectively when the jobs are activated and when they finish. The code should looks like that:: + + from core import Scheduler + + class EDF_mono(Scheduler): + def init(self): + self.ready_list = [] + + def on_activate(self, job): + self.ready_list.append(job) + + def on_terminated(self, job): + self.ready_list.remove(job) + + def schedule(self, cpu): + pass + + +The second step is to write the schedule logic. Selecting the job with the closest absolute deadline is pretty easy. But we need to be sure that there is at least one ready job. One possible implementation is:: + + def schedule(self, cpu): + if self.ready_list: # If at least one job is ready: + # job with the highest priority + job = min(self.ready_list, key=lambda x: x.absolute_deadline) + else: + job = None + + return (job, cpu) + +At this point, we are still missing a very important thing: calling the scheduler! This is not done by invoking the `schedule` method. As a reminder, that's the processor which is responsible to call the `scheduler`. The reason is that if an overhead must be applied, it is done on the processor running the scheduler. The good way to call the scheduler is by sending a message to the processor using the :meth:`resched ` method. + +Any job is affected to a processor. This is the last processor on which the task was running or an arbitrary processor on the first execution. The scheduler can be called indirectly using ``job.cpu.resched()`` when a scheduling event occurs. We could also use ``self.processors[0].resched`` to run the scheduler on the first (and only) processor of the system. + +This is the full code:: + + from simso.core import Scheduler + + + class EDF_mono(Scheduler): + def init(self): + self.ready_list = [] + + def on_activate(self, job): + self.ready_list.append(job) + job.cpu.resched() + + def on_terminated(self, job): + self.ready_list.remove(job) + job.cpu.resched() + + def schedule(self, cpu): + if self.ready_list: # If at least one job is ready: + # job with the highest priority + job = min(self.ready_list, key=lambda x: x.absolute_deadline) + else: + job = None + + return (job, cpu) + +Example 2: Partitionned EDF +--------------------------- + +The simplest method to handle multiprocessor architectures is to use partitionning. This approach consists in allocating the tasks to the processors and executing a mono-processor scheduler on each processor. + +In order to ease the work for the developer of a scheduler, an helping class, named :class:`PartitionedScheduler `, is provided. + +Initializing the scheduler +"""""""""""""""""""""""""" + +The :class:`PartitionedScheduler ` is defined in the `simso.utils` module. It is also necessary to load the :class:`SchedulerInfo ` class in order to give to the `PartitionedScheduler ` the mono-processor scheduler to use. The first thing to do is importing these classes:: + + from simso.utils import PartitionedScheduler + from simso.core.Scheduler import SchedulerInfo + +Then the Scheduler can be initialized like this:: + + class P_EDF(PartitionedScheduler): + def init(self): + PartitionedScheduler.init(self, SchedulerInfo("EDF_mono", EDF_mono)) + + +Defining the packing +"""""""""""""""""""" + +A First-Fit bin-packing can be used to affect the tasks to the processors. For that, the :meth:`packer` must be overriden:: + + def packer(self): + # First Fit + cpus = [[cpu, 0] for cpu in self.processors] + for task in self.task_list: + j = 0 + # Find a processor with free space. + while cpus[j][1] + float(task.wcet) / task.period > 1.0: + j += 1 + if j >= len(self.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + self.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + return True + + +Complete example +"""""""""""""""" + +Complete source code:: + + from simso.core.Scheduler import SchedulerInfo + from EDF_mono import EDF_mono + from simso.utils import PartitionedScheduler + + + class P_EDF(PartitionedScheduler): + def init(self): + PartitionedScheduler.init(self, SchedulerInfo("EDF_mono", EDF_mono)) + + def packer(self): + # First Fit + cpus = [[cpu, 0] for cpu in self.processors] + for task in self.task_list: + j = 0 + # Find a processor with free space. + while cpus[j][1] + float(task.wcet) / task.period > 1.0: + j += 1 + if j >= len(self.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + self.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + return True + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..50581f1 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup, find_packages +import simso + +setup( + name='simso', + version=simso.__version__, + description='Simulation of Multiprocessor Real-Time Scheduling with Overheads', + author='Maxime Cheramy', + author_email='maxime.cheramy@laas.fr', + url='http://homepages.laas.fr/mcheramy/simso/', + classifiers=[ + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'License :: OSI Approved', + 'Operating System :: OS Independent', + 'Topic :: Scientific/Engineering', + 'Development Status :: 5 - Production/Stable' + ], + packages=find_packages(), + install_requires=[ + 'SimPy==2.3.1', + 'numpy>=1.6' + ], + long_description="""\ +SimSo is a scheduling simulator for real-time multiprocessor architectures that +takes into account some scheduling overheads (scheduling decisions, context- +switches) and the impact of caches through statistical models. Based on a +Discrete-Event Simulator, it allows quick simulations and a fast prototyping +of scheduling policies using Python.""" +) diff --git a/simso/__init__.py b/simso/__init__.py new file mode 100644 index 0000000..e220fa9 --- /dev/null +++ b/simso/__init__.py @@ -0,0 +1 @@ +__version__ = '0.7' diff --git a/simso/configuration/Configuration.py b/simso/configuration/Configuration.py new file mode 100644 index 0000000..1637967 --- /dev/null +++ b/simso/configuration/Configuration.py @@ -0,0 +1,312 @@ +#!/usr/bin/python +# coding=utf-8 + +import os +import re +from xml.dom import minidom +from simso.core.Scheduler import SchedulerInfo +from simso.core import Scheduler +from simso.core.Task import TaskInfo +from simso.core.Processor import ProcInfo + +from .GenerateConfiguration import generate +from .parser import Parser + + +# Hack for Python2 +if not hasattr(minidom.NamedNodeMap, '__contains__'): + minidom.NamedNodeMap.__contains__ = minidom.NamedNodeMap.has_key + + +def _gcd(*numbers): + """Return the greatest common divisor of the given integers""" + from fractions import gcd + return reduce(gcd, numbers) + + +# Least common multiple is not in standard libraries? +def _lcm(numbers): + """Return lowest common multiple.""" + def lcm(a, b): + return (a * b) // _gcd(a, b) + return reduce(lcm, numbers, 1) + + +class Configuration(object): + """ + The configuration class store all the details about a system. An instance + of this class will be passed to the constructor of the + :class:`Model ` class. + """ + def __init__(self, filename=None): + """ + Args: + - `filename` A file can be used to initialize the configuration. + """ + if filename: + parser = Parser(filename) + self.etm = parser.etm + self.duration = parser.duration + self.cycles_per_ms = parser.cycles_per_ms + self._caches_list = parser.caches_list + self.memory_access_time = parser.memory_access_time + self._task_info_list = parser.task_info_list + self.task_data_fields = parser.task_data_fields + self._proc_info_list = parser.proc_info_list + self.proc_data_fields = parser.proc_data_fields + self._scheduler_info = parser.scheduler_info + self.penalty_preemption = parser.penalty_preemption + self.penalty_migration = parser.penalty_migration + else: + self.etm = "wcet" + self.duration = 100000000 + self.penalty_preemption = 0 + self.penalty_migration = 0 + self.cycles_per_ms = 1000000 + self._caches_list = [] + self._task_info_list = [] + self.task_data_fields = {} + self._proc_info_list = [] + self.proc_data_fields = {} + self.memory_access_time = 100 + self._scheduler_info = SchedulerInfo() + self.calc_penalty_cache() + self._set_filename(filename) + + def _set_filename(self, filename): + self._simulation_file = filename + if filename: + self._cur_dir = os.path.split(filename)[0] + if not self._cur_dir: + self._cur_dir = os.curdir + else: + self._cur_dir = os.curdir + + def save(self, simulation_file=None): + """ + Save the current configuration in a file. If no file is given as + argument, the previous file used to write or read the configuration is + used again. + """ + if simulation_file: + old_dir = self._cur_dir + self._cur_dir = os.path.split(simulation_file)[0] or '.' + + # Update relative paths. + self._scheduler_info.set_name( + old_dir + '/' + self._scheduler_info.name, self._cur_dir) + for task in self._task_info_list: + if task.stack_file: + task.set_stack_file( + old_dir + '/' + task.stack_file, self._cur_dir) + + self._simulation_file = simulation_file + + conf_file = open(self._simulation_file, 'w') + conf_file.write(generate(self)) + + def calc_penalty_cache(self): + for proc in self.proc_info_list: + access_time = self.memory_access_time + for cache in reversed(proc.caches): + cache.penalty = access_time - cache.access_time + access_time = cache.access_time + + proc.penalty = access_time + + def check_all(self): + """ + Check the correctness of the configuration (without simulating it). + """ + self.check_general() + self.check_scheduler() + self.check_processors() + self.check_tasks() + self.check_caches() + + def check_general(self): + assert self.duration >= 0, \ + "Simulation duration must be a positive number." + assert self.cycles_per_ms >= 0, \ + "Cycles / ms must be a positive number." + assert self.memory_access_time >= 0, \ + "The memory access time must be a positive number." + + def check_scheduler(self): + cls = self._scheduler_info.get_cls() + assert cls is not None, \ + "A scheduler is needed." + assert issubclass(cls, Scheduler), \ + "Must inherits from Scheduler." + assert self._scheduler_info.overhead >= 0, \ + "An overhead must not be negative." + + def check_processors(self): + # At least one processor: + assert len(self._proc_info_list) > 0, \ + "At least one processor is needed." + + # Caches inclusifs : + succ = {} + for proc in self._proc_info_list: + cur = None + for cache in reversed(proc.caches): + assert not (cache in succ and succ[cache] != cur), \ + "Caches must be inclusives." + succ[cache] = cur + cur = cache + + for index, proc in enumerate(self._proc_info_list): + # Nom correct : + assert re.match('^[a-zA-Z][a-zA-Z0-9 _-]*$', proc.name), \ + "A processor name must begins with a letter and must not "\ + "contains any special character." + # Id unique : + assert proc.identifier not in [ + x.identifier for x in self._proc_info_list[index + 1:]], \ + "Processors' identifiers must be uniques." + + # Overheads positifs : + assert proc.cs_overhead >= 0, \ + "Context Save overhead can't be negative." + assert proc.cl_overhead >= 0, \ + "Context Load overhead can't be negative." + + def check_tasks(self): + assert len(self._task_info_list) > 0, "At least one task is needed." + for index, task in enumerate(self._task_info_list): + # Id unique : + assert task.identifier not in [ + x.identifier for x in self._task_info_list[index + 1:]], \ + "Tasks' identifiers must be uniques." + # Nom correct : + assert re.match('^[a-zA-Z][a-zA-Z0-9 _-]*$', task.name), "A task "\ + "name must begins with a letter and must not contains any "\ + "special character." + + # Activation date >= 0: + assert task.activation_date >= 0, \ + "Activation date must be positive." + + # Period >= 0: + assert task.period >= 0, "Tasks' periods must be positives." + + # Deadline >= 0: + assert task.deadline >= 0, "Tasks' deadlines must be positives." + + # N_instr >= 0: + assert task.n_instr >= 0, \ + "A number of instructions must be positive." + + # WCET >= 0: + assert task.wcet >= 0, "WCET must be positive." + + # ACET >= 0: + assert task.acet >= 0, "ACET must be positive." + + # ET-STDDEV >= 0: + assert task.et_stddev >= 0, \ + "A standard deviation is a positive number." + + # mix in [0.0, 2.0] + assert 0.0 <= task.mix <= 2.0, \ + "A mix must be positive and less or equal than 2.0" + + if self.etm == "cache": + # stack + assert task.stack_file, "A task needs a stack profile." + + # stack ok + assert task.csdp, "Stack not found or empty." + + def check_caches(self): + for index, cache in enumerate(self._caches_list): + # Id unique : + assert cache.identifier not in [ + x.identifier for x in self._caches_list[index + 1:]], \ + "Caches' identifiers must be uniques." + + # Nom correct : + assert re.match('^[a-zA-Z][a-zA-Z0-9_-]*$', cache.name), \ + "A cache name must begins with a letter and must not " \ + "contains any spacial character nor space." + + # Taille positive : + assert cache.size >= 0, "A cache size must be positive." + + # Access time >= 0: + assert cache.access_time >= 0, "An access time must be positive." + + def get_hyperperiod(self): + """ + Compute and return the hyperperiod of the tasks. + """ + return _lcm([x.period for x in self.task_info_list]) + + @property + def duration_ms(self): + return self.duration / self.cycles_per_ms + + @property + def simulation_file(self): + return self._simulation_file + + @property + def cur_dir(self): + return self._cur_dir + + @property + def caches_list(self): + return self._caches_list + + @property + def task_info_list(self): + """ + List of tasks (TaskInfo objects). + """ + return self._task_info_list + + @property + def proc_info_list(self): + """ + List of processors (ProcInfo objects). + """ + return self._proc_info_list + + @property + def scheduler_info(self): + """ + SchedulerInfo object. + """ + return self._scheduler_info + + def add_task(self, name, identifier, task_type="Periodic", + abort_on_miss=False, period=10, activation_date=0, + n_instr=0, mix=0.5, stack_file="", wcet=0, acet=0, + et_stddev=0, deadline=10, base_cpi=1.0, followed_by=None, + list_activation_dates=[], preemption_cost=0, data=None): + """ + Helper method to create a TaskInfo and add it to the list of tasks. + """ + if data is None: + data = dict((k, None) for k in self.task_data_fields) + + task = TaskInfo(name, identifier, task_type, abort_on_miss, period, + activation_date, n_instr, mix, + (stack_file, self.cur_dir), wcet, acet, et_stddev, + deadline, base_cpi, followed_by, list_activation_dates, + preemption_cost, data) + self.task_info_list.append(task) + return task + + def add_processor(self, name, identifier, cs_overhead=0, + cl_overhead=0, migration_overhead=0, speed=1.0): + """ + Helper method to create a ProcInfo and add it to the list of + processors. + """ + proc = ProcInfo( + identifier, name, cs_overhead, cl_overhead, migration_overhead, + speed) + self.proc_info_list.append(proc) + return proc diff --git a/simso/configuration/GenerateConfiguration.py b/simso/configuration/GenerateConfiguration.py new file mode 100644 index 0000000..f5dc5a3 --- /dev/null +++ b/simso/configuration/GenerateConfiguration.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# coding=utf-8 + +from xml.etree.ElementTree import Element, SubElement +from xml.etree import ElementTree +from xml.dom import minidom + + +def prettify(elem): + """Return a pretty-printed XML string for the Element. + """ + rough_string = ElementTree.tostring(elem, 'utf-8') + reparsed = minidom.parseString(rough_string) + return reparsed.toprettyxml(indent="\t") + + +def generate(configuration): + """ + Generate a string containing the XML version of the configuration. + """ + attrs = {'duration': str(int(configuration.duration)), + 'cycles_per_ms': str(configuration.cycles_per_ms), + 'etm': str(configuration.etm)} + top = Element('simulation', attrs) + + generate_sched(top, configuration.scheduler_info) + generate_cache( + top, configuration.caches_list, configuration.memory_access_time) + generate_processors( + top, configuration.proc_info_list, configuration.proc_data_fields) + generate_tasks( + top, configuration.task_info_list, configuration.task_data_fields) + + return prettify(top) + + +def generate_sched(top, sched_info): + sched = SubElement(top, 'sched', { + 'className': sched_info.name, + 'overhead': str(sched_info.overhead), + 'overhead_activate': str(sched_info.overhead_activate), + 'overhead_terminate': str(sched_info.overhead_terminate) + }) + for field_name in sched_info.data.keys(): + SubElement(sched, 'field', {'name': field_name, + 'value': str(sched_info.data[field_name]), + 'type': sched_info.fields_types[field_name] + }) + return sched + + +def generate_cache(top, caches_list, memory_access_time): + caches = SubElement(top, 'caches', + {'memory_access_time': str(memory_access_time)}) + for cache in caches_list: + SubElement(caches, 'cache', {'name': cache.name, + 'id': str(cache.identifier), + 'policy': "LRU", # TODO + 'type': "data", # TODO + 'size': str(cache.size), + 'access_time': str(cache.access_time)}) + return caches + + +def generate_processors(top, proc_info_list, fields): + processors = SubElement(top, 'processors') + + for name, ftype in fields.items(): + attrs = {'name': name, 'type': ftype} + SubElement(processors, 'field', attrs) + + for proc in proc_info_list: + attrs = dict((k, str(proc.data[k])) for k in proc.data.keys()) + attrs.update({ + 'name': proc.name, + 'id': str(proc.identifier), + 'cl_overhead': str(proc.cl_overhead), + 'cs_overhead': str(proc.cs_overhead), + 'speed': str(proc.speed)}) + processor = SubElement(processors, 'processor', attrs) + for cache in proc.caches: + SubElement(processor, 'cache', {'ref': str(cache.identifier)}) + + +def generate_tasks(top, task_info_list, fields): + tasks = SubElement(top, 'tasks') + + for name, ftype in fields.items(): + attrs = {'name': name, 'type': ftype} + SubElement(tasks, 'field', attrs) + + for task in task_info_list: + attrs = dict((k, str(task.data[k])) for k in task.data.keys()) + attrs.update({'name': task.name, + 'id': str(task.identifier), + 'task_type': task.task_type, + 'abort_on_miss': 'yes' if task.abort_on_miss else 'no', + 'period': str(task.period), + 'activationDate': str(task.activation_date), + 'list_activation_dates': ', '.join( + map(str, task.list_activation_dates)), + 'deadline': str(task.deadline), + 'base_cpi': str(task.base_cpi), + 'instructions': str(task.n_instr), + 'mix': str(task.mix), + 'WCET': str(task.wcet), + 'ACET': str(task.acet), + 'preemption_cost': str(task.preemption_cost), + 'et_stddev': str(task.et_stddev)}) + if task.followed_by is not None: + attrs['followed_by'] = str(task.followed_by) + if task.stack_file: + # XXX: what if the path contain a non-ascii character? + attrs['stack'] = str(task.stack_file) + SubElement(tasks, 'task', attrs) diff --git a/simso/configuration/__init__.py b/simso/configuration/__init__.py new file mode 100644 index 0000000..65ce5a9 --- /dev/null +++ b/simso/configuration/__init__.py @@ -0,0 +1 @@ +from .Configuration import Configuration diff --git a/simso/configuration/parser.py b/simso/configuration/parser.py new file mode 100644 index 0000000..e57549e --- /dev/null +++ b/simso/configuration/parser.py @@ -0,0 +1,238 @@ +# coding=utf-8 + +from xml.dom.minidom import parse +import os.path +from simso.core.Task import TaskInfo, task_types +from simso.core.Processor import ProcInfo +from simso.core.Caches import Cache_LRU +from simso.core.Scheduler import SchedulerInfo + + +convert_function = { + 'int': int, + 'float': float, + 'bool': bool, + 'str': str +} + + +class Parser(object): + """ + Simulation file parser. + """ + def __init__(self, filename): + self.filename = filename + self.cur_dir = os.path.split(filename)[0] + if not self.cur_dir: + self.cur_dir = '.' + self._dom = parse(filename) + self._parse_etm() + self._parse_duration() + self._parse_cycles_per_ms() + self._parse_caches() + self._parse_tasks() + self._parse_processors() + self._parse_scheduler() + self._parse_penalty() + + def _parse_caches(self): + self.caches_list = [] + caches_element = self._dom.getElementsByTagName('caches')[0] + caches = caches_element.getElementsByTagName('cache') + attr = caches_element.attributes + + self.memory_access_time = 100 + if 'memory_access_time' in attr: + self.memory_access_time = int(attr['memory_access_time'].value) + + for cache in caches: + attr = cache.attributes + if attr['policy'].value == 'LRU' and attr['type'].value == 'data': + access_time = 1 + associativity = int(attr['size'].value) + if 'access_time' in attr: + access_time = int(attr['access_time'].value) + if 'associativity' in attr: + associativity = int(attr['associativity'].value) + cache = Cache_LRU(attr['name'].value, int(attr['id'].value), + int(attr['size'].value), associativity, + access_time) + self.caches_list.append(cache) + # TODO Généraliser aux autres types de cache. + + def _parse_tasks(self): + tasks_el = self._dom.getElementsByTagName('tasks')[0] + + self.task_data_fields = {} + for field in tasks_el.getElementsByTagName('field'): + attr = field.attributes + self.task_data_fields[attr['name'].value] = attr['type'].value + + tasks = tasks_el.getElementsByTagName('task') + self.task_info_list = [] + for task in tasks: + attr = task.attributes + + data = dict( + (k, convert_function[self.task_data_fields[k]](attr[k].value)) + for k in attr.keys() if k in self.task_data_fields) + + task_type = 'Periodic' + if 'task_type' in attr and attr['task_type'].value in task_types: + task_type = attr['task_type'].value + elif 'periodic' in attr and attr['periodic'].value == 'no': + task_type = 'APeriodic' + + list_activation_dates = [] + if 'list_activation_dates' in attr and attr['list_activation_dates'].value != '': + list_activation_dates = sorted( + map(float, attr['list_activation_dates'].value.split(','))) + + t = TaskInfo( + attr['name'].value, + int(attr['id'].value), + task_type, + 'abort_on_miss' in attr + and attr['abort_on_miss'].value == 'yes', + float(attr['period'].value), + float(attr['activationDate'].value) + if 'activationDate' in attr else 0, + int(attr['instructions'].value), + float(attr['mix'].value), + (self.cur_dir + '/' + attr['stack'].value, + self.cur_dir) if 'stack' in attr else ("", self.cur_dir), + float(attr['WCET'].value), + float(attr['ACET'].value) if 'ACET' in attr else 0, + float(attr['et_stddev'].value) if 'et_stddev' in attr else 0, + float(attr['deadline'].value), + float(attr['base_cpi'].value), + int(attr['followed_by'].value) + if 'followed_by' in attr else None, + list_activation_dates, + int(float(attr['preemption_cost'].value)) + if 'preemption_cost' in attr else 0, + data) + self.task_info_list.append(t) + + def _parse_processors(self): + processors_el = self._dom.getElementsByTagName('processors')[0] + processors = self._dom.getElementsByTagName('processors')[0] + attr = processors.attributes + + migration_overhead = 0 + if 'migration_overhead' in attr: + migration_overhead = int(attr['migration_overhead'].value) + + self.proc_data_fields = {} + for field in processors_el.getElementsByTagName('field'): + attr = field.attributes + self.proc_data_fields[attr['name'].value] = attr['type'].value + + cpus = processors.getElementsByTagName('processor') + self.proc_info_list = [] + for cpu in cpus: + attr = cpu.attributes + + data = dict( + (k, convert_function[self.proc_data_fields[k]](attr[k].value)) + for k in attr.keys() if k in self.proc_data_fields) + + cl_overhead = 0 + cs_overhead = 0 + if 'cl_overhead' in attr: + cl_overhead = int(float(attr['cl_overhead'].value)) + if 'cs_overhead' in attr: + cs_overhead = int(float(attr['cs_overhead'].value)) + + speed = 1.0 + if 'speed' in attr: + speed = float(attr['speed'].value) + + proc = ProcInfo(name=attr['name'].value, + identifier=int(attr['id'].value), + cs_overhead=cs_overhead, + cl_overhead=cl_overhead, + migration_overhead=migration_overhead, + speed=speed, + data=data) + + caches = cpu.getElementsByTagName('cache') + for cache_element in caches: + attr = cache_element.attributes + for cache in self.caches_list: + if cache.identifier == int(attr['ref'].value): + proc.add_cache(cache) + + self.proc_info_list.append(proc) + + def _parse_etm(self): + simulation = self._dom.getElementsByTagName('simulation')[0] + + if 'etm' in simulation.attributes: + self.etm = simulation.attributes['etm'].value + else: + use_wcet = True + if 'use_wcet' in simulation.attributes: + use_wcet = (simulation.attributes['use_wcet'].value + in ('true', 'yes')) + if use_wcet: + self.etm = "wcet" + else: + self.etm = "cache" + + def _parse_duration(self): + simulation = self._dom.getElementsByTagName('simulation')[0] + if 'duration' in simulation.attributes: + self.duration = int(simulation.attributes['duration'].value) + else: + self.duration = 50000 + + def _parse_penalty(self): + simulation = self._dom.getElementsByTagName('simulation')[0] + if 'penalty_preemption' in simulation.attributes: + self.penalty_preemption = int( + simulation.attributes['penalty_preemption'].value) + else: + self.penalty_preemption = 100000 + if 'penalty_migration' in simulation.attributes: + self.penalty_migration = int( + simulation.attributes['penalty_migration'].value) + else: + self.penalty_migration = 100000 + + def _parse_cycles_per_ms(self): + simulation = self._dom.getElementsByTagName('simulation')[0] + if 'cycles_per_ms' in simulation.attributes: + self.cycles_per_ms = int( + simulation.attributes['cycles_per_ms'].value) + else: + self.cycles_per_ms = 1000000 + + def _parse_scheduler(self): + overhead = 0 + overhead_activate = 0 + overhead_terminate = 0 + sched = self._dom.getElementsByTagName('sched')[0] + attr = sched.attributes + filename = attr['className'].value + if 'overhead' in attr: + overhead = int(float(attr['overhead'].value)) + if 'overhead_activate' in attr: + overhead_activate = int(float(attr['overhead_activate'].value)) + if 'overhead_terminate' in attr: + overhead_terminate = int(float(attr['overhead_terminate'].value)) + + data = {} + fields = sched.getElementsByTagName('field') + for field in fields: + name = field.attributes['name'].value + type_ = field.attributes['type'].value + value = field.attributes['value'].value + data[name] = (convert_function[type_](value), type_) + + self.scheduler_info = SchedulerInfo( + overhead=overhead, overhead_activate=overhead_activate, + overhead_terminate=overhead_terminate, fields=data) + if filename[0] != '/': + filename = self.cur_dir + '/' + filename + self.scheduler_info.set_name(filename, self.cur_dir) diff --git a/simso/core/CSDP.py b/simso/core/CSDP.py new file mode 100644 index 0000000..a306d4e --- /dev/null +++ b/simso/core/CSDP.py @@ -0,0 +1,20 @@ +# coding=utf-8 + + +class CSDP(object): + def __init__(self, stack): + self._csdp = [0] + c = 0 + s = 0.0 + for dist, value in stack.items(): + while c < dist: + self._csdp.append(s) + c += 1 + s += value + self._csdp.append(s) + + def get(self, dist): + if dist < len(self._csdp): + return self._csdp[dist] + else: + return self._csdp[-1] diff --git a/simso/core/Caches.py b/simso/core/Caches.py new file mode 100644 index 0000000..dea5d42 --- /dev/null +++ b/simso/core/Caches.py @@ -0,0 +1,40 @@ +class Cache(object): + def __init__(self, name, identifier, size, associativity, access_time): + self.name = name + self.identifier = identifier + self.size = size + self.access_time = access_time + self.associativity = associativity + self.penalty = 0 + + self.shared_with = None + + def init(self): + self.shared_with = [] + + +class Cache_LRU(Cache): + def __init__(self, name, identifier, size, associativity, penalty): + Cache.__init__(self, name, identifier, size, associativity, penalty) + self._groups = None + + def init(self): + Cache.init(self) + self._groups = [] + + def update(self, task, lines): + self._groups = [x for x in self._groups if x[0] != task] + self._groups.append([task, lines]) + + usedlines = sum([x[1] for x in self._groups]) + + while usedlines > self.size: + if self._groups[0][1] <= usedlines - self.size: + usedlines -= self._groups.pop(0)[1] + else: + self._groups[0][1] -= usedlines - self.size + usedlines = self.size + + def get_lines(self, task): + groups = [x for x in self._groups if x[0] == task] + return groups and groups[0][1] or 0 diff --git a/simso/core/Job.py b/simso/core/Job.py new file mode 100644 index 0000000..92c32d1 --- /dev/null +++ b/simso/core/Job.py @@ -0,0 +1,298 @@ +# coding=utf-8 + +from SimPy.Simulation import Process, hold, passivate +from simso.core.JobEvent import JobEvent +from math import ceil + + +class Job(Process): + """The Job class simulate the behavior of a real Job. This *should* only be + instantiated by a Task.""" + + def __init__(self, task, name, pred, monitor, etm, sim): + """ + Args: + - `task`: The parent :class:`task `. + - `name`: The name for this job. + - `pred`: If the task is not periodic, pred is the job that \ + released this one. + - `monitor`: A monitor is an object that log in time. + - `etm`: The execution time model. + - `sim`: :class:`Model ` instance. + + :type task: GenericTask + :type name: str + :type pred: bool + :type monitor: Monitor + :type etm: AbstractExecutionTimeModel + :type sim: Model + """ + Process.__init__(self, name=name, sim=sim) + self._task = task + self._pred = pred + self.instr_count = 0 # Updated by the cache model. + self._computation_time = 0 + self._last_exec = None + self._n_instr = task.n_instr + self._start_date = None + self._end_date = None + self._is_preempted = False + self._activation_date = self.sim.now_ms() + self._absolute_deadline = self.sim.now_ms() + task.deadline + self._aborted = False + self._sim = sim + self._monitor = monitor + self._etm = etm + self._was_running_on = task.cpu + + self._on_activate() + + self.context_ok = True # The context is ready to be loaded. + + def is_active(self): + """ + Return True if the job is still active. + """ + return self._end_date is None + + def _on_activate(self): + self._monitor.observe(JobEvent(self, JobEvent.ACTIVATE)) + self._sim.logger.log(self.name + " Activated.", kernel=True) + self._etm.on_activate(self) + + def _on_execute(self): + self._last_exec = self.sim.now() + + self._etm.on_execute(self) + if self._is_preempted: + self._is_preempted = False + + self.cpu.was_running = self + + self._monitor.observe(JobEvent(self, JobEvent.EXECUTE, self.cpu)) + self._sim.logger.log("{} Executing on {}".format( + self.name, self._task.cpu.name), kernel=True) + + def _on_stop_exec(self): + if self._last_exec is not None: + self._computation_time += self.sim.now() - self._last_exec + self._last_exec = None + + def _on_preempted(self): + self._on_stop_exec() + self._etm.on_preempted(self) + self._is_preempted = True + self._was_running_on = self.cpu + + self._monitor.observe(JobEvent(self, JobEvent.PREEMPTED)) + self._sim.logger.log(self.name + " Preempted! ret: " + + str(self.interruptLeft), kernel=True) + + def _on_terminated(self): + self._on_stop_exec() + self._etm.on_terminated(self) + + self._end_date = self.sim.now() + self._monitor.observe(JobEvent(self, JobEvent.TERMINATED)) + self._task.end_job(self) + self._task.cpu.terminate(self) + self._sim.logger.log(self.name + " Terminated.", kernel=True) + + def _on_abort(self): + self._on_stop_exec() + self._etm.on_abort(self) + self._end_date = self.sim.now() + self._aborted = True + self._monitor.observe(JobEvent(self, JobEvent.ABORTED)) + self._task.end_job(self) + self._task.cpu.terminate(self) + self._sim.logger.log("Job " + str(self.name) + " aborted! ret:" + str(self.ret)) + + def is_running(self): + """ + Return True if the job is currently running on a processor. + Equivalent to ``self.cpu.running == self``. + + :rtype: bool + """ + return self.cpu.running == self + + def abort(self): + """ + Abort this job. Warning, this is currently only used by the Task when + the job exceeds its deadline. It has not be tested from outside, such + as from the scheduler. + """ + self._on_abort() + + @property + def aborted(self): + """ + True if the job has been aborted. + + :rtype: bool + """ + return self._aborted + + @property + def exceeded_deadline(self): + """ + True if the end_date is greater than the deadline or if the job was + aborted. + """ + return (self._absolute_deadline * self._sim.cycles_per_ms < + self._end_date or self._aborted) + + @property + def start_date(self): + """ + Date (in ms) when this job started executing + (different than the activation). + """ + return self._start_date + + @property + def end_date(self): + """ + Date (in ms) when this job finished its execution. + """ + return self._end_date + + @property + def response_time(self): + if self._end_date: + return (float(self._end_date) / self._sim.cycles_per_ms - + self._activation_date) + else: + return None + + @property + def ret(self): + """ + Remaining execution time. + """ + return self.wcet - self.actual_computation_time + + @property + def computation_time(self): + return float(self.computation_time_cycles) / self._sim.cycles_per_ms + + @property + def computation_time_cycles(self): + if self._last_exec is None: + return int(self._computation_time) + else: + return (int(self._computation_time) + + self.sim.now() - self._last_exec) + + @property + def actual_computation_time(self): + return float( + self.actual_computation_time_cycles) / self._sim.cycles_per_ms + + @property + def actual_computation_time_cycles(self): + """ + Computation time as if the processor speed was 1.0 during the whole + execution. + """ + return self._etm.get_executed(self) + + @property + def cpu(self): + """ + The :class:`processor ` on which the + job is attached. Equivalent to ``self.task.cpu``. + """ + return self._task.cpu + + @property + def task(self): + """The :class:`task ` for this job.""" + return self._task + + @property + def data(self): + """ + The extra data specified for the task. Equivalent to + ``self.task.data``. + """ + return self._task.data + + @property + def wcet(self): + """ + Worst-Case Execution Time in milliseconds. + Equivalent to ``self.task.wcet``. + """ + return self._task.wcet + + @property + def activation_date(self): + """ + Activation date in milliseconds for this job. + """ + return self._activation_date + + @property + def absolute_deadline(self): + """ + Absolute deadline in milliseconds for this job. This is the activation + date + the relative deadline. + """ + return self._absolute_deadline + + @property + def absolute_deadline_cycles(self): + return self._absolute_deadline * self._sim.cycles_per_ms + + @property + def period(self): + """Period in milliseconds. Equivalent to ``self.task.period``.""" + return self._task.period + + @property + def deadline(self): + """ + Relative deadline in milliseconds. + Equivalent to ``self.task.deadline``. + """ + return self._task.deadline + + @property + def pred(self): + return self._pred + + def activate_job(self): + self._start_date = self.sim.now() + # Notify the OS. + self._task.cpu.activate(self) + + # While the job's execution is not finished. + while self._end_date is None: + # Wait an execute order. + yield passivate, self + + # Execute the job. + if not self.interrupted(): + self._on_execute() + # ret is a duration lower than the remaining execution time. + ret = self._etm.get_ret(self) + + while ret > 0: + yield hold, self, int(ceil(ret)) + + if not self.interrupted(): + # If executed without interruption for ret cycles. + ret = self._etm.get_ret(self) + else: + self._on_preempted() + self.interruptReset() + break + + if ret <= 0: + # End of job. + self._on_terminated() + + else: + self.interruptReset() diff --git a/simso/core/JobEvent.py b/simso/core/JobEvent.py new file mode 100644 index 0000000..ea99dee --- /dev/null +++ b/simso/core/JobEvent.py @@ -0,0 +1,18 @@ +# coding=utf-8 + + +class JobEvent: + ACTIVATE = 1 + EXECUTE = 2 + PREEMPTED = 3 + TERMINATED = 4 + ABORTED = 5 + + count = 0 + + def __init__(self, job, event, cpu=None): + self.event = event + self.job = job + self.cpu = cpu + JobEvent.count += 1 + self.id_ = JobEvent.count diff --git a/simso/core/Logger.py b/simso/core/Logger.py new file mode 100644 index 0000000..2631cf2 --- /dev/null +++ b/simso/core/Logger.py @@ -0,0 +1,34 @@ +# coding=utf-8 + +from SimPy.Simulation import Monitor + + +class Logger(object): + """ + Simple logger. Every message is logged with its date. + """ + def __init__(self, sim): + """ + Args: + - `sim`: The :class:`model ` object. + """ + self.sim = sim + self._logs = Monitor(name="Logs", sim=sim) + + def log(self, msg, kernel=False): + """ + Log the message `msg`. + + Args: + - `msg`: The message to log. + - `kernel`: Allows to make a distinction between a message from \ + the core of the simulation or from the scheduler. + """ + self._logs.observe((msg, kernel)) + + @property + def logs(self): + """ + The logs, a SimPy Monitor object. + """ + return self._logs diff --git a/simso/core/Model.py b/simso/core/Model.py new file mode 100644 index 0000000..de61b0e --- /dev/null +++ b/simso/core/Model.py @@ -0,0 +1,149 @@ +# coding=utf-8 + +from SimPy.Simulation import Simulation +from simso.core.Processor import Processor +from simso.core.Task import Task +from simso.core.Timer import Timer +from simso.core.etm import execution_time_models +from simso.core.Logger import Logger +from simso.core.results import Results + + +class Model(Simulation): + """ + Main class for the simulation. It instantiate the various components + required by the simulation and run it. + """ + + def __init__(self, configuration, callback=None): + """ + Args: + - `callback`: A callback can be specified. This function will be \ + called to report the advance of the simulation (useful for a \ + progression bar). + - `configuration`: The :class:`configuration \ + ` of the simulation. + + Methods: + """ + Simulation.__init__(self) + self._logger = Logger(self) + task_info_list = configuration.task_info_list + proc_info_list = configuration.proc_info_list + self._cycles_per_ms = configuration.cycles_per_ms + self.scheduler = configuration.scheduler_info.instantiate(self) + + try: + self._etm = execution_time_models[configuration.etm]( + self, len(proc_info_list) + ) + except KeyError: + print("Unknowned Execution Time Model.", configuration.etm) + + self._task_list = [] + for task_info in task_info_list: + self._task_list.append(Task(self, task_info)) + + # Init the processor class. This will in particular reinit the + # identifiers to 0. + Processor.init() + + # Initialization of the caches + for cache in configuration.caches_list: + cache.init() + + self._processors = [] + for proc_info in proc_info_list: + proc = Processor(self, proc_info) + proc.caches = proc_info.caches + self._processors.append(proc) + + # XXX: too specific. + self.penalty_preemption = configuration.penalty_preemption + self.penalty_migration = configuration.penalty_migration + + self._etm.init() + + self._duration = configuration.duration + self.progress = Timer(self, Model._on_tick, (self,), + self.duration // 20 + 1, one_shot=False, + in_ms=False) + self._callback = callback + self.scheduler.task_list = self._task_list + self.scheduler.processors = self._processors + self.results = None + + def now_ms(self): + return float(self.now()) / self._cycles_per_ms + + @property + def logs(self): + """ + All the logs from the :class:`Logger `. + """ + return self._logger.logs + + @property + def logger(self): + return self._logger + + @property + def cycles_per_ms(self): + """ + Number of cycles per milliseconds. A cycle is the internal unit used + by SimSo. However, the tasks are defined using milliseconds. + """ + return self._cycles_per_ms + + @property + def etm(self): + """ + Execution Time Model + """ + return self._etm + + @property + def processors(self): + """ + List of all the processors. + """ + return self._processors + + @property + def task_list(self): + """ + List of all the tasks. + """ + return self._task_list + + @property + def duration(self): + """ + Duration of the simulation. + """ + return self._duration + + def _on_tick(self): + if self._callback: + self._callback(self.now()) + + def run_model(self): + """ Execute the simulation.""" + self.initialize() + self.scheduler.init() + self.progress.start() + + for cpu in self._processors: + self.activate(cpu, cpu.run()) + + for task in self._task_list: + self.activate(task, task.execute()) + + try: + self.simulate(until=self._duration) + finally: + self._etm.update() + + if self.now() > 0: + self.results = Results(self) + self.results.end() diff --git a/simso/core/ProcEvent.py b/simso/core/ProcEvent.py new file mode 100644 index 0000000..1003406 --- /dev/null +++ b/simso/core/ProcEvent.py @@ -0,0 +1,38 @@ +# coding=utf-8 + + +class ProcEvent(object): + RUN = 1 + IDLE = 2 + OVERHEAD = 3 + + def __init__(self, event=0, args=None): + self.event = event + self.args = args + + +class ProcRunEvent(ProcEvent): + def __init__(self, job): + ProcEvent.__init__(self, ProcEvent.RUN, job) + + +class ProcIdleEvent(ProcEvent): + def __init__(self): + ProcEvent.__init__(self, ProcEvent.IDLE) + + +class ProcOverheadEvent(ProcEvent): + def __init__(self, type_overhead): + ProcEvent.__init__(self, ProcEvent.OVERHEAD, type_overhead) + + +class ProcCxtSaveEvent(ProcOverheadEvent): + def __init__(self, terminated=False): + ProcOverheadEvent.__init__(self, "CS") + self.terminated = terminated + + +class ProcCxtLoadEvent(ProcOverheadEvent): + def __init__(self, terminated=False): + ProcOverheadEvent.__init__(self, "CL") + self.terminated = terminated diff --git a/simso/core/Processor.py b/simso/core/Processor.py new file mode 100644 index 0000000..ad7db4a --- /dev/null +++ b/simso/core/Processor.py @@ -0,0 +1,250 @@ +# coding=utf-8 + +from collections import deque +from SimPy.Simulation import Process, Monitor, hold, waituntil +from simso.core.ProcEvent import ProcRunEvent, ProcIdleEvent, \ + ProcOverheadEvent, ProcCxtSaveEvent, ProcCxtLoadEvent + + +RESCHED = 1 +ACTIVATE = 2 +TERMINATE = 3 +TIMER = 4 +MIGRATE = 5 +SPEED = 6 + + +class ProcInfo(object): + def __init__(self, identifier, name, cs_overhead=0, cl_overhead=0, + migration_overhead=0, speed=1.0, data=None): + self.identifier = identifier + self.name = name + self.penalty = 0 + self.caches = [] + self.cs_overhead = cs_overhead + self.cl_overhead = cl_overhead + self.migration_overhead = migration_overhead + if data is None: + data = {} + self.data = data + self.speed = speed + + def add_cache(self, cache): + self.caches.append(cache) + + +class Processor(Process): + """ + A processor is responsible of deciding whether the simulated processor + should execute a job or execute the scheduler. There is one instance of + Processor per simulated processor. Those are responsible to call the + scheduler methods. + + When a scheduler needs to take a scheduling decision, it must invoke the + :meth:`resched` method. This is typically done in the :meth:`on_activate + `, :meth:`on_terminated + ` or in a :class:`timer + ` handler. + """ + _identifier = 0 + + @classmethod + def init(cls): + cls._identifier = 0 + + def __init__(self, model, proc_info): + Process.__init__(self, name=proc_info.name, sim=model) + self._model = model + self._internal_id = Processor._identifier + Processor._identifier += 1 + self.identifier = proc_info.identifier + self._running = None + self.was_running = None + self._evts = deque([]) + self.sched = model.scheduler + self.monitor = Monitor(name="Monitor" + proc_info.name, sim=model) + self._caches = [] + self._penalty = proc_info.penalty + self._cs_overhead = proc_info.cs_overhead + self._cl_overhead = proc_info.cl_overhead + self._migration_overhead = proc_info.migration_overhead + self.set_caches(proc_info.caches) + self.timer_monitor = Monitor(name="Monitor Timer" + proc_info.name, + sim=model) + self._speed = proc_info.speed + + def resched(self): + """ + Add a resched event to the list of events to handle. + """ + self._evts.append((RESCHED,)) + + def migrate(self, job): + self._evts.append((MIGRATE, job)) + self._running = job + + def activate(self, job): + self._evts.append((ACTIVATE, job)) + + def terminate(self, job): + self._evts.append((TERMINATE, job)) + self._running = None + + def preempt(self): + if MIGRATE not in self._evts: + self._evts.append(("preempt",)) + self._running = None + + def timer(self, timer): + self._evts.append((TIMER, timer)) + + def set_speed(self, speed): + assert speed >= 0, "Speed must be positive." + self._evts.append((SPEED, speed)) + + @property + def speed(self): + return self._speed + + def is_running(self): + """ + Return True if a job is currently running on that processor. + """ + return self._running is not None + + def set_caches(self, caches): + self._caches = caches + for cache in caches: + cache.shared_with.append(self) + + def get_caches(self): + return self._caches + + caches = property(get_caches, set_caches) + + @property + def penalty_memaccess(self): + return self._penalty + + @property + def cs_overhead(self): + return self._cs_overhead + + @property + def cl_overhead(self): + return self._cl_overhead + + @property + def internal_id(self): + """A unique, internal, id.""" + return self._internal_id + + @property + def running(self): + """ + The job currently running on that processor. None if no job is + currently running on the processor. + """ + return self._running + + def run(self): + while True: + if not self._evts: + job = self._running + if job: + yield waituntil, self, lambda: job.context_ok + self.monitor.observe(ProcCxtLoadEvent()) + yield hold, self, self.cl_overhead # overhead load context + self.monitor.observe(ProcCxtLoadEvent(terminated=True)) + job.interruptReset() + self.sim.reactivate(job) + self.monitor.observe(ProcRunEvent(job)) + job.context_ok = False + else: + self.monitor.observe(ProcIdleEvent()) + + # Wait event. + yield waituntil, self, lambda: self._evts + if job: + self.interrupt(job) + self.monitor.observe(ProcCxtSaveEvent()) + yield hold, self, self.cs_overhead # overhead save context + self.monitor.observe(ProcCxtSaveEvent(terminated=True)) + job.context_ok = True + + evt = self._evts.popleft() + if evt[0] == RESCHED: + if any(x[0] != RESCHED for x in self._evts): + self._evts.append(evt) + continue + + if evt[0] == ACTIVATE: + self.sched.on_activate(evt[1]) + self.monitor.observe(ProcOverheadEvent("JobActivation")) + self.sched.monitor_begin_activate(self) + yield hold, self, self.sched.overhead_activate + self.sched.monitor_end_activate(self) + elif evt[0] == TERMINATE: + self.sched.on_terminated(evt[1]) + self.monitor.observe(ProcOverheadEvent("JobTermination")) + self.sched.monitor_begin_terminate(self) + yield hold, self, self.sched.overhead_terminate + self.sched.monitor_end_terminate(self) + elif evt[0] == TIMER: + self.timer_monitor.observe(None) + if evt[1].overhead > 0: + print(self.sim.now(), "hold", evt[1].overhead) + yield hold, self, evt[1].overhead + evt[1].call_handler() + elif evt[0] == MIGRATE: + self._running = evt[1] + self.monitor.observe(ProcOverheadEvent("Migration")) + #yield hold, self, self._migration_overhead #overhead migration + elif evt[0] == SPEED: + self._speed = evt[1] + elif evt[0] == RESCHED: + self.monitor.observe(ProcOverheadEvent("Scheduling")) + self.sched.monitor_begin_schedule(self) + yield waituntil, self, self.sched.get_lock + decisions = self.sched.schedule(self) + yield hold, self, self.sched.overhead # overhead scheduling + if type(decisions) is not list: + decisions = [decisions] + + for decision in decisions: + if decision is None: + continue + else: + job, cpu = decision + + if cpu.running == job: + continue + + if job is not None and not job.is_active(): + print("Can't schedule a terminated job! ({})" + .format(job.name)) + continue + + # if the job was running somewhere else, stop it. + if job and job.cpu.running == job: + job.cpu.preempt() + + # Send that job to processor cpu. + if job is None: + cpu.preempt() + else: + cpu.migrate(job) + + if job: + job.task.cpu = cpu + + running_tasks = [ + cpu.running.name + for cpu in self._model.processors if cpu.running] + #if len(set(running_tasks)) != len(running_tasks): + # print(running_tasks) + assert len(set(running_tasks)) == len(running_tasks), \ + "Try to run a job on 2 processors simultaneously!" + + self.sched.release_lock() + self.sched.monitor_end_schedule(self) diff --git a/simso/core/Scheduler.py b/simso/core/Scheduler.py new file mode 100644 index 0000000..0685549 --- /dev/null +++ b/simso/core/Scheduler.py @@ -0,0 +1,258 @@ +from __future__ import print_function +import sys +import imp +import os.path + +from simso.core.SchedulerEvent import SchedulerBeginScheduleEvent, \ + SchedulerEndScheduleEvent, SchedulerBeginActivateEvent, \ + SchedulerEndActivateEvent, SchedulerBeginTerminateEvent, \ + SchedulerEndTerminateEvent +from SimPy.Simulation import Monitor + + +class SchedulerInfo(object): + """ + SchedulerInfo groups the data that characterize a Scheduler (such as the + scheduling overhead) and do the dynamic loading of the scheduler. + """ + def __init__(self, name='', cls=None, overhead=0, overhead_activate=0, + overhead_terminate=0, fields=None): + """ + Args: + - `name`: Name of the scheduler. + - `cls`: Class associated to this scheduler. + - `overhead`: Overhead associated to a scheduling decision. + + Methods: + """ + self._name = name + self._filename = None + self._cls = cls + self.overhead = overhead + self.overhead_activate = overhead_activate + self.overhead_terminate = overhead_terminate + self.data = {} + self.fields_types = {} + + if fields: + for key, value in fields.items(): + self.data[key] = value[0] + self.fields_types[key] = value[1] + + def set_fields(self, fields): + for key, value in fields.items(): + self.data[key] = value[0] + self.fields_types[key] = value[1] + + def set_name(self, filename, cur_dir=None): + """ + Set the scheduler from a file. + + Args: + - `filename`: relative path to the Python source containing the \ + Scheduler. + - `cur_dir`: current directory. Used to set the name relatively \ + to the simulation file. + """ + if cur_dir is None: + cur_dir = os.curdir + self._name = os.path.relpath(filename, cur_dir) + self._filename = filename + + @property + def name(self): + """ + The name of the Scheduler (its relative path to the XML). + """ + return self._name + + @property + def filename(self): + """ + Path of the scheduler (absolute or relative to simso's working + directory). + """ + return self._filename + + def get_cls(self): + """ + Get the class of this scheduler. + """ + if self._filename: + (path, name) = os.path.split(self._filename) + name = os.path.splitext(name)[0] + + try: + fp, pathname, description = imp.find_module(name, [path]) + if path not in sys.path: + sys.path.append(path) + self._cls = getattr(imp.load_module(name, fp, pathname, + description), name) + fp.close() + except ImportError as e: + print("ImportError: ", e) + print("name: ", name, "path: ", path) + + return self._cls + + def instantiate(self, model): + """ + Instantiate the :class:`Scheduler` class. + + Args: + - `model`: The :class:`Model ` object \ + that is passed to the constructor. + """ + cls = self.get_cls() + return cls(model, self) + + +class Scheduler(object): + """ + The implementation of a scheduler is done by subclassing this abstract + class. + + The scheduling events are modeled by method calls which take as arguments + the :class:`jobs ` and the :class:`processors + `. + + The following methods should be redefined in order to interact with the + simulation: + + - :meth:`init` Called when the simulation is ready. The scheduler \ + logic should be initialized here. + - :meth:`on_activate` Called upon a job activation. + - :meth:`on_terminated` Called when a job is terminated. + - :meth:`schedule` Take the scheduling decision. This method should \ + not be called directly. A call to the :meth:`resched \ + ` method is required. + + By default, the scheduler can only run on a single processor at the same + simulation time. It is also possible to override this behavior by + overriding the :meth:`get_lock` and :meth:`release_lock` methods. + """ + + def __init__(self, sim, scheduler_info, **kwargs): + """ + Args: + + - `sim`: :class:`Model ` instance. + - `scheduler_info`: A :class:`SchedulerInfo` representing the \ + scheduler. + + Attributes: + + - **sim**: :class:`Model ` instance. \ + Useful to get current time with ``sim.now_ms()`` (in ms) or \ + ``sim.now()`` (in cycles). + - **processors**: List of :class:`processors \ + ` handled by this scheduler. + - **task_list**: List of :class:`tasks ` \ + handled by this scheduler. + + Methods: + """ + self.sim = sim + self.processors = [] + self.task_list = [] + self._lock = False + self.overhead = scheduler_info.overhead + self.overhead_activate = scheduler_info.overhead_activate + self.overhead_terminate = scheduler_info.overhead_terminate + self.data = scheduler_info.data + self.monitor = Monitor(name="MonitorScheduler", sim=sim) + + def init(self): + """ + This method is called when the system is ready to run. This method + should be used in lieu of the __init__ method. This method is + guaranteed to be called when the simulation starts, after the tasks + are instantiated + """ + pass + + def on_activate(self, job): + """ + This method is called upon a job activation. + + Args: + - `job`: The activated :class:`job `. + """ + pass + + def on_terminated(self, job): + """ + This method is called when a job finish (termination or abortion). + + Args: + - `job`: The :class:`job ` that terminates . + """ + pass + + def schedule(self, cpu): + """ + The schedule method must be redefined by the simulated scheduler. + It takes as argument the cpu on which the scheduler runs. + + Args: + - `cpu`: The :class:`processor ` \ + on which the scheduler runs. + + Returns a decision or a list of decisions. A decision is a couple + (job, cpu). + """ + raise NotImplementedError("Function schedule to override!") + + def add_task(self, task): + """ + Add a task to the list of tasks handled by this scheduler. + + Args: + - `task`: The :class:`task ` to add. + """ + self.task_list.append(task) + + def add_processor(self, cpu): + """ + Add a processor to the list of processors handled by this scheduler. + + Args: + - `processor`: The :class:`processor \ + ` to add. + """ + self.processors.append(cpu) + + def get_lock(self): + """ + Implement a lock mechanism. Override it to remove the lock or change + its behavior. + """ + if not self._lock: + self._lock = True + else: + return False + return True + + def release_lock(self): + """ + Release the lock. Goes in pair with :meth:`get_lock`. + """ + self._lock = False + + def monitor_begin_schedule(self, cpu): + self.monitor.observe(SchedulerBeginScheduleEvent(cpu)) + + def monitor_end_schedule(self, cpu): + self.monitor.observe(SchedulerEndScheduleEvent(cpu)) + + def monitor_begin_activate(self, cpu): + self.monitor.observe(SchedulerBeginActivateEvent(cpu)) + + def monitor_end_activate(self, cpu): + self.monitor.observe(SchedulerEndActivateEvent(cpu)) + + def monitor_begin_terminate(self, cpu): + self.monitor.observe(SchedulerBeginTerminateEvent(cpu)) + + def monitor_end_terminate(self, cpu): + self.monitor.observe(SchedulerEndTerminateEvent(cpu)) diff --git a/simso/core/SchedulerEvent.py b/simso/core/SchedulerEvent.py new file mode 100644 index 0000000..180b008 --- /dev/null +++ b/simso/core/SchedulerEvent.py @@ -0,0 +1,50 @@ +# coding=utf-8 + + +class SchedulerEvent(object): + BEGIN_SCHEDULE = 1 + END_SCHEDULE = 2 + BEGIN_ACTIVATE = 3 + END_ACTIVATE = 4 + BEGIN_TERMINATE = 5 + END_TERMINATE = 6 + + def __init__(self, cpu): + self.event = 0 + self.cpu = cpu + + +class SchedulerBeginScheduleEvent(SchedulerEvent): + def __init__(self, cpu): + SchedulerEvent.__init__(self, cpu) + self.event = SchedulerEvent.BEGIN_SCHEDULE + + +class SchedulerEndScheduleEvent(SchedulerEvent): + def __init__(self, cpu): + SchedulerEvent.__init__(self, cpu) + self.event = SchedulerEvent.END_SCHEDULE + + +class SchedulerBeginActivateEvent(SchedulerEvent): + def __init__(self, cpu): + SchedulerEvent.__init__(self, cpu) + self.event = SchedulerEvent.BEGIN_ACTIVATE + + +class SchedulerEndActivateEvent(SchedulerEvent): + def __init__(self, cpu): + SchedulerEvent.__init__(self, cpu) + self.event = SchedulerEvent.END_ACTIVATE + + +class SchedulerBeginTerminateEvent(SchedulerEvent): + def __init__(self, cpu): + SchedulerEvent.__init__(self, cpu) + self.event = SchedulerEvent.BEGIN_TERMINATE + + +class SchedulerEndTerminateEvent(SchedulerEvent): + def __init__(self, cpu): + SchedulerEvent.__init__(self, cpu) + self.event = SchedulerEvent.END_TERMINATE diff --git a/simso/core/Task.py b/simso/core/Task.py new file mode 100644 index 0000000..da5ad2d --- /dev/null +++ b/simso/core/Task.py @@ -0,0 +1,359 @@ +# coding=utf-8 + +from collections import deque +from SimPy.Simulation import Process, Monitor, hold, passivate +from simso.core.Job import Job +from simso.core.Timer import Timer +from .CSDP import CSDP + +import os +import os.path + + +class TaskInfo(object): + """ + TaskInfo is mainly a container class grouping the data that characterize + a Task. A list of TaskInfo objects are passed to the Model so that + :class:`Task` instances can be created. + """ + + def __init__(self, name, identifier, task_type, abort_on_miss, period, + activation_date, n_instr, mix, stack_file, wcet, acet, + et_stddev, deadline, base_cpi, followed_by, + list_activation_dates, preemption_cost, data): + """ + :type name: str + :type identifier: int + :type task_type: str + :type abort_on_miss: bool + :type period: float + :type activation_date: float + :type n_instr: int + :type mix: float + :type stack_file: str + :type wcet: float + :type acet: float + :type et_stddev: float + :type deadline: float + :type base_cpi: float + :type followed_by: int + :type list_activation_dates: list + :type preemption_cost: int + :type data: dict + """ + self.name = name + self.identifier = identifier + self.task_type = task_type + self.period = period + self.activation_date = activation_date + self.n_instr = n_instr + self.mix = mix + self.wcet = wcet + self.acet = acet + self.et_stddev = et_stddev + self.base_cpi = base_cpi + self._stack = None + self._csdp = None + self._stack_file = '' + self.set_stack_file(*stack_file) + self.deadline = deadline + self.followed_by = followed_by + self.abort_on_miss = abort_on_miss + self.list_activation_dates = list_activation_dates + self.data = data + self.preemption_cost = preemption_cost + + @property + def csdp(self): + """ + Accumulated Stack Distance Profile. Used by the cache models instead of + the Stack Distance Profile for optimization matters. + """ + return self._csdp + + @property + def stack_file(self): + """ + Stack distance profile input file. + """ + return self._stack_file + + def set_stack_file(self, stack_file, cur_dir): + """ + Set the stack distance profile. + """ + if stack_file: + try: + self._stack = TaskInfo._parse_stack(stack_file) + self._csdp = CSDP(self._stack) + self._stack_file = os.path.relpath(stack_file, cur_dir) + except Exception as e: + print("set_stack_file failed:", e) + + @staticmethod + def _parse_stack(stack_file): + stack = {} + if stack_file and os.path.isfile(stack_file): + for line in open(stack_file): + dist, value = line.split() + stack[int(dist)] = float(value) + else: + stack = None + return stack + + +class GenericTask(Process): + """ + Abstract class for Tasks. :class:`ATask` and :class:`PTask` inherits from + this class. + + These classes simulate the behavior of the simulated task. It controls the + release of the jobs and is able to abort the jobs that exceed their + deadline. + + The majority of the task_info attributes are available through this class + too. A set of metrics such as the number of preemptions and migrations are + available for analysis. + """ + fields = [] + + def __init__(self, sim, task_info): + """ + Args: + + - `sim`: :class:`Model ` instance. + - `task_info`: A :class:`TaskInfo` representing the Task. + + :type sim: Model + :type task_info: TaskInfo + """ + Process.__init__(self, name=task_info.name, sim=sim) + self.name = task_info.name + self._task_info = task_info + self._monitor = Monitor(name="Monitor" + self.name + "_states", + sim=sim) + self._activations_fifo = deque([]) + self._sim = sim + self.cpu = None + self._etm = sim.etm + self._job_count = 0 + self._last_cpu = None + self._cpi_alone = {} + self._jobs = [] + self.job = None + + def __lt__(self, other): + return self.identifier < other.identifier + + def is_active(self): + return self.job is not None and self.job.is_active() + + def set_cpi_alone(self, proc, cpi): + self._cpi_alone[proc] = cpi + + def get_cpi_alone(self, proc=None): + if proc is None: + proc = self.cpu + return self._cpi_alone[proc] + + @property + def base_cpi(self): + return self._task_info.base_cpi + + @property + def data(self): + """ + Extra data to characterize the task. Only used by the scheduler. + """ + return self._task_info.data + + @property + def deadline(self): + """ + Deadline in milliseconds. + """ + return self._task_info.deadline + + @property + def n_instr(self): + return self._task_info.n_instr + + @property + def mix(self): + return self._task_info.mix + + @property + def csdp(self): + return self._task_info.csdp + + @property + def preemption_cost(self): + return self._task_info.preemption_cost + + @property + def footprint(self): + return int(self._task_info.n_instr * self._task_info.mix * + (1 - self._task_info.csdp.get(-1))) + + @property + def wcet(self): + """Worst-Case Execution Time in milliseconds.""" + return self._task_info.wcet + + @property + def acet(self): + return self._task_info.acet + + @property + def et_stddev(self): + return self._task_info.et_stddev + + @property + def period(self): + """ + Period of the task. + """ + return self._task_info.period + + @property + def identifier(self): + """ + Identifier of the task. + """ + return self._task_info.identifier + + @property + def monitor(self): + """ + The monitor for this Task. Similar to a log mechanism (see Monitor in + SimPy doc). + """ + return self._monitor + + @property + def followed_by(self): + """ + Task that is activated by the end of a job from this task. + """ + if self._task_info.followed_by is not None: + followed = [x for x in self._sim.task_list + if (x.identifier == self._task_info.followed_by)] + if followed: + return followed[0] + return None + + @property + def jobs(self): + """ + List of the jobs. + """ + return self._jobs + + def end_job(self, job): + self._last_cpu = self.cpu + if self.followed_by: + self.followed_by.create_job(job) + + if len(self._activations_fifo) > 0: + self._activations_fifo.popleft() + if len(self._activations_fifo) > 0: + self.job = self._activations_fifo[0] + self.sim.activate(self.job, self.job.activate_job()) + + def _job_killer(self, job): + if job.end_date is None and job.computation_time < job.wcet: + if self._task_info.abort_on_miss: + self.cancel(job) + job.abort() + + def create_job(self, pred=None): + """ + Create a new job from this task. This should probably not be used + directly by a scheduler. + """ + self._job_count += 1 + job = Job(self, "{}_{}".format(self.name, self._job_count), pred, + monitor=self._monitor, etm=self._etm, sim=self.sim) + + if len(self._activations_fifo) == 0: + self.job = job + self.sim.activate(job, job.activate_job()) + self._activations_fifo.append(job) + self._jobs.append(job) + + timer_deadline = Timer(self.sim, GenericTask._job_killer, + (self, job), self.deadline) + timer_deadline.start() + + def _init(self): + if self.cpu is None: + self.cpu = self._sim.processors[0] + + +class ATask(GenericTask): + """ + Non-periodic Task process. Inherits from :class:`GenericTask`. The job is + created by another task. + """ + fields = ['deadline', 'wcet'] + + def execute(self): + self._init() + yield passivate, self + + +class PTask(GenericTask): + """ + Periodic Task process. Inherits from :class:`GenericTask`. The jobs are + created periodically. + """ + fields = ['activation_date', 'period', 'deadline', 'wcet'] + + def execute(self): + self._init() + # wait the activation date. + yield hold, self, int(self._task_info.activation_date * + self._sim.cycles_per_ms) + + while True: + #print self.sim.now(), "activate", self.name + self.create_job() + yield hold, self, int(self.period * self._sim.cycles_per_ms) + + +class SporadicTask(GenericTask): + """ + Sporadic Task process. Inherits from :class:`GenericTask`. The jobs are + created using a list of activation dates. + """ + fields = ['list_activation_dates', 'deadline', 'wcet'] + + def execute(self): + + self._init() + for ndate in self.list_activation_dates: + yield hold, self, int(ndate * self._sim.cycles_per_ms) \ + - self._sim.now() + self.create_job() + + @property + def list_activation_dates(self): + return self._task_info.list_activation_dates + + +task_types = { + "Periodic": PTask, + "APeriodic": ATask, + "Sporadic": SporadicTask +} + +task_types_names = ["Periodic", "APeriodic", "Sporadic"] + + +def Task(sim, task_info): + """ + Task factory. Return and instantiate the correct class according to the + task_info. + """ + + return task_types[task_info.task_type](sim, task_info) diff --git a/simso/core/Timer.py b/simso/core/Timer.py new file mode 100644 index 0000000..f410e4d --- /dev/null +++ b/simso/core/Timer.py @@ -0,0 +1,99 @@ +# coding=utf-8 + +from SimPy.Simulation import Process, hold + +# TODO: allow the user to specify an overhead. + + +class InstanceTimer(Process): + def __init__(self, timer): + Process.__init__(self, name="Timer", sim=timer.sim) + self.function = timer.function + self.args = timer.args + self.delay = timer.delay + self.one_shot = timer.one_shot + self.cpu = timer.cpu + self.running = False + self.overhead = timer.overhead + + def call_handler(self): + if self.running: + self.function(*self.args) + + def run(self): + self.running = True + while self.running: + yield hold, self, self.delay + if self.interrupted() or not self.running: + break + if self.cpu: + self.cpu.timer(self) + else: + self.call_handler() + if self.one_shot: + break + + +class Timer(object): + """ + Allow to declare a timer. A timer is a mechanism that allows to call a + function after a certain amount of time, periodically or single shot. + + A Timer can be used with or without specifying a processor. If a processor + is specified, when the timer fire, if a job was running on the processor, + it is temporarly interrupted. This is more realistic, even if for the + moment there is no overhead associated to this action. A scheduler using a + timer should define on which processor the callback will execute. + + The delay is expressed in milliseconds by default but it can also be given + in cycles. + """ + def __init__(self, sim, function, args, delay, one_shot=True, prior=False, + cpu=None, in_ms=True, overhead=0): + """ + Args: + - `sim`: The :class:`model ` object. + - `function`: Callback function, called when the delay expires. + - `args`: Arguments passed to the callback function. + - `delay`: Time to wait before calling the function. + - `one_shot`: True if the timer should execute only once. + - `prior`: If true, for the same date, the simulation should \ + start by handling the timer (should probably not be True). + - `cpu`: On which :class:`processor \ + ` the function is virtually \ + executing. + - `in_ms`: True if the delay is expressed in millisecond. In \ + cycles otherwise. + + Methods: + """ + self.sim = sim + self.function = function + self.args = args + if in_ms: + self.delay = int(delay * sim.cycles_per_ms) + else: + self.delay = int(delay) + self.one_shot = one_shot + self.prior = prior + self.cpu = cpu + self.instance = None + if in_ms: + self.overhead = int(overhead * sim.cycles_per_ms) + else: + self.overhead = int(overhead) + assert self.delay >= 0, "delay must be >= 0" + + def start(self): + """ + Start the timer. + """ + self.instance = InstanceTimer(self) + self.sim.activate(self.instance, self.instance.run(), self.prior) + + def stop(self): + """ + Stop the timer. + """ + if self.instance: + self.instance.running = False diff --git a/simso/core/__init__.py b/simso/core/__init__.py new file mode 100644 index 0000000..e9eb171 --- /dev/null +++ b/simso/core/__init__.py @@ -0,0 +1,11 @@ +""" +The core module include all the classes needed for the simulation. +""" + +from simso.core.JobEvent import JobEvent +from simso.core.ProcEvent import ProcEvent +from simso.core.Model import Model +from simso.core.Processor import Processor +from simso.core.Scheduler import Scheduler +from simso.core.Timer import Timer +from simso.core.results import Results \ No newline at end of file diff --git a/simso/core/etm/ACET.py b/simso/core/etm/ACET.py new file mode 100644 index 0000000..5ce81f8 --- /dev/null +++ b/simso/core/etm/ACET.py @@ -0,0 +1,59 @@ +from simso.core.etm.AbstractExecutionTimeModel \ + import AbstractExecutionTimeModel +import random + +# TODO: the seed should be specified in order to evaluate on identical systems. +# More precisely, the computation time of the jobs should remain the same. + + +class ACET(AbstractExecutionTimeModel): + def __init__(self, sim, _): + self.sim = sim + self.et = {} + self.executed = {} + self.on_execute_date = {} + + def init(self): + pass + + def update_executed(self, job): + if job in self.on_execute_date: + self.executed[job] += (self.sim.now() - self.on_execute_date[job] + ) * job.cpu.speed + + del self.on_execute_date[job] + + def on_activate(self, job): + self.executed[job] = 0 + self.et[job] = min( + job.task.wcet, + random.normalvariate(job.task.acet, job.task.et_stddev) + ) * self.sim.cycles_per_ms + + def on_execute(self, job): + self.on_execute_date[job] = self.sim.now() + + def on_preempted(self, job): + self.update_executed(job) + + def on_terminated(self, job): + self.update_executed(job) + del self.et[job] + + def on_abort(self, job): + self.update_executed(job) + del self.et[job] + + def get_executed(self, job): + if job in self.on_execute_date: + c = (self.sim.now() - self.on_execute_date[job]) * job.cpu.speed + else: + c = 0 + return self.executed[job] + c + + def get_ret(self, job): + return int(self.et[job] - self.get_executed(job)) + + def update(self): + for job in list(self.on_execute_date.keys()): + self.update_executed(job) diff --git a/simso/core/etm/AbstractExecutionTimeModel.py b/simso/core/etm/AbstractExecutionTimeModel.py new file mode 100644 index 0000000..c169204 --- /dev/null +++ b/simso/core/etm/AbstractExecutionTimeModel.py @@ -0,0 +1,40 @@ +import abc + + +class AbstractExecutionTimeModel(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def init(self): + pass + + @abc.abstractmethod + def on_activate(self, _): + pass + + @abc.abstractmethod + def update(self): + pass + + @abc.abstractmethod + def on_execute(self, _): + pass + + @abc.abstractmethod + def on_preempted(self, _): + pass + + @abc.abstractmethod + def on_terminated(self, _): + pass + + @abc.abstractmethod + def on_abort(self, _): + pass + + @abc.abstractmethod + def get_ret(self, _): + return + + def get_executed(self, job): + return job.computation_time_cycles diff --git a/simso/core/etm/CacheModel.py b/simso/core/etm/CacheModel.py new file mode 100644 index 0000000..c5d39cf --- /dev/null +++ b/simso/core/etm/CacheModel.py @@ -0,0 +1,132 @@ +# coding=utf-8 + +from simso.core.etm.AbstractExecutionTimeModel \ + import AbstractExecutionTimeModel + + +def calc_cpi(base_cpi, mix, miss_rates, penalties): + """ + Compute the CPI using the miss_rates and penalties. + """ + penalty_per_memaccess = penalties[0] + for mp, mr in zip(penalties[1:], miss_rates): + penalty_per_memaccess += mp * mr + return base_cpi + mix * penalty_per_memaccess + + +def capacity_miss_LRU(csdp, cache_size): + """ + Capacity miss rate using an LRU cache. + """ + return 1.0 - csdp.get(int(cache_size + .5)) + + +def cpi_alone(task, cache_sizes, penalties): + miss_rates = [capacity_miss_LRU(task.csdp, cache_size) + for cache_size in cache_sizes] + return calc_cpi(task.base_cpi, task.mix, miss_rates, penalties) + + +def calc_cache_sizes(caches, task, running_jobs): + """ + Compute the virtual size of the cache taking into account the other running + tasks (FOA model). + """ + result = [] + for cache in caches: + shared_jobs = [j for j in running_jobs if j.cpu in cache.shared_with] + sum_af = sum(j.task.mix / j.task.get_cpi_alone() for j in shared_jobs) + proportion = (task.mix / task.get_cpi_alone()) / sum_af + + result.append(cache.size * proportion) + return result + + +def compute_instructions(task, running_jobs, duration): + caches = task.cpu.caches + penalties = [task.cpu.penalty_memaccess] + [c.penalty for c in caches] + sizes = calc_cache_sizes(caches, task, running_jobs) + miss_rates = [capacity_miss_LRU(task.csdp, size) for size in sizes] + return duration / calc_cpi(task.base_cpi, task.mix, miss_rates, penalties) + + +class CacheModel(AbstractExecutionTimeModel): + def __init__(self, sim, nb_processors): + self.sim = sim + self._nb_processors = nb_processors + + def init(self): + self._last_update = 0 + self._running_jobs = set() + self._instr_jobs = {} + self._total_preemptions_cost = 0 + self.running = {} + self.penalty = {} + self.was_running_on = {} + + # precompute cpi_alone for each task on each cpu + for task in self.sim.task_list: + for proc in self.sim.processors: + caches = proc.caches + task.set_cpi_alone( + proc, + cpi_alone(task, [c.size for c in caches], + [proc.penalty_memaccess] + + [c.penalty for c in caches]) + ) + + def update(self): + self._update_instructions() + + def _update_instructions(self): + for job in self._running_jobs: + # Compute number of instr for self.sim.now() - last_update + instr = compute_instructions(job.task, self._running_jobs, + self.sim.now() - self._last_update) + # Update the number of instr for this job + self._instr_jobs[job] = self._instr_jobs.get(job, 0) + instr + + # Update last_update + self._last_update = self.sim.now() + + def on_activate(self, job): + self.penalty[job] = 0 + + def on_execute(self, job): + # Compute penalty. + if job in self.was_running_on: + # resume on the same processor. + if self.was_running_on[job] is job.cpu: + if self.running[job.cpu] is not job: + self.penalty[job] += job.task.preemption_cost + else: # migration. + self.penalty[job] += job.task.preemption_cost + + self.running[job.cpu] = job + self.was_running_on[job] = job.cpu + + # Update the number of instructions executed for the running jobs. + self._update_instructions() + # Add the job in the list of running jobs. + self._running_jobs.add(job) + + def _stop_job(self, job): + # Update the number of instructions executed for the running jobs. + self._update_instructions() + # Remove the job from the list of running jobs. + self._running_jobs.remove(job) + + def on_preempted(self, job): + self._stop_job(job) + + def on_terminated(self, job): + self._stop_job(job) + + def on_abort(self, job): + self._stop_job(job) + + def get_ret(self, job): + self._update_instructions() + penalty = self.penalty[job] + return (job.task.n_instr - self._instr_jobs[job]) \ + * job.task.get_cpi_alone() + penalty diff --git a/simso/core/etm/FixedPenalty.py b/simso/core/etm/FixedPenalty.py new file mode 100644 index 0000000..c3abb56 --- /dev/null +++ b/simso/core/etm/FixedPenalty.py @@ -0,0 +1,67 @@ +from simso.core.etm.AbstractExecutionTimeModel \ + import AbstractExecutionTimeModel + + +class FixedPenalty(AbstractExecutionTimeModel): + def __init__(self, sim, _): + self.sim = sim + self.running = {} + self.penalty = {} + self.was_running_on = {} + self.executed = {} + self.on_execute_date = {} + + def init(self): + pass + + def update_executed(self, job): + if job in self.on_execute_date: + self.executed[job] += (self.sim.now() - self.on_execute_date[job] + ) * job.cpu.speed + + del self.on_execute_date[job] + + def on_activate(self, job): + self.penalty[job] = 0 + self.executed[job] = 0 + + def on_execute(self, job): + self.on_execute_date[job] = self.sim.now() + if job in self.was_running_on: + # resume on the same processor. + if self.was_running_on[job] is job.cpu: + if self.running[job.cpu] is not job: + self.penalty[job] += self.sim.penalty_preemption + else: # migration. + self.penalty[job] += self.sim.penalty_migration + + self.running[job.cpu] = job + self.was_running_on[job] = job.cpu + + def on_preempted(self, job): + self.executed[job] += (self.sim.now() - self.on_execute_date[job] + ) * job.cpu.speed + + def on_terminated(self, job): + if job in self.on_execute_date: + del self.on_execute_date[job] + + def on_abort(self, job): + if job in self.on_execute_date: + del self.on_execute_date[job] + + def get_executed(self, job): + if job in self.on_execute_date: + c = (self.sim.now() - self.on_execute_date[job]) * job.cpu.speed + else: + c = 0 + return self.executed[job] + c + + def get_ret(self, job): + wcet_cycles = int(job.wcet * self.sim.cycles_per_ms) + penalty = self.penalty[job] + return int(wcet_cycles + penalty - job.computation_time_cycles) + + def update(self): + for job in list(self.on_execute_date.keys()): + self.update_executed(job) diff --git a/simso/core/etm/WCET.py b/simso/core/etm/WCET.py new file mode 100644 index 0000000..afaa37f --- /dev/null +++ b/simso/core/etm/WCET.py @@ -0,0 +1,49 @@ +from simso.core.etm.AbstractExecutionTimeModel \ + import AbstractExecutionTimeModel + + +class WCET(AbstractExecutionTimeModel): + def __init__(self, sim, _): + self.sim = sim + self.executed = {} + self.on_execute_date = {} + + def init(self): + pass + + def update_executed(self, job): + if job in self.on_execute_date: + self.executed[job] += (self.sim.now() - self.on_execute_date[job] + ) * job.cpu.speed + + del self.on_execute_date[job] + + def on_activate(self, job): + self.executed[job] = 0 + + def on_execute(self, job): + self.on_execute_date[job] = self.sim.now() + + def on_preempted(self, job): + self.update_executed(job) + + def on_terminated(self, job): + self.update_executed(job) + + def on_abort(self, job): + self.update_executed(job) + + def get_executed(self, job): + if job in self.on_execute_date: + c = (self.sim.now() - self.on_execute_date[job]) * job.cpu.speed + else: + c = 0 + return self.executed[job] + c + + def get_ret(self, job): + wcet_cycles = int(job.wcet * self.sim.cycles_per_ms) + return int(wcet_cycles - self.get_executed(job)) + + def update(self): + for job in list(self.on_execute_date.keys()): + self.update_executed(job) diff --git a/simso/core/etm/__init__.py b/simso/core/etm/__init__.py new file mode 100644 index 0000000..14329a4 --- /dev/null +++ b/simso/core/etm/__init__.py @@ -0,0 +1,18 @@ +from .WCET import WCET +from .ACET import ACET +from .CacheModel import CacheModel +from .FixedPenalty import FixedPenalty + +execution_time_models = { + 'wcet': WCET, + 'acet': ACET, + 'cache': CacheModel, + 'fixedpenalty': FixedPenalty +} + +execution_time_model_names = { + 'WCET': 'wcet', + 'ACET': 'acet', + 'Cache Model': 'cache', + 'Fixed Penalty': 'fixedpenalty' +} diff --git a/simso/core/results.py b/simso/core/results.py new file mode 100644 index 0000000..cb1f0b6 --- /dev/null +++ b/simso/core/results.py @@ -0,0 +1,423 @@ +from simso.core.ProcEvent import ProcEvent +from simso.core.JobEvent import JobEvent +from simso.core.SchedulerEvent import SchedulerEvent + + +class ProcessorR(object): + """ + Add information about a processor such as the number of CxtSave and + CxtLoad and their total overhead. + """ + def __init__(self): + self.context_save_overhead = 0 + self.context_save_count = 0 + self.context_load_overhead = 0 + self.context_load_count = 0 + + +class SchedulerR(object): + """ + Add information about the scheduler such as the number of scheduling + events and their total overhead. + """ + def __init__(self): + self.schedule_overhead = 0 + self.activate_overhead = 0 + self.terminate_overhead = 0 + self.schedule_count = 0 + self.activate_count = 0 + self.terminate_count = 0 + + +class TaskR(object): + """ + Add a set of metrics to a task. These metrics include: task_migrations, + abortion count, etc. + + The attribute jobs contains a list of JobR, sorted by activation date. + """ + def __init__(self, task, delta_preemption=100): + self.task = task + self.delta_preemption = delta_preemption + self.jobs = [] + self.waiting_jobs = [] + self.resumptions = [] + self.task_migrations = [] + self.abort_count = 0 + self.execute_date = None + self.preempt_date = None + self.cpu = None + self.other_executed = False + + def add_job(self, date, job): + jobr = JobR(date, job) + self.jobs.append(jobr) + self.waiting_jobs.append(jobr) + if len(self.waiting_jobs) == 1: + jobr.start(date) + + def terminate_job(self, date): + if self.waiting_jobs: + self.preempt(date) + self.waiting_jobs[0].terminate(date) + self.waiting_jobs.pop(0) + if self.waiting_jobs: + self.waiting_jobs[0].start(date) + self.preempt_date = None + + def abort_job(self, date): + if self.waiting_jobs: + self.preempt(date) + self.waiting_jobs[0].abort(date) + self.waiting_jobs.pop(0) + self.abort_count += 1 + if self.waiting_jobs: + self.waiting_jobs[0].start(date) + self.preempt_date = None + + def execute(self, date, cpu): + if self.waiting_jobs: + if self.waiting_jobs[0].computation_time == 0: + if self.cpu == cpu or self.cpu is None: + self.resumptions.append((date, self.waiting_jobs[0])) + else: + self.task_migrations.append((date, self.waiting_jobs[0])) + else: + if self.cpu == cpu: + self.waiting_jobs[0].preemption_count += 1 + if date - self.preempt_date > self.delta_preemption: + self.waiting_jobs[0].preemption_delta_count += 1 + if self.other_executed: + self.waiting_jobs[0].preemption_inter_count += 1 + else: + self.waiting_jobs[0].migration_count += 1 + if date - self.preempt_date > self.delta_preemption: + self.waiting_jobs[0].migration_delta_count += 1 + + self.execute_date = date + self.cpu = cpu + self.preempt_date = None + + def preempt(self, date): + if (self.execute_date is not None + and self.execute_date < date and self.waiting_jobs): + self.waiting_jobs[0].add_exec_time(date - self.execute_date) + self.execute_date = None + self.preempt_date = date + self.other_executed = False + + @property + def resumption_count(self): + return len(self.resumptions) + + @property + def task_migration_count(self): + return len(self.task_migrations) + + @property + def exceeded_count(self): + count = 0 + for job in self.jobs: + if job.exceeded_deadline: + count += 1 + return count + + @property + def migration_count(self): + return sum(job.migration_count for job in self.jobs) + + @property + def preemption_count(self): + return sum(job.preemption_count for job in self.jobs) + + @property + def preemption_inter_count(self): + return sum(job.preemption_inter_count for job in self.jobs) + + @property + def name(self): + return self.task.name + + +class JobR(object): + """ + Add a set of metrics to a job. Such metrics include: preemption count, + migration count, response time, etc. + """ + def __init__(self, date, job): + self.job = job + self.preemption_count = 0 + self.preemption_delta_count = 0 + self.preemption_inter_count = 0 + self.migration_count = 0 + self.migration_delta_count = 0 + self.activation_date = date + self.aborted = False + self.computation_time = 0 + self.end_date = None + self.response_time = None + self.start_date = None + self.absolute_deadline = job.absolute_deadline_cycles + + def terminate(self, date): + self.end_date = date + self.response_time = date - self.activation_date + + def abort(self, date): + self.aborted = True + self.end_date = date + self.response_time = date - self.activation_date + + def add_exec_time(self, duration): + self.computation_time += duration + + def start(self, date): + self.start_date = date + + @property + def name(self): + return self.job.name + + @property + def exceeded_deadline(self): + return self.end_date and (self.end_date > self.absolute_deadline + or self.aborted) + + @property + def normalized_laxity(self): + return ((self.job.task.deadline - float(self.response_time) + / self.job.sim.cycles_per_ms) / self.job.task.period) + + @property + def task(self): + return self.job.task + + +class Results(object): + """ + This class embeds and analyzes all the results from the simulation. + This allows to retrieve the usual metrics. + + The Results instance object contains the following attributes: + - `tasks`: a dictionary of TaskR where the key is the original Task. + - `scheduler`: a SchedulerR instance. + - `processors`: a dictionary of ProcessorR where the key is the \ + original Processor. + + . + """ + def __init__(self, model): + self.model = model + self.error = None + self._observation_window = None + + self.tasks = {} + self.scheduler = None + self.processors = {} + self.total_timers = 0 + self.timers = None + + def end(self): + self._analyze() + + def tasks_event(self): + """ + Generator of the tasks events sorted by their date. + """ + monitors = {} + indices = {} + for task in self.model.task_list: + monitors[task] = task.monitor + indices[task] = 0 + + while True: + m = None + for task in self.model.task_list: + if indices[task] < len(monitors[task]): + evt = monitors[task][indices[task]] + if m is None or evt[1].id_ < m[0][1].id_: + m = (evt, task) + if m is None: + break + indices[m[1]] += 1 + yield m + + def _generate_tasks(self): + self.tasks = {} + + for task in self.model.task_list: + self.tasks[task] = TaskR(task) + + for evt, task in self.tasks_event(): + if (evt[0] < self.observation_window[0] or + evt[0] > self.observation_window[1]): + # The events that start before the observation window should + # maybe be stored... + continue + if evt[1].event == JobEvent.ACTIVATE: + self.tasks[task].add_job(evt[0], evt[1].job) + elif evt[1].event == JobEvent.TERMINATED: + self.tasks[task].terminate_job(evt[0]) + elif evt[1].event == JobEvent.ABORTED: + self.tasks[task].abort_job(evt[0]) + elif evt[1].event == JobEvent.EXECUTE: + self.tasks[task].execute(evt[0], evt[1].cpu) + for rt in self.tasks.values(): + if rt.preempt_date and evt[1].cpu == rt.cpu: + rt.other_executed = True + elif evt[1].event == JobEvent.PREEMPTED: + self.tasks[task].preempt(evt[0]) + + def _generate_scheduler(self): + self.scheduler = SchedulerR() + last = self.observation_window[0] + for t, evt in self.model.scheduler.monitor: + if (t < self.observation_window[0] or + t > self.observation_window[1]): + continue + + if evt.event == SchedulerEvent.BEGIN_SCHEDULE: + self.scheduler.schedule_count += 1 + elif evt.event == SchedulerEvent.END_SCHEDULE: + self.scheduler.schedule_overhead += t - last + elif evt.event == SchedulerEvent.BEGIN_ACTIVATE: + self.scheduler.activate_count += 1 + elif evt.event == SchedulerEvent.END_ACTIVATE: + self.scheduler.activate_overhead += t - last + elif evt.event == SchedulerEvent.BEGIN_TERMINATE: + self.scheduler.terminate_count += 1 + elif evt.event == SchedulerEvent.END_TERMINATE: + self.scheduler.terminate_overhead += t - last + last = t + + def _generate_processors(self): + self.processors = {} + for proc in self.model.processors: + proc_r = ProcessorR() + self.processors[proc] = proc_r + last = self.observation_window[0] + for t, evt in proc.monitor: + if (t < self.observation_window[0] or + t > self.observation_window[1]): + continue + if evt.event == ProcEvent.OVERHEAD and evt.args == "CS": + if evt.terminated: + proc_r.context_save_overhead += t - last + else: + proc_r.context_save_count += 1 + if evt.event == ProcEvent.OVERHEAD and evt.args == "CL": + if evt.terminated: + proc_r.context_load_overhead += t - last + else: + proc_r.context_load_count += 1 + last = t + + def _compute_timers(self): + self.total_timers = 0 + self.timers = {} + for proc in self.model.processors: + self.timers[proc] = 0 + for t, evt in proc.timer_monitor: + if (t < self.observation_window[0] or + t > self.observation_window[1]): + continue + self.total_timers += 1 + self.timers[proc] += 1 + + def _analyze(self): + self._generate_tasks() + self._generate_scheduler() + self._generate_processors() + self._compute_timers() + + def get_observation_window(self): + """ + Get the observation window. + """ + if self._observation_window is None: + self._observation_window = (0, self.model.now()) + return self._observation_window + + def set_observation_window(self, window): + """ + Set the observation window. The events that occurs outside of the + observation window are discarded. + """ + self._observation_window = window + self._analyze() + + observation_window = property(get_observation_window, + set_observation_window) + + @property + def observation_window_duration(self): + return self.observation_window[1] - self.observation_window[0] + + @property + def total_migrations(self): + migrations = 0 + for task in self.tasks.values(): + migrations += task.migration_count + return migrations + + @property + def total_preemptions(self): + preemptions = 0 + for task in self.tasks.values(): + preemptions += task.preemption_count + return preemptions + + @property + def total_task_migrations(self): + migrations = 0 + for task in self.tasks.values(): + migrations += task.task_migration_count + return migrations + + @property + def total_task_resumptions(self): + resumptions = 0 + for task in self.tasks.values(): + resumptions += task.resumption_count + return resumptions + + @property + def total_exceeded_count(self): + count = 0 + for task in self.tasks.values(): + count += task.exceeded_count + return count + + def calc_load(self): + """ + Yield a tuple (proc, load, overhead) for each processor. + """ + for proc in self.model.processors: + sum_run = 0 + sum_overhead = 0 + last_event = ProcEvent.IDLE + x1 = self.observation_window[0] + for evt in proc.monitor: + current_date = evt[0] + if current_date < self.observation_window[0]: + last_event = evt[1].event + continue + if current_date >= self.observation_window[1]: + break + + if last_event == ProcEvent.RUN: + sum_run += current_date - x1 + elif last_event == ProcEvent.OVERHEAD: + sum_overhead += current_date - x1 + + x1 = current_date + last_event = evt[1].event + + if last_event == ProcEvent.RUN: + sum_run += self.observation_window[1] - x1 + elif last_event == ProcEvent.OVERHEAD: + sum_overhead += self.observation_window[1] - x1 + + yield (proc, + float(sum_run) / self.observation_window_duration, + float(sum_overhead) / self.observation_window_duration) diff --git a/simso/generator/__init__.py b/simso/generator/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/simso/generator/__init__.py diff --git a/simso/generator/task_generator.py b/simso/generator/task_generator.py new file mode 100755 index 0000000..5f254f5 --- /dev/null +++ b/simso/generator/task_generator.py @@ -0,0 +1,312 @@ +""" +Tools for generating task sets. +""" + +import numpy as np +import random +import math + + +def UUniFastDiscard(n, u, nsets): + sets = [] + while len(sets) < nsets: + # Classic UUniFast algorithm: + utilizations = [] + sumU = u + for i in range(1, n): + nextSumU = sumU * random.random() ** (1.0 / (n - i)) + utilizations.append(sumU - nextSumU) + sumU = nextSumU + utilizations.append(nextSumU) + + # If no task utilization exceeds 1: + if not [ut for ut in utilizations if ut > 1]: + sets.append(utilizations) + + return sets + + +def StaffordRandFixedSum(n, u, nsets): + """ + Copyright 2010 Paul Emberson, Roger Stafford, Robert Davis. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The views and conclusions contained in the software and documentation are + those of the authors and should not be interpreted as representing official + policies, either expressed or implied, of Paul Emberson, Roger Stafford or + Robert Davis. + + Includes Python implementation of Roger Stafford's randfixedsum implementation + http://www.mathworks.com/matlabcentral/fileexchange/9700 + Adapted specifically for the purpose of taskset generation with fixed + total utilisation value + + Please contact paule@rapitasystems.com or robdavis@cs.york.ac.uk if you have + any questions regarding this software. + """ + if n < u: + return None + + #deal with n=1 case + if n == 1: + return np.tile(np.array([u]), [nsets, 1]) + + k = min(int(u), n - 1) + s = u + s1 = s - np.arange(k, k - n, -1.) + s2 = np.arange(k + n, k, -1.) - s + + tiny = np.finfo(float).tiny + huge = np.finfo(float).max + + w = np.zeros((n, n + 1)) + w[0, 1] = huge + t = np.zeros((n - 1, n)) + + for i in np.arange(2, n + 1): + tmp1 = w[i - 2, np.arange(1, i + 1)] * s1[np.arange(0, i)] / float(i) + tmp2 = w[i - 2, np.arange(0, i)] * s2[np.arange(n - i, n)] / float(i) + w[i - 1, np.arange(1, i + 1)] = tmp1 + tmp2 + tmp3 = w[i - 1, np.arange(1, i + 1)] + tiny + tmp4 = s2[np.arange(n - i, n)] > s1[np.arange(0, i)] + t[i - 2, np.arange(0, i)] = (tmp2 / tmp3) * tmp4 + \ + (1 - tmp1 / tmp3) * (np.logical_not(tmp4)) + + x = np.zeros((n, nsets)) + rt = np.random.uniform(size=(n - 1, nsets)) # rand simplex type + rs = np.random.uniform(size=(n - 1, nsets)) # rand position in simplex + s = np.repeat(s, nsets) + j = np.repeat(k + 1, nsets) + sm = np.repeat(0, nsets) + pr = np.repeat(1, nsets) + + for i in np.arange(n - 1, 0, -1): # iterate through dimensions + # decide which direction to move in this dimension (1 or 0): + e = rt[(n - i) - 1, ...] <= t[i - 1, j - 1] + sx = rs[(n - i) - 1, ...] ** (1.0 / i) # next simplex coord + sm = sm + (1.0 - sx) * pr * s / (i + 1) + pr = sx * pr + x[(n - i) - 1, ...] = sm + pr * e + s = s - e + j = j - e # change transition table column if required + + x[n - 1, ...] = sm + pr * s + + #iterated in fixed dimension order but needs to be randomised + #permute x row order within each column + for i in range(0, nsets): + x[..., i] = x[np.random.permutation(n), i] + + return x.T.tolist() + + +def gen_ripoll(nsets, compute, deadline, period, target_util): + """ + Ripoll et al. tasksets generator. + + Args: + - `nsets`: Number of tasksets to generate. + - `compute`: Maximum computation time of a task. + - `deadline`: Maximum slack time. + - `period`: Maximum delay after the deadline. + - `target_util`: Total utilization to reach. + """ + sets = [] + for i in range(nsets): + task_set = [] + total_util = 0.0 + while total_util < target_util: + c = random.randint(1, compute) + d = c + random.randint(0, deadline) + p = d + random.randint(0, period) + task_set.append((c, d, p)) + total_util += c / p + sets.append(task_set) + return sets + + +def gen_uunifastdiscard(nsets, u, n): + """ + The UUniFast algorithm was proposed by Bini for generating task + utilizations on uniprocessor architectures. + + The UUniFast-Discard algorithm extends it to multiprocessor by + discarding task sets containing any utilization that exceeds 1. + + This algorithm is easy and widely used. However, it suffers from very + long computation times when n is close to u. Stafford's algorithm is + faster. + + Args: + - `n`: The number of tasks in a task set. + - `u`: Total utilization of the task set. + - `nsets`: Number of sets to generate. + + Returns `nsets` of `n` task utilizations. + """ + return UUniFastDiscard(u, n, nsets) + + +def gen_randfixedsum(nsets, u, n): + """ + Stafford's RandFixedSum algorithm implementated in Python. + + Based on the Python implementation given by Paul Emberson, Roger Stafford, + and Robert Davis. Available under the Simplified BSD License. + + Args: + - `n`: The number of tasks in a task set. + - `u`: Total utilization of the task set. + - `nsets`: Number of sets to generate. + """ + return StaffordRandFixedSum(u, n, nsets) + + +def gen_kato_utilizations(nsets, umin, umax, target_util): + """ + Kato et al. tasksets generator. + + Args: + - `nsets`: Number of tasksets to generate. + - `umin`: Minimum task utilization. + - `umax`: Maximum task utilization. + - `target_util`: + """ + sets = [] + for i in range(nsets): + task_set = [] + total_util = 0.0 + while total_util < target_util: + u = random.uniform(umin, umax) + if u + total_util > target_util: + u = target_util - total_util + total_util += u + task_set.append(u) + sets.append(task_set) + return sets + + +def next_arrival_poisson(period): + return -math.log(1.0 - random.random()) * period + + +def gen_arrivals(period, min_, max_, round_to_int=False): + def trunc(x, p): + return int(x * 10 ** p) / float(10 ** p) + + dates = [] + n = min_ - period + while True: + n += next_arrival_poisson(period) + period + if round_to_int: + n = int(round(n)) + else: + n = trunc(n, 6) + if n > max_: + break + dates.append(n) + return dates + + +def gen_periods_loguniform(n, nsets, min_, max_, round_to_int=False): + """ + Generate a list of `nsets` sets containing each `n` random periods using a + loguniform distribution. + + Args: + - `n`: The number of tasks in a task set. + - `nsets`: Number of sets to generate. + - `min_`: Period min. + - `max_`: Period max. + """ + periods = np.exp(np.random.uniform(low=np.log(min_), high=np.log(max_), + size=(nsets, n))) + if round_to_int: + return np.rint(periods).tolist() + else: + return periods.tolist() + + +def gen_periods_uniform(n, nsets, min_, max_, round_to_int=False): + """ + Generate a list of `nsets` sets containing each `n` random periods using a + uniform distribution. + + Args: + - `n`: The number of tasks in a task set. + - `nsets`: Number of sets to generate. + - `min_`: Period min. + - `max_`: Period max. + """ + periods = np.random.uniform(low=min_, high=max_, size=(nsets, n)) + + if round_to_int: + return np.rint(periods).tolist() + else: + return periods.tolist() + + +def gen_periods_discrete(n, nsets, periods): + """ + Generate a matrix of (nsets x n) random periods chosen randomly in the + list of periods. + + Args: + - `n`: The number of tasks in a task set. + - `nsets`: Number of sets to generate. + - `periods`: A list of available periods. + """ + try: + return np.random.choice(periods, size=(nsets, n)).tolist() + except AttributeError: + # Numpy < 1.7: + p = np.array(periods) + return p[np.random.randint(len(p), size=(nsets, n))].tolist() + + +def gen_tasksets(utilizations, periods): + """ + Take a list of task utilization sets and a list of task period sets and + return a list of couples (c, p) sets. The computation times are truncated + at a precision of 10^-10 to avoid floating point precision errors. + + Args: + - `utilization`: The list of task utilization sets. For example:: + + [[0.3, 0.4, 0.8], [0.1, 0.9, 0.5]] + - `periods`: The list of task period sets. For examples:: + + [[100, 50, 1000], [200, 500, 10]] + + Returns: + For the above example, it returns:: + + [[(30.0, 100), (20.0, 50), (800.0, 1000)], + [(20.0, 200), (450.0, 500), (5.0, 10)]] + """ + def trunc(x, p): + return int(x * 10 ** p) / float(10 ** p) + + return [[(trunc(ui * pi, 6), trunc(pi, 6)) for ui, pi in zip(us, ps)] + for us, ps in zip(utilizations, periods)] diff --git a/simso/utils/PartitionedScheduler.py b/simso/utils/PartitionedScheduler.py new file mode 100644 index 0000000..c1d390f --- /dev/null +++ b/simso/utils/PartitionedScheduler.py @@ -0,0 +1,223 @@ +from simso.core import Scheduler + + +def best_fit(scheduler, task_list=None): + """ + Best-Fit heuristic. Put the tasks somewhere it fits but with the least + spare place. + """ + cpus = [[cpu, 0] for cpu in scheduler.processors] + + if task_list is None: + task_list = scheduler.task_list + + for task in task_list: + j = 0 + # Find a processor with free space. + while cpus[j][1] * task.period + float(task.wcet) > task.period: + j += 1 + if j >= len(scheduler.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + scheduler.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + + cpus[:] = sorted(cpus, key=lambda c: -c[1]) + + return True + + +def worst_fit(scheduler, task_list=None): + """ + Worst-Fit heuristic. Put the tasks somewhere it fits with the largest + spare place. + """ + + cpus = [[cpu, 0] for cpu in scheduler.processors] + + if task_list is None: + task_list = scheduler.task_list + + for task in task_list: + j = 0 + # Find a processor with free space. + while cpus[j][1] * task.period + float(task.wcet) > task.period: + j += 1 + if j >= len(scheduler.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + scheduler.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + + cpus[:] = sorted(cpus, key=lambda c: c[1]) + + return True + + +def next_fit(scheduler, task_list=None): + """ + Next-Fit heuristic. Put each task on the next processor with enough space. + """ + + cpus = [[cpu, 0] for cpu in scheduler.processors] + + if task_list is None: + task_list = scheduler.task_list + + j = 0 + for task in task_list: + k = 0 + # Find a processor with free space. + while cpus[j][1] * task.period + float(task.wcet) > task.period: + j = (j + 1) % len(scheduler.processors) + k += 1 + if k >= len(scheduler.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + scheduler.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + + return True + + +def first_fit(scheduler, task_list=None): + """ + First-Fit heuristic. Put each task on the first processor with enough + space. + """ + + cpus = [[cpu, 0] for cpu in scheduler.processors] + + if task_list is None: + task_list = scheduler.task_list + + for task in task_list: + j = 0 + # Find a processor with free space. + while cpus[j][1] * task.period + float(task.wcet) > task.period: + j += 1 + if j >= len(scheduler.processors): + print("oops bin packing failed.") + return False + + # Affect it to the task. + scheduler.affect_task_to_processor(task, cpus[j][0]) + + # Update utilization. + cpus[j][1] += float(task.wcet) / task.period + + return True + + +def decreasing_first_fit(scheduler): + """ + First-Fit with tasks inversely sorted by their u_i. + """ + return first_fit( + scheduler, sorted(scheduler.task_list, + key=lambda t: -float(t.wcet) / t.period)) + + +def decreasing_next_fit(scheduler): + """ + Next-Fit with tasks inversely sorted by their u_i. + """ + + return next_fit( + scheduler, sorted(scheduler.task_list, + key=lambda t: -float(t.wcet) / t.period)) + + +def decreasing_best_fit(scheduler): + """ + Best-Fit with tasks inversely sorted by their u_i. + """ + + return next_fit( + scheduler, sorted(scheduler.task_list, + key=lambda t: -float(t.wcet) / t.period)) + + +def decreasing_worst_fit(scheduler): + """ + Worst-Fit with tasks inversely sorted by their u_i. + """ + + return next_fit( + scheduler, sorted(scheduler.task_list, + key=lambda t: -float(t.wcet) / t.period)) + + +class PartitionedScheduler(Scheduler): + """ + The PartitionedScheduler class provide facilities to create a new + Partitioned Scheduler. Only the packing phase is not done and should + be overriden. + """ + def init(self, scheduler_info, packer=None): + """ + Args: + - `scheduler_info`: A :class:`SchedulerInfo \ + ` object. One scheduler from \ + this SchedulerInfo will be instantiated for each processor. + """ + assert scheduler_info is not None, \ + "PartitionedScheduler requires a monoprocessor scheduler to " \ + "instantiate." + + # Mapping processor to scheduler. + self.map_cpu_sched = {} + # Mapping task to scheduler. + self.map_task_sched = {} + + for cpu in self.processors: + # Instantiate a scheduler. + sched = scheduler_info.instantiate(self.sim) + sched.add_processor(cpu) + + # Affect the scheduler to the processor. + self.map_cpu_sched[cpu.identifier] = sched + + self._packer = packer + assert self.packer(), "Packing failed" + + for cpu in self.processors: + self.map_cpu_sched[cpu.identifier].init() + + def packer(self): + if self._packer: + return self._packer(self) + raise Exception("A bin packing method is required.") + + def affect_task_to_processor(self, task, proc): + # Get the scheduler for this processor. + sched = self.map_cpu_sched[proc.identifier] + self.map_task_sched[task.identifier] = sched + sched.add_task(task) + # Put the task on that processor. + task.cpu = proc + + def get_lock(self): + # No lock mechanism is needed. + return True + + def schedule(self, cpu): + return self.map_cpu_sched[cpu.identifier].schedule(cpu) + + def on_activate(self, job): + self.map_task_sched[job.task.identifier].on_activate(job) + + def on_terminated(self, job): + self.map_task_sched[job.task.identifier].on_terminated(job) diff --git a/simso/utils/SchedulingTests.py b/simso/utils/SchedulingTests.py new file mode 100644 index 0000000..6391109 --- /dev/null +++ b/simso/utils/SchedulingTests.py @@ -0,0 +1,32 @@ +def GFB(configuration): + """ + Sufficient test for Global-EDF. + """ + umax = max(t.wcet / t.deadline for t in configuration.task_info_list) + utot = sum(t.wcet / t.deadline for t in configuration.task_info_list) + m = len(configuration.proc_info_list) + + return utot <= m * (1 - umax) + umax + + +def BAK(configuration): + """ + Sufficient test for Global-EDF. This code is untested. + """ + m = len(configuration.proc_info_list) + + def b(task, task_k): + lk = task_k.wcet / task_k.deadline + ui = task.wcet / task.period + if lk >= ui: + return ui * (1 + (task.period - task.deadline) / task_k.deadline) + else: + return ui * (1 + (task.period - task.deadline) / task_k.deadline) \ + + (task.wcet - lk * task.period) / task_k.deadline + + def cond(task): + s = sum(min(1, b(i, task)) for i in configuration.task_info_list) + lk = task.wcet / task.deadline + return s <= m * (1 - lk) + lk + + return all(cond(k) for k in configuration.task_info_list) diff --git a/simso/utils/__init__.py b/simso/utils/__init__.py new file mode 100644 index 0000000..c0eeec2 --- /dev/null +++ b/simso/utils/__init__.py @@ -0,0 +1 @@ +from .PartitionedScheduler import PartitionedScheduler