mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-10-31 14:01:20 +00:00 
			
		
		
		
	devlib initial commit.
This commit is contained in:
		
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| *.pyc | ||||
| *.pyo | ||||
| *.orig | ||||
| .ropeproject | ||||
| *.egg-info | ||||
							
								
								
									
										37
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| devlib | ||||
| ====== | ||||
|  | ||||
| ``devlib`` exposes an interface for interacting with and collecting | ||||
| measurements from a variety of devices (such as mobile phones, tablets and | ||||
| development boards) running a Linux-based operating system. | ||||
|  | ||||
|  | ||||
| Installation | ||||
| ------------ | ||||
|  | ||||
| :: | ||||
|  | ||||
|         sudo -H pip install devlib | ||||
|  | ||||
|  | ||||
| Usage | ||||
| ----- | ||||
|  | ||||
| Please refer  to the "Overview" section of the documentation. | ||||
|  | ||||
|  | ||||
| License | ||||
| ------- | ||||
|  | ||||
| This package is distributed under `Apache v2.0 License <http://www.apache.org/licenses/LICENSE-2.0>`_.  | ||||
|  | ||||
|  | ||||
| Feedback, Contrubutions and Support | ||||
| ----------------------------------- | ||||
|  | ||||
| - Please use the GitHub Issue Tracker associated with this repository for | ||||
|   feedback. | ||||
| - ARM licensees may contact ARM directly via their partner managers. | ||||
| - We welcome code contributions via GitHub Pull requests. Please try to | ||||
|   stick to the style in the rest of the code for your contributions. | ||||
|  | ||||
							
								
								
									
										18
									
								
								devlib/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								devlib/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| from devlib.target import Target, LinuxTarget, AndroidTarget, LocalLinuxTarget | ||||
| from devlib.host import PACKAGE_BIN_DIRECTORY | ||||
| from devlib.exception import DevlibError, TargetError, HostError, TargetNotRespondingError | ||||
|  | ||||
| from devlib.module import Module, HardRestModule, BootModule, FlashModule | ||||
| from devlib.module import get_module, register_module | ||||
|  | ||||
| from devlib.platform import Platform | ||||
| from devlib.platform.arm import TC2, Juno, JunoEnergyInstrument | ||||
|  | ||||
| from devlib.instrument import Instrument, InstrumentChannel, Measurement, MeasurementsCsv | ||||
| from devlib.instrument import MEASUREMENT_TYPES, INSTANTANEOUS, CONTINUOUS | ||||
| from devlib.instrument.daq import DaqInstrument | ||||
| from devlib.instrument.energy_probe import EnergyProbeInstrument | ||||
| from devlib.instrument.hwmon import HwmonInstrument | ||||
| from devlib.instrument.netstats import NetstatsInstrument | ||||
|  | ||||
| from devlib.trace.ftrace import FtraceCollector | ||||
							
								
								
									
										348
									
								
								devlib/bin/LICENSE.busybox
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								devlib/bin/LICENSE.busybox
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,348 @@ | ||||
| --- A note on GPL versions | ||||
|  | ||||
| BusyBox is distributed under version 2 of the General Public License (included | ||||
| in its entirety, below).  Version 2 is the only version of this license which | ||||
| this version of BusyBox (or modified versions derived from this one) may be | ||||
| distributed under. | ||||
|  | ||||
| ------------------------------------------------------------------------ | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
| 		       Version 2, June 1991 | ||||
|  | ||||
|  Copyright (C) 1989, 1991 Free Software Foundation, Inc. | ||||
|      51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
| 			    Preamble | ||||
|  | ||||
|   The licenses for most software are designed to take away your | ||||
| freedom to share and change it.  By contrast, the GNU General Public | ||||
| License is intended to guarantee your freedom to share and change free | ||||
| software--to make sure the software is free for all its users.  This | ||||
| General Public License applies to most of the Free Software | ||||
| Foundation's software and to any other program whose authors commit to | ||||
| using it.  (Some other Free Software Foundation software is covered by | ||||
| the GNU Library General Public License instead.)  You can apply it to | ||||
| your programs, too. | ||||
|  | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| price.  Our General Public Licenses are designed to make sure that you | ||||
| have the freedom to distribute copies of free software (and charge for | ||||
| this service if you wish), that you receive source code or can get it | ||||
| if you want it, that you can change the software or use pieces of it | ||||
| in new free programs; and that you know you can do these things. | ||||
|  | ||||
|   To protect your rights, we need to make restrictions that forbid | ||||
| anyone to deny you these rights or to ask you to surrender the rights. | ||||
| These restrictions translate to certain responsibilities for you if you | ||||
| distribute copies of the software, or if you modify it. | ||||
|  | ||||
|   For example, if you distribute copies of such a program, whether | ||||
| gratis or for a fee, you must give the recipients all the rights that | ||||
| you have.  You must make sure that they, too, receive or can get the | ||||
| source code.  And you must show them these terms so they know their | ||||
| rights. | ||||
|  | ||||
|   We protect your rights with two steps: (1) copyright the software, and | ||||
| (2) offer you this license which gives you legal permission to copy, | ||||
| distribute and/or modify the software. | ||||
|  | ||||
|   Also, for each author's protection and ours, we want to make certain | ||||
| that everyone understands that there is no warranty for this free | ||||
| software.  If the software is modified by someone else and passed on, we | ||||
| want its recipients to know that what they have is not the original, so | ||||
| that any problems introduced by others will not reflect on the original | ||||
| authors' reputations. | ||||
|  | ||||
|   Finally, any free program is threatened constantly by software | ||||
| patents.  We wish to avoid the danger that redistributors of a free | ||||
| program will individually obtain patent licenses, in effect making the | ||||
| program proprietary.  To prevent this, we have made it clear that any | ||||
| patent must be licensed for everyone's free use or not licensed at all. | ||||
|  | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
|  | ||||
| 		    GNU GENERAL PUBLIC LICENSE | ||||
|    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | ||||
|  | ||||
|   0. This License applies to any program or other work which contains | ||||
| a notice placed by the copyright holder saying it may be distributed | ||||
| under the terms of this General Public License.  The "Program", below, | ||||
| refers to any such program or work, and a "work based on the Program" | ||||
| means either the Program or any derivative work under copyright law: | ||||
| that is to say, a work containing the Program or a portion of it, | ||||
| either verbatim or with modifications and/or translated into another | ||||
| language.  (Hereinafter, translation is included without limitation in | ||||
| the term "modification".)  Each licensee is addressed as "you". | ||||
|  | ||||
| Activities other than copying, distribution and modification are not | ||||
| covered by this License; they are outside its scope.  The act of | ||||
| running the Program is not restricted, and the output from the Program | ||||
| is covered only if its contents constitute a work based on the | ||||
| Program (independent of having been made by running the Program). | ||||
| Whether that is true depends on what the Program does. | ||||
|  | ||||
|   1. You may copy and distribute verbatim copies of the Program's | ||||
| source code as you receive it, in any medium, provided that you | ||||
| conspicuously and appropriately publish on each copy an appropriate | ||||
| copyright notice and disclaimer of warranty; keep intact all the | ||||
| notices that refer to this License and to the absence of any warranty; | ||||
| and give any other recipients of the Program a copy of this License | ||||
| along with the Program. | ||||
|  | ||||
| You may charge a fee for the physical act of transferring a copy, and | ||||
| you may at your option offer warranty protection in exchange for a fee. | ||||
|  | ||||
|   2. You may modify your copy or copies of the Program or any portion | ||||
| of it, thus forming a work based on the Program, and copy and | ||||
| distribute such modifications or work under the terms of Section 1 | ||||
| above, provided that you also meet all of these conditions: | ||||
|  | ||||
|     a) You must cause the modified files to carry prominent notices | ||||
|     stating that you changed the files and the date of any change. | ||||
|  | ||||
|     b) You must cause any work that you distribute or publish, that in | ||||
|     whole or in part contains or is derived from the Program or any | ||||
|     part thereof, to be licensed as a whole at no charge to all third | ||||
|     parties under the terms of this License. | ||||
|  | ||||
|     c) If the modified program normally reads commands interactively | ||||
|     when run, you must cause it, when started running for such | ||||
|     interactive use in the most ordinary way, to print or display an | ||||
|     announcement including an appropriate copyright notice and a | ||||
|     notice that there is no warranty (or else, saying that you provide | ||||
|     a warranty) and that users may redistribute the program under | ||||
|     these conditions, and telling the user how to view a copy of this | ||||
|     License.  (Exception: if the Program itself is interactive but | ||||
|     does not normally print such an announcement, your work based on | ||||
|     the Program is not required to print an announcement.) | ||||
|  | ||||
| These requirements apply to the modified work as a whole.  If | ||||
| identifiable sections of that work are not derived from the Program, | ||||
| and can be reasonably considered independent and separate works in | ||||
| themselves, then this License, and its terms, do not apply to those | ||||
| sections when you distribute them as separate works.  But when you | ||||
| distribute the same sections as part of a whole which is a work based | ||||
| on the Program, the distribution of the whole must be on the terms of | ||||
| this License, whose permissions for other licensees extend to the | ||||
| entire whole, and thus to each and every part regardless of who wrote it. | ||||
|  | ||||
| Thus, it is not the intent of this section to claim rights or contest | ||||
| your rights to work written entirely by you; rather, the intent is to | ||||
| exercise the right to control the distribution of derivative or | ||||
| collective works based on the Program. | ||||
|  | ||||
| In addition, mere aggregation of another work not based on the Program | ||||
| with the Program (or with a work based on the Program) on a volume of | ||||
| a storage or distribution medium does not bring the other work under | ||||
| the scope of this License. | ||||
|  | ||||
|   3. You may copy and distribute the Program (or a work based on it, | ||||
| under Section 2) in object code or executable form under the terms of | ||||
| Sections 1 and 2 above provided that you also do one of the following: | ||||
|  | ||||
|     a) Accompany it with the complete corresponding machine-readable | ||||
|     source code, which must be distributed under the terms of Sections | ||||
|     1 and 2 above on a medium customarily used for software interchange; or, | ||||
|  | ||||
|     b) Accompany it with a written offer, valid for at least three | ||||
|     years, to give any third party, for a charge no more than your | ||||
|     cost of physically performing source distribution, a complete | ||||
|     machine-readable copy of the corresponding source code, to be | ||||
|     distributed under the terms of Sections 1 and 2 above on a medium | ||||
|     customarily used for software interchange; or, | ||||
|  | ||||
|     c) Accompany it with the information you received as to the offer | ||||
|     to distribute corresponding source code.  (This alternative is | ||||
|     allowed only for noncommercial distribution and only if you | ||||
|     received the program in object code or executable form with such | ||||
|     an offer, in accord with Subsection b above.) | ||||
|  | ||||
| The source code for a work means the preferred form of the work for | ||||
| making modifications to it.  For an executable work, complete source | ||||
| code means all the source code for all modules it contains, plus any | ||||
| associated interface definition files, plus the scripts used to | ||||
| control compilation and installation of the executable.  However, as a | ||||
| special exception, the source code distributed need not include | ||||
| anything that is normally distributed (in either source or binary | ||||
| form) with the major components (compiler, kernel, and so on) of the | ||||
| operating system on which the executable runs, unless that component | ||||
| itself accompanies the executable. | ||||
|  | ||||
| If distribution of executable or object code is made by offering | ||||
| access to copy from a designated place, then offering equivalent | ||||
| access to copy the source code from the same place counts as | ||||
| distribution of the source code, even though third parties are not | ||||
| compelled to copy the source along with the object code. | ||||
|  | ||||
|   4. You may not copy, modify, sublicense, or distribute the Program | ||||
| except as expressly provided under this License.  Any attempt | ||||
| otherwise to copy, modify, sublicense or distribute the Program is | ||||
| void, and will automatically terminate your rights under this License. | ||||
| However, parties who have received copies, or rights, from you under | ||||
| this License will not have their licenses terminated so long as such | ||||
| parties remain in full compliance. | ||||
|  | ||||
|   5. You are not required to accept this License, since you have not | ||||
| signed it.  However, nothing else grants you permission to modify or | ||||
| distribute the Program or its derivative works.  These actions are | ||||
| prohibited by law if you do not accept this License.  Therefore, by | ||||
| modifying or distributing the Program (or any work based on the | ||||
| Program), you indicate your acceptance of this License to do so, and | ||||
| all its terms and conditions for copying, distributing or modifying | ||||
| the Program or works based on it. | ||||
|  | ||||
|   6. Each time you redistribute the Program (or any work based on the | ||||
| Program), the recipient automatically receives a license from the | ||||
| original licensor to copy, distribute or modify the Program subject to | ||||
| these terms and conditions.  You may not impose any further | ||||
| restrictions on the recipients' exercise of the rights granted herein. | ||||
| You are not responsible for enforcing compliance by third parties to | ||||
| this License. | ||||
|  | ||||
|   7. If, as a consequence of a court judgment or allegation of patent | ||||
| infringement or for any other reason (not limited to patent issues), | ||||
| conditions are imposed on you (whether by court order, agreement or | ||||
| otherwise) that contradict the conditions of this License, they do not | ||||
| excuse you from the conditions of this License.  If you cannot | ||||
| distribute so as to satisfy simultaneously your obligations under this | ||||
| License and any other pertinent obligations, then as a consequence you | ||||
| may not distribute the Program at all.  For example, if a patent | ||||
| license would not permit royalty-free redistribution of the Program by | ||||
| all those who receive copies directly or indirectly through you, then | ||||
| the only way you could satisfy both it and this License would be to | ||||
| refrain entirely from distribution of the Program. | ||||
|  | ||||
| If any portion of this section is held invalid or unenforceable under | ||||
| any particular circumstance, the balance of the section is intended to | ||||
| apply and the section as a whole is intended to apply in other | ||||
| circumstances. | ||||
|  | ||||
| It is not the purpose of this section to induce you to infringe any | ||||
| patents or other property right claims or to contest validity of any | ||||
| such claims; this section has the sole purpose of protecting the | ||||
| integrity of the free software distribution system, which is | ||||
| implemented by public license practices.  Many people have made | ||||
| generous contributions to the wide range of software distributed | ||||
| through that system in reliance on consistent application of that | ||||
| system; it is up to the author/donor to decide if he or she is willing | ||||
| to distribute software through any other system and a licensee cannot | ||||
| impose that choice. | ||||
|  | ||||
| This section is intended to make thoroughly clear what is believed to | ||||
| be a consequence of the rest of this License. | ||||
|  | ||||
|   8. If the distribution and/or use of the Program is restricted in | ||||
| certain countries either by patents or by copyrighted interfaces, the | ||||
| original copyright holder who places the Program under this License | ||||
| may add an explicit geographical distribution limitation excluding | ||||
| those countries, so that distribution is permitted only in or among | ||||
| countries not thus excluded.  In such case, this License incorporates | ||||
| the limitation as if written in the body of this License. | ||||
|  | ||||
|   9. The Free Software Foundation may publish revised and/or new versions | ||||
| of the General Public License from time to time.  Such new versions will | ||||
| be similar in spirit to the present version, but may differ in detail to | ||||
| address new problems or concerns. | ||||
|  | ||||
| Each version is given a distinguishing version number.  If the Program | ||||
| specifies a version number of this License which applies to it and "any | ||||
| later version", you have the option of following the terms and conditions | ||||
| either of that version or of any later version published by the Free | ||||
| Software Foundation.  If the Program does not specify a version number of | ||||
| this License, you may choose any version ever published by the Free Software | ||||
| Foundation. | ||||
|  | ||||
|   10. If you wish to incorporate parts of the Program into other free | ||||
| programs whose distribution conditions are different, write to the author | ||||
| to ask for permission.  For software which is copyrighted by the Free | ||||
| Software Foundation, write to the Free Software Foundation; we sometimes | ||||
| make exceptions for this.  Our decision will be guided by the two goals | ||||
| of preserving the free status of all derivatives of our free software and | ||||
| of promoting the sharing and reuse of software generally. | ||||
|  | ||||
| 			    NO WARRANTY | ||||
|  | ||||
|   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | ||||
| FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN | ||||
| OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | ||||
| PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | ||||
| OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||||
| MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS | ||||
| TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE | ||||
| PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | ||||
| REPAIR OR CORRECTION. | ||||
|  | ||||
|   12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | ||||
| REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | ||||
| INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | ||||
| OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | ||||
| TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | ||||
| YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | ||||
| PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | ||||
| POSSIBILITY OF SUCH DAMAGES. | ||||
|  | ||||
| 		     END OF TERMS AND CONDITIONS | ||||
|  | ||||
| 	    How to Apply These Terms to Your New Programs | ||||
|  | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| free software which everyone can redistribute and change under these terms. | ||||
|  | ||||
|   To do so, attach the following notices to the program.  It is safest | ||||
| to attach them to the start of each source file to most effectively | ||||
| convey the exclusion of warranty; and each file should have at least | ||||
| the "copyright" line and a pointer to where the full notice is found. | ||||
|  | ||||
|     <one line to give the program's name and a brief idea of what it does.> | ||||
|     Copyright (C) <year>  <name of author> | ||||
|  | ||||
|     This program is free software; you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|     the Free Software Foundation; either version 2 of the License, or | ||||
|     (at your option) any later version. | ||||
|  | ||||
|     This program is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|  | ||||
|  | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
| If the program is interactive, make it output a short notice like this | ||||
| when it starts in an interactive mode: | ||||
|  | ||||
|     Gnomovision version 69, Copyright (C) year  name of author | ||||
|     Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
|  | ||||
| The hypothetical commands `show w' and `show c' should show the appropriate | ||||
| parts of the General Public License.  Of course, the commands you use may | ||||
| be called something other than `show w' and `show c'; they could even be | ||||
| mouse-clicks or menu items--whatever suits your program. | ||||
|  | ||||
| You should also get your employer (if you work as a programmer) or your | ||||
| school, if any, to sign a "copyright disclaimer" for the program, if | ||||
| necessary.  Here is a sample; alter the names: | ||||
|  | ||||
|   Yoyodyne, Inc., hereby disclaims all copyright interest in the program | ||||
|   `Gnomovision' (which makes passes at compilers) written by James Hacker. | ||||
|  | ||||
|   <signature of Ty Coon>, 1 April 1989 | ||||
|   Ty Coon, President of Vice | ||||
|  | ||||
| This General Public License does not permit incorporating your program into | ||||
| proprietary programs.  If your program is a subroutine library, you may | ||||
| consider it more useful to permit linking proprietary applications with the | ||||
| library.  If this is what you want to do, use the GNU Library General | ||||
| Public License instead of this License. | ||||
							
								
								
									
										39
									
								
								devlib/bin/LICENSE.trace-cmd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								devlib/bin/LICENSE.trace-cmd
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| Included trace-cmd binaries are Free Software ditributed under GPLv2: | ||||
|  | ||||
| /* | ||||
|  * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> | ||||
|  * | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation; version 2 of the License (not later!) | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not,  see <http://www.gnu.org/licenses> | ||||
|  * | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  */ | ||||
|  | ||||
| The full text of the license may be viewed here: | ||||
|  | ||||
| http://www.gnu.org/licenses/gpl-2.0.html | ||||
|  | ||||
| Source code for trace-cmd may be obtained here: | ||||
|  | ||||
| git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/trace-cmd.git | ||||
|  | ||||
| Binaries included here contain modifications by ARM that, at the time of writing, | ||||
| have not yet made it into the above repository. The patches for these modifications | ||||
| are available here: | ||||
|  | ||||
| http://article.gmane.org/gmane.linux.kernel/1869111 | ||||
| http://article.gmane.org/gmane.linux.kernel/1869112 | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/arm64/busybox
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/arm64/busybox
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/arm64/readenergy
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/arm64/readenergy
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/arm64/trace-cmd
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/arm64/trace-cmd
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/armeabi/busybox
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/armeabi/busybox
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/armeabi/trace-cmd
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/armeabi/trace-cmd
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										40
									
								
								devlib/exception.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								devlib/exception.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| from devlib.utils.misc import TimeoutError  # NOQA pylint: disable=W0611 | ||||
|  | ||||
|  | ||||
| class DevlibError(Exception): | ||||
|     """Base class for all Workload Automation exceptions.""" | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class TargetError(DevlibError): | ||||
|     """An error has occured on the target""" | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class TargetNotRespondingError(DevlibError): | ||||
|     """The target is unresponsive.""" | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         super(TargetNotRespondingError, self).__init__('Target {} is not responding.'.format(target)) | ||||
|  | ||||
|  | ||||
| class HostError(DevlibError): | ||||
|     """An error has occured on the host""" | ||||
|     pass | ||||
|  | ||||
							
								
								
									
										80
									
								
								devlib/host.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								devlib/host.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| import os | ||||
| import shutil | ||||
| import subprocess | ||||
| import logging | ||||
| from getpass import getpass | ||||
|  | ||||
| from devlib.exception import TargetError | ||||
| from devlib.utils.misc import check_output | ||||
|  | ||||
| PACKAGE_BIN_DIRECTORY = os.path.join(os.path.dirname(__file__), 'bin') | ||||
|  | ||||
|  | ||||
| class LocalConnection(object): | ||||
|  | ||||
|     name = 'local' | ||||
|  | ||||
|     def __init__(self, timeout=10, keep_password=True, unrooted=False): | ||||
|         self.logger = logging.getLogger('local_connection') | ||||
|         self.timeout = timeout | ||||
|         self.keep_password = keep_password | ||||
|         self.unrooted = unrooted | ||||
|         self.password = None | ||||
|  | ||||
|     def push(self, source, dest, timeout=None, as_root=False):  # pylint: disable=unused-argument | ||||
|         self.logger.debug('cp {} {}'.format(source, dest)) | ||||
|         shutil.copy(source, dest) | ||||
|  | ||||
|     def pull(self, source, dest, timeout=None, as_root=False): # pylint: disable=unused-argument | ||||
|         self.logger.debug('cp {} {}'.format(source, dest)) | ||||
|         shutil.copy(source, dest) | ||||
|  | ||||
|     def execute(self, command, timeout=None, check_exit_code=True, as_root=False): | ||||
|         self.logger.debug(command) | ||||
|         if as_root: | ||||
|             if self.unrooted: | ||||
|                 raise TargetError('unrooted') | ||||
|             password = self._get_password() | ||||
|             command = 'echo \'{}\' | sudo -S '.format(password) + command | ||||
|         ignore = None if check_exit_code else 'all' | ||||
|         try: | ||||
|             return check_output(command, shell=True, timeout=timeout, ignore=ignore)[0] | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             raise TargetError(e) | ||||
|  | ||||
|     def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False): | ||||
|         if as_root: | ||||
|             if self.unrooted: | ||||
|                 raise TargetError('unrooted') | ||||
|             password = self._get_password() | ||||
|             command = 'echo \'{}\' | sudo -S '.format(password) + command | ||||
|         return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True) | ||||
|  | ||||
|     def close(self): | ||||
|         pass | ||||
|  | ||||
|     def cancel_running_command(self): | ||||
|         pass | ||||
|  | ||||
|     def _get_password(self): | ||||
|         if self.password: | ||||
|             return self.password | ||||
|         password = getpass('sudo password:') | ||||
|         if self.keep_password: | ||||
|             self.password = password | ||||
|         return password | ||||
|  | ||||
							
								
								
									
										225
									
								
								devlib/instrument/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								devlib/instrument/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| import csv | ||||
| import logging | ||||
|  | ||||
| from devlib.utils.types import numeric | ||||
|  | ||||
|  | ||||
| # Channel modes describe what sort of measurement the instrument supports. | ||||
| # Values must be powers of 2 | ||||
| INSTANTANEOUS = 1 | ||||
| CONTINUOUS = 2 | ||||
|  | ||||
|  | ||||
| class MeasurementType(tuple): | ||||
|  | ||||
|     __slots__ = [] | ||||
|  | ||||
|     def __new__(cls, name, units, category=None): | ||||
|         return tuple.__new__(cls, (name, units, category)) | ||||
|  | ||||
|     @property | ||||
|     def name(self): | ||||
|         return tuple.__getitem__(self, 0) | ||||
|  | ||||
|     @property | ||||
|     def units(self): | ||||
|         return tuple.__getitem__(self, 1) | ||||
|  | ||||
|     @property | ||||
|     def category(self): | ||||
|         return tuple.__getitem__(self, 2) | ||||
|  | ||||
|     def __getitem__(self, item): | ||||
|         raise TypeError() | ||||
|  | ||||
|     def __cmp__(self, other): | ||||
|         if isinstance(other, MeasurementType): | ||||
|             other = other.name | ||||
|         return cmp(self.name, other) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| # Standard measures | ||||
| _measurement_types = [ | ||||
|     MeasurementType('time', 'seconds'), | ||||
|     MeasurementType('temperature', 'degrees'), | ||||
|  | ||||
|     MeasurementType('power', 'watts', 'power/energy'), | ||||
|     MeasurementType('voltage', 'volts', 'power/energy'), | ||||
|     MeasurementType('current', 'amps', 'power/energy'), | ||||
|     MeasurementType('energy', 'joules', 'power/energy'), | ||||
|  | ||||
|     MeasurementType('tx', 'bytes', 'data transfer'), | ||||
|     MeasurementType('rx', 'bytes', 'data transfer'), | ||||
|     MeasurementType('tx/rx', 'bytes', 'data transfer'), | ||||
| ] | ||||
| MEASUREMENT_TYPES = {m.name: m for m in _measurement_types} | ||||
|  | ||||
|  | ||||
| class Measurement(object): | ||||
|  | ||||
|     __slots__ = ['value', 'channel'] | ||||
|  | ||||
|     @property | ||||
|     def name(self): | ||||
|         return '{}_{}'.format(self.channel.site, self.channel.kind) | ||||
|  | ||||
|     @property | ||||
|     def units(self): | ||||
|         return self.channel.units | ||||
|  | ||||
|     def __init__(self, value, channel): | ||||
|         self.value = value | ||||
|         self.channel = channel | ||||
|  | ||||
|     def __cmp__(self, other): | ||||
|         if isinstance(other, Measurement): | ||||
|             return cmp(self.value, other.value) | ||||
|         else: | ||||
|             return cmp(self.value, other) | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.units: | ||||
|             return '{}: {} {}'.format(self.name, self.value, self.units) | ||||
|         else: | ||||
|             return '{}: {}'.format(self.name, self.value) | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class MeasurementsCsv(object): | ||||
|  | ||||
|     def __init__(self, path, channels): | ||||
|         self.path = path | ||||
|         self.channels = channels | ||||
|         self._fh = open(path, 'rb') | ||||
|  | ||||
|     def measurements(self): | ||||
|         return list(self.itermeasurements()) | ||||
|  | ||||
|     def itermeasurements(self): | ||||
|         self._fh.seek(0) | ||||
|         reader = csv.reader(self._fh) | ||||
|         reader.next()  # headings | ||||
|         for row in reader: | ||||
|             values = map(numeric, row) | ||||
|             yield [Measurement(v, c) for (v, c) in zip(values, self.channels)] | ||||
|  | ||||
|  | ||||
| class InstrumentChannel(object): | ||||
|  | ||||
|     @property | ||||
|     def label(self): | ||||
|         return '{}_{}'.format(self.site, self.kind) | ||||
|  | ||||
|     @property | ||||
|     def kind(self): | ||||
|         return self.measurement_type.name | ||||
|  | ||||
|     @property | ||||
|     def units(self): | ||||
|         return self.measurement_type.units | ||||
|  | ||||
|     def __init__(self, name, site, measurement_type, **attrs): | ||||
|         self.name = name | ||||
|         self.site = site | ||||
|         if isinstance(measurement_type, MeasurementType): | ||||
|             self.measurement_type = measurement_type | ||||
|         else: | ||||
|             try: | ||||
|                 self.measurement_type = MEASUREMENT_TYPES[measurement_type] | ||||
|             except KeyError: | ||||
|                 raise ValueError('Unknown measurement type:  {}'.format(measurement_type)) | ||||
|         for atname, atvalue in attrs.iteritems(): | ||||
|             setattr(self, atname, atvalue) | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.name == self.label: | ||||
|             return 'CHAN({})'.format(self.label) | ||||
|         else: | ||||
|             return 'CHAN({}, {})'.format(self.name, self.label) | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class Instrument(object): | ||||
|  | ||||
|     mode = 0 | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         self.target = target | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|         self.channels = {} | ||||
|         self.active_channels = [] | ||||
|  | ||||
|     # channel management | ||||
|  | ||||
|     def list_channels(self): | ||||
|         return self.channels.values() | ||||
|  | ||||
|     def get_channels(self, measure): | ||||
|         if hasattr(measure, 'name'): | ||||
|             measure = measure.name | ||||
|         return [c for c in self.channels if c.measure.name == measure] | ||||
|  | ||||
|     def add_channel(self, site, measure, name=None, **attrs): | ||||
|         if name is None: | ||||
|             name = '{}_{}'.format(site, measure) | ||||
|         chan = InstrumentChannel(name, site, measure, **attrs) | ||||
|         self.channels[chan.label] = chan | ||||
|  | ||||
|     # initialization and teardown | ||||
|  | ||||
|     def setup(self, *args, **kwargs): | ||||
|         pass | ||||
|  | ||||
|     def teardown(self): | ||||
|         pass | ||||
|  | ||||
|     def reset(self, sites=None, kinds=None): | ||||
|         if kinds is None and sites is None: | ||||
|             self.active_channels = sorted(self.channels.values(), key=lambda x: x.label) | ||||
|         else: | ||||
|             if isinstance(sites, basestring): | ||||
|                 sites = [sites] | ||||
|             if isinstance(kinds, basestring): | ||||
|                 kinds = [kinds] | ||||
|             self.active_channels = [] | ||||
|             for chan in self.channels.values(): | ||||
|                 if (kinds is None or chan.kind in kinds) and \ | ||||
|                    (sites is None or chan.site in sites): | ||||
|                     self.active_channels.append(chan) | ||||
|  | ||||
|     # instantaneous | ||||
|  | ||||
|     def take_measurement(self): | ||||
|         pass | ||||
|  | ||||
|     # continuous | ||||
|  | ||||
|     def start(self): | ||||
|         pass | ||||
|  | ||||
|     def stop(self): | ||||
|         pass | ||||
|  | ||||
|     def get_data(self, outfile): | ||||
|         pass | ||||
							
								
								
									
										138
									
								
								devlib/instrument/daq.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								devlib/instrument/daq.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| import os | ||||
| import csv | ||||
| import tempfile | ||||
| from itertools import chain | ||||
|  | ||||
| from devlib.instrument import Instrument, MeasurementsCsv, CONTINUOUS | ||||
| from devlib.exception import HostError | ||||
| from devlib.utils.misc import unique | ||||
|  | ||||
| try: | ||||
|     from daqpower.client import execute_command, Status | ||||
|     from daqpower.config import DeviceConfiguration, ServerConfiguration | ||||
| except ImportError, e: | ||||
|     execute_command, Status = None, None | ||||
|     DeviceConfiguration, ServerConfiguration, ConfigurationError = None, None, None | ||||
|     import_error_mesg = e.message | ||||
|  | ||||
|  | ||||
| class DaqInstrument(Instrument): | ||||
|  | ||||
|     mode = CONTINUOUS | ||||
|  | ||||
|     def __init__(self, target, resistor_values,  # pylint: disable=R0914 | ||||
|                  labels=None, | ||||
|                  host='localhost', | ||||
|                  port=45677, | ||||
|                  device_id='Dev1', | ||||
|                  v_range=2.5, | ||||
|                  dv_range=0.2, | ||||
|                  sampling_rate=10000, | ||||
|                  channel_map=(0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23), | ||||
|                  ): | ||||
|         # pylint: disable=no-member | ||||
|         super(DaqInstrument, self).__init__(target) | ||||
|         self._need_reset = True | ||||
|         if execute_command is None: | ||||
|             raise HostError('Could not import "daqpower": {}'.format(import_error_mesg)) | ||||
|         if labels is None: | ||||
|             labels = ['PORT_{}'.format(i) for i in xrange(len(resistor_values))] | ||||
|         if len(labels) != len(resistor_values): | ||||
|             raise ValueError('"labels" and "resistor_values" must be of the same length') | ||||
|         self.server_config = ServerConfiguration(host=host, | ||||
|                                                  port=port) | ||||
|         result = self.execute('list_devices') | ||||
|         if result.status == Status.OK: | ||||
|             if device_id not in result.data: | ||||
|                 raise ValueError('Device "{}" is not found on the DAQ server.'.format(device_id)) | ||||
|         elif result.status != Status.OKISH: | ||||
|             raise HostError('Problem querying DAQ server: {}'.format(result.message)) | ||||
|  | ||||
|         self.device_config = DeviceConfiguration(device_id=device_id, | ||||
|                                                  v_range=v_range, | ||||
|                                                  dv_range=dv_range, | ||||
|                                                  sampling_rate=sampling_rate, | ||||
|                                                  resistor_values=resistor_values, | ||||
|                                                  channel_map=channel_map, | ||||
|                                                  labels=labels) | ||||
|  | ||||
|         for label in labels: | ||||
|             for kind in ['power', 'voltage']: | ||||
|                 self.add_channel(label, kind) | ||||
|  | ||||
|     def reset(self, sites=None, kinds=None): | ||||
|         super(DaqInstrument, self).reset(sites, kinds) | ||||
|         self.execute('close') | ||||
|         result = self.execute('configure', config=self.device_config) | ||||
|         if not result.status == Status.OK:  # pylint: disable=no-member | ||||
|             raise HostError(result.message) | ||||
|         self._need_reset = False | ||||
|  | ||||
|     def start(self): | ||||
|         if self._need_reset: | ||||
|             self.reset() | ||||
|         self.execute('start') | ||||
|  | ||||
|     def stop(self): | ||||
|         self.execute('stop') | ||||
|         self._need_reset = True | ||||
|  | ||||
|     def get_data(self, outfile):  # pylint: disable=R0914 | ||||
|         tempdir = tempfile.mkdtemp(prefix='daq-raw-') | ||||
|         self.execute('get_data', output_directory=tempdir) | ||||
|         raw_file_map = {} | ||||
|         for entry in os.listdir(tempdir): | ||||
|             site = os.path.splitext(entry)[0] | ||||
|             path = os.path.join(tempdir, entry) | ||||
|             raw_file_map[site] = path | ||||
|  | ||||
|         active_sites = unique([c.site for c in self.active_channels]) | ||||
|         file_handles = [] | ||||
|         try: | ||||
|             site_readers = {} | ||||
|             for site in active_sites: | ||||
|                 try: | ||||
|                     site_file = raw_file_map[site] | ||||
|                     fh = open(site_file, 'rb') | ||||
|                     site_readers[site] = csv.reader(fh) | ||||
|                     file_handles.append(fh) | ||||
|                 except KeyError: | ||||
|                     message = 'Could not get DAQ trace for {}; Obtained traces are in {}' | ||||
|                     raise HostError(message.format(site, tempdir)) | ||||
|  | ||||
|             # The first row is the headers | ||||
|             channel_order = [] | ||||
|             for site, reader in site_readers.iteritems(): | ||||
|                 channel_order.extend(['{}_{}'.format(site, kind) | ||||
|                                       for kind in reader.next()]) | ||||
|  | ||||
|             def _read_next_rows(): | ||||
|                 parts = [] | ||||
|                 for reader in site_readers.itervalues(): | ||||
|                     try: | ||||
|                         parts.extend(reader.next()) | ||||
|                     except StopIteration: | ||||
|                         parts.extend([None, None]) | ||||
|                 return list(chain(parts)) | ||||
|  | ||||
|             with open(outfile, 'wb') as wfh: | ||||
|                 field_names = [c.label for c in self.active_channels] | ||||
|                 writer = csv.writer(wfh) | ||||
|                 writer.writerow(field_names) | ||||
|                 raw_row = _read_next_rows() | ||||
|                 while any(raw_row): | ||||
|                     row = [raw_row[channel_order.index(f)] for f in field_names] | ||||
|                     writer.writerow(row) | ||||
|                     raw_row = _read_next_rows() | ||||
|  | ||||
|             return MeasurementsCsv(outfile, self.active_channels) | ||||
|         finally: | ||||
|             for fh in file_handles: | ||||
|                 fh.close() | ||||
|  | ||||
|     def teardown(self): | ||||
|         self.execute('close') | ||||
|  | ||||
|     def execute(self, command, **kwargs): | ||||
|         return execute_command(self.server_config, command, **kwargs) | ||||
|  | ||||
							
								
								
									
										116
									
								
								devlib/instrument/energy_probe.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								devlib/instrument/energy_probe.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| from __future__ import division | ||||
| import os | ||||
| import csv | ||||
| import signal | ||||
| import tempfile | ||||
| import struct | ||||
| import subprocess | ||||
|  | ||||
| try: | ||||
|     import pandas | ||||
| except ImportError: | ||||
|     pandas = None | ||||
|  | ||||
| from devlib.instrument import Instrument, CONTINUOUS, MeasurementsCsv | ||||
| from devlib.exception import HostError | ||||
| from devlib.utils.misc import which | ||||
|  | ||||
|  | ||||
| class EnergyProbeInstrument(Instrument): | ||||
|  | ||||
|     mode = CONTINUOUS | ||||
|  | ||||
|     def __init__(self, target, resistor_values, | ||||
|                  labels=None, | ||||
|                  device_entry='/dev/ttyACM0', | ||||
|                  ): | ||||
|         super(EnergyProbeInstrument, self).__init__(target) | ||||
|         self.resistor_values = resistor_values | ||||
|         if labels is not None: | ||||
|             self.labels = labels | ||||
|         else: | ||||
|             self.labels = ['PORT_{}'.format(i) | ||||
|                            for i in xrange(len(resistor_values))] | ||||
|         self.device_entry = device_entry | ||||
|         self.caiman = which('caiman') | ||||
|         if self.caiman is None: | ||||
|             raise HostError('caiman must be installed on the host ' | ||||
|                             '(see https://github.com/ARM-software/caiman)') | ||||
|         if pandas is None: | ||||
|             self.logger.info("pandas package will significantly speed up this instrument") | ||||
|             self.logger.info("to install it try: pip install pandas") | ||||
|         self.attributes_per_sample = 3 | ||||
|         self.bytes_per_sample = self.attributes_per_sample * 4 | ||||
|         self.attributes = ['power', 'voltage', 'current'] | ||||
|         self.command = None | ||||
|         self.raw_output_directory = None | ||||
|         self.process = None | ||||
|  | ||||
|         for label in self.labels: | ||||
|             for kind in self.attributes: | ||||
|                 self.add_channel(label, kind) | ||||
|  | ||||
|     def reset(self, sites=None, kinds=None): | ||||
|         super(EnergyProbeInstrument, self).reset(sites, kinds) | ||||
|         self.raw_output_directory = tempfile.mkdtemp(prefix='eprobe-caiman-') | ||||
|         parts = ['-r {}:{} '.format(i, int(1000 * rval)) | ||||
|                  for i, rval in enumerate(self.resistor_values)] | ||||
|         rstring = ''.join(parts) | ||||
|         self.command = '{} -d {} -l {} {}'.format(self.caiman, self.device_entry, rstring, self.raw_output_directory) | ||||
|  | ||||
|     def start(self): | ||||
|         self.logger.debug(self.command) | ||||
|         self.process = subprocess.Popen(self.command, | ||||
|                                         stdout=subprocess.PIPE, | ||||
|                                         stderr=subprocess.PIPE, | ||||
|                                         stdin=subprocess.PIPE, | ||||
|                                         preexec_fn=os.setpgrp, | ||||
|                                         shell=True) | ||||
|  | ||||
|     def stop(self): | ||||
|         os.killpg(self.process.pid, signal.SIGTERM) | ||||
|  | ||||
|     def get_data(self, outfile):  # pylint: disable=R0914 | ||||
|         all_channels = [c.label for c in self.list_channels()] | ||||
|         active_channels = [c.label for c in self.active_channels] | ||||
|         active_indexes = [all_channels.index(ac) for ac in active_channels] | ||||
|  | ||||
|         num_of_ports = len(self.resistor_values) | ||||
|         struct_format = '{}I'.format(num_of_ports * self.attributes_per_sample) | ||||
|         not_a_full_row_seen = False | ||||
|         raw_data_file = os.path.join(self.raw_output_directory, '0000000000') | ||||
|  | ||||
|         self.logger.debug('Parsing raw data file: {}'.format(raw_data_file)) | ||||
|         with open(raw_data_file, 'rb') as bfile: | ||||
|             with open(outfile, 'wb') as wfh: | ||||
|                 writer = csv.writer(wfh) | ||||
|                 writer.writerow(active_channels) | ||||
|                 while True: | ||||
|                     data = bfile.read(num_of_ports * self.bytes_per_sample) | ||||
|                     if data == '': | ||||
|                         break | ||||
|                     try: | ||||
|                         unpacked_data = struct.unpack(struct_format, data) | ||||
|                         row = [unpacked_data[i] / 1000 for i in active_indexes] | ||||
|                         writer.writerow(row) | ||||
|                     except struct.error: | ||||
|                         if not_a_full_row_seen: | ||||
|                             self.logger.warn('possibly missaligned caiman raw data, row contained {} bytes'.format(len(data))) | ||||
|                             continue | ||||
|                         else: | ||||
|                             not_a_full_row_seen = True | ||||
|         return MeasurementsCsv(outfile, self.active_channels) | ||||
							
								
								
									
										89
									
								
								devlib/instrument/hwmon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								devlib/instrument/hwmon.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| from __future__ import division | ||||
| import re | ||||
|  | ||||
| from devlib.instrument import Instrument, Measurement, INSTANTANEOUS | ||||
| from devlib.exception import TargetError | ||||
|  | ||||
|  | ||||
| class HwmonInstrument(Instrument): | ||||
|  | ||||
|     name = 'hwmon' | ||||
|     mode = INSTANTANEOUS | ||||
|  | ||||
|     # sensor kind --> (meaure, standard unit conversion) | ||||
|     measure_map = { | ||||
|         'temp': ('temperature', lambda x: x / 1000), | ||||
|         'in': ('voltage', lambda x: x / 1000), | ||||
|         'curr': ('current', lambda x: x / 1000), | ||||
|         'power': ('power', lambda x: x / 1000000), | ||||
|         'energy': ('energy', lambda x: x / 1000000), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         if not hasattr(target, 'hwmon'): | ||||
|             raise TargetError('Target does not support HWMON') | ||||
|         super(HwmonInstrument, self).__init__(target) | ||||
|  | ||||
|         self.logger.debug('Discovering available HWMON sensors...') | ||||
|         for ts in self.target.hwmon.sensors: | ||||
|             try: | ||||
|                 ts.get_file('input') | ||||
|                 measure = self.measure_map.get(ts.kind)[0] | ||||
|                 if measure: | ||||
|                     self.logger.debug('\tAdding sensor {}'.format(ts.name)) | ||||
|                     self.add_channel(_guess_site(ts), measure, name=ts.name, sensor=ts) | ||||
|                 else: | ||||
|                     self.logger.debug('\tSkipping sensor {} (unknown kind "{}")'.format(ts.name, ts.kind)) | ||||
|             except ValueError: | ||||
|                 message = 'Skipping sensor {} because it does not have an input file' | ||||
|                 self.logger.debug(message.format(ts.name)) | ||||
|                 continue | ||||
|  | ||||
|     def take_measurement(self): | ||||
|         result = [] | ||||
|         for chan in self.active_channels: | ||||
|             convert = self.measure_map[chan.sensor.kind][1] | ||||
|             value = convert(chan.sensor.get('input')) | ||||
|             result.append(Measurement(value, chan)) | ||||
|         return result | ||||
|  | ||||
|  | ||||
| def _guess_site(sensor): | ||||
|     """ | ||||
|     HWMON does not specify a standard for labeling its sensors, or for | ||||
|     device/item split (the implication is that each hwmon device a separate chip | ||||
|     with possibly several sensors on it, but not everyone adheres to that, e.g., | ||||
|     with some mobile devices splitting a chip's sensors across multiple hwmon | ||||
|     devices.  This function processes name/label of the senors to attempt to | ||||
|     identify the best "candidate" for the site to which the sensor belongs. | ||||
|     """ | ||||
|     if sensor.name == sensor.label: | ||||
|         # If no label has been specified for the sensor (in which case, it | ||||
|         # defaults to the sensor's name), assume that the "site" of the sensor | ||||
|         # is identified by the HWMON device | ||||
|         text = sensor.device.name | ||||
|     else: | ||||
|         # If a label has been specified, assume multiple sensors controlled by | ||||
|         # the same device and the label identifies the site. | ||||
|         text = sensor.label | ||||
|     # strip out sensor kind suffix, if any, as that does not indicate a site | ||||
|     for kind in ['volt', 'in', 'curr', 'power', 'energy', | ||||
|                  'temp', 'voltage', 'temperature', 'current']: | ||||
|         if kind in text.lower(): | ||||
|             regex = re.compile(r'_*{}\d*_*'.format(kind), re.I) | ||||
|             text = regex.sub('', text) | ||||
|     return text.strip() | ||||
							
								
								
									
										135
									
								
								devlib/instrument/netstats/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								devlib/instrument/netstats/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| import os | ||||
| import re | ||||
| import csv | ||||
| import tempfile | ||||
| from datetime import datetime | ||||
| from collections import defaultdict | ||||
| from itertools import izip_longest | ||||
|  | ||||
| from devlib.instrument import Instrument, MeasurementsCsv, CONTINUOUS | ||||
| from devlib.exception import TargetError, HostError | ||||
| from devlib.utils.android import ApkInfo | ||||
|  | ||||
|  | ||||
| THIS_DIR = os.path.dirname(__file__) | ||||
|  | ||||
| NETSTAT_REGEX = re.compile(r'I/(?P<tag>netstats-\d+)\(\s*\d*\): (?P<ts>\d+) ' | ||||
|                            r'"(?P<package>[^"]+)" TX: (?P<tx>\S+) RX: (?P<rx>\S+)') | ||||
|  | ||||
|  | ||||
| def extract_netstats(filepath, tag=None): | ||||
|     netstats = [] | ||||
|     with open(filepath) as fh: | ||||
|         for line in fh: | ||||
|             match = NETSTAT_REGEX.search(line) | ||||
|             if not match: | ||||
|                 continue | ||||
|             if tag and match.group('tag') != tag: | ||||
|                 continue | ||||
|             netstats.append((match.group('tag'), | ||||
|                              match.group('ts'), | ||||
|                              match.group('package'), | ||||
|                              match.group('tx'), | ||||
|                              match.group('rx'))) | ||||
|     return netstats | ||||
|  | ||||
|  | ||||
| def netstats_to_measurements(netstats): | ||||
|     measurements = defaultdict(list) | ||||
|     for row in netstats: | ||||
|         tag, ts, package, tx, rx = row  # pylint: disable=unused-variable | ||||
|         measurements[package + '_tx'].append(tx) | ||||
|         measurements[package + '_rx'].append(rx) | ||||
|     return measurements | ||||
|  | ||||
|  | ||||
| def write_measurements_csv(measurements, filepath): | ||||
|     headers = sorted(measurements.keys()) | ||||
|     columns = [measurements[h] for h in headers] | ||||
|     with open(filepath, 'wb') as wfh: | ||||
|         writer = csv.writer(wfh) | ||||
|         writer.writerow(headers) | ||||
|         writer.writerows(izip_longest(*columns)) | ||||
|  | ||||
|  | ||||
| class NetstatsInstrument(Instrument): | ||||
|  | ||||
|     mode = CONTINUOUS | ||||
|  | ||||
|     def __init__(self, target, apk=None, service='.TrafficMetricsService'): | ||||
|         """ | ||||
|         Additional paramerter: | ||||
|  | ||||
|         :apk: Path to the APK file that contains ``com.arm.devlab.netstats`` | ||||
|               package. If not specified, it will be assumed that an APK with | ||||
|               name "netstats.apk" is located in the same directory as the | ||||
|               Python module for the instrument. | ||||
|         :service: Name of the service to be launched. This service must be | ||||
|                   present in the APK. | ||||
|  | ||||
|         """ | ||||
|         if target.os != 'android': | ||||
|             raise TargetError('netstats insturment only supports Android targets') | ||||
|         if apk is None: | ||||
|             apk = os.path.join(THIS_DIR, 'netstats.apk') | ||||
|         if not os.path.isfile(apk): | ||||
|             raise HostError('APK for netstats instrument does not exist ({})'.format(apk)) | ||||
|         super(NetstatsInstrument, self).__init__(target) | ||||
|         self.apk = apk | ||||
|         self.package = ApkInfo(self.apk).package | ||||
|         self.service = service | ||||
|         self.tag = None | ||||
|         self.command = None | ||||
|         self.stop_command = 'am kill {}'.format(self.package) | ||||
|  | ||||
|         for package in self.target.list_packages(): | ||||
|             self.add_channel(package, 'tx') | ||||
|             self.add_channel(package, 'rx') | ||||
|  | ||||
|     def setup(self, force=False, *args, **kwargs): | ||||
|         if self.target.package_is_installed(self.package): | ||||
|             if force: | ||||
|                 self.logger.debug('Re-installing {} (forced)'.format(self.package)) | ||||
|                 self.target.uninstall_package(self.package) | ||||
|                 self.target.install(self.apk) | ||||
|             else: | ||||
|                 self.logger.debug('{} already present on target'.format(self.package)) | ||||
|         else: | ||||
|             self.logger.debug('Deploying {} to target'.format(self.package)) | ||||
|             self.target.install(self.apk) | ||||
|  | ||||
|     def reset(self, sites=None, kinds=None, period=None):  # pylint: disable=arguments-differ | ||||
|         super(NetstatsInstrument, self).reset(sites, kinds) | ||||
|         period_arg, packages_arg = '', '' | ||||
|         self.tag = 'netstats-{}'.format(datetime.now().strftime('%Y%m%d%H%M%s')) | ||||
|         tag_arg = ' --es tag {}'.format(self.tag) | ||||
|         if sites: | ||||
|             packages_arg = ' --es packages {}'.format(','.join(sites)) | ||||
|         if period: | ||||
|             period_arg = ' --ei period {}'.format(period) | ||||
|         self.command = 'am startservice{}{}{} {}/{}'.format(tag_arg, | ||||
|                                                             period_arg, | ||||
|                                                             packages_arg, | ||||
|                                                             self.package, | ||||
|                                                             self.service) | ||||
|         self.target.execute(self.stop_command)  # ensure the service is not running. | ||||
|  | ||||
|     def start(self): | ||||
|         if self.command is None: | ||||
|             raise RuntimeError('reset() must be called before start()') | ||||
|         self.target.execute(self.command) | ||||
|  | ||||
|     def stop(self): | ||||
|         self.target.execute(self.stop_command) | ||||
|  | ||||
|     def get_data(self, outfile): | ||||
|         raw_log_file = tempfile.mktemp() | ||||
|         self.target.dump_logcat(raw_log_file) | ||||
|         data = extract_netstats(raw_log_file) | ||||
|         measurements = netstats_to_measurements(data) | ||||
|         write_measurements_csv(measurements, outfile) | ||||
|         os.remove(raw_log_file) | ||||
|         return MeasurementsCsv(outfile, self.active_channels) | ||||
|  | ||||
|     def teardown(self): | ||||
|         self.target.uninstall_package(self.package) | ||||
							
								
								
									
										
											BIN
										
									
								
								devlib/instrument/netstats/netstats.apk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/instrument/netstats/netstats.apk
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										122
									
								
								devlib/module/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								devlib/module/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| import logging | ||||
| from inspect import isclass | ||||
|  | ||||
| from devlib.utils.misc import walk_modules | ||||
| from devlib.utils.types import identifier | ||||
|  | ||||
|  | ||||
| __module_cache = {} | ||||
|  | ||||
|  | ||||
| class Module(object): | ||||
|  | ||||
|     name = None | ||||
|     kind = None | ||||
|     # This is the stage at which the module will be installed. Current valid | ||||
|     # stages are: | ||||
|     #  'early' -- installed when the Target is first created. This should be | ||||
|     #             used for modules that do not rely on the main connection | ||||
|     #             being established (usually because the commumnitcate with the | ||||
|     #             target through some sorto of secondary connection, e.g. via | ||||
|     #             serial). | ||||
|     #  'connected' -- installed when a connection to to the target has been | ||||
|     #                 established. This is the default. | ||||
|     stage = 'connected' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     @classmethod | ||||
|     def install(cls, target, **params): | ||||
|         if cls.kind is not None: | ||||
|             attr_name = identifier(cls.kind) | ||||
|         else: | ||||
|             attr_name = identifier(cls.name) | ||||
|         if hasattr(target, attr_name): | ||||
|             existing_module = getattr(target, attr_name) | ||||
|             existing_name = getattr(existing_module, 'name', str(existing_module)) | ||||
|             message = 'Attempting to install module "{}" which already exists (new: {}, existing: {})' | ||||
|             raise ValueError(message.format(attr_name, cls.name, existing_name)) | ||||
|         setattr(target, attr_name, cls(target, **params)) | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         self.target = target | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|  | ||||
|  | ||||
| class HardRestModule(Module):  # pylint: disable=R0921 | ||||
|  | ||||
|     kind = 'hard_reset' | ||||
|  | ||||
|     def __call__(self): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|  | ||||
| class BootModule(Module):  # pylint: disable=R0921 | ||||
|  | ||||
|     kind = 'boot' | ||||
|  | ||||
|     def __call__(self): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def update(self, **kwargs): | ||||
|         for name, value in kwargs.iteritems(): | ||||
|             if not hasattr(self, name): | ||||
|                 raise ValueError('Unknown parameter "{}" for {}'.format(name, self.name)) | ||||
|             self.logger.debug('Updating "{}" to "{}"'.format(name, value)) | ||||
|             setattr(self, name, value) | ||||
|  | ||||
|  | ||||
| class FlashModule(Module): | ||||
|  | ||||
|     kind = 'flash' | ||||
|  | ||||
|     def __call__(self, image_bundle=None, images=None, boot_config=None): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|  | ||||
| def get_module(mod): | ||||
|     if not __module_cache: | ||||
|         __load_cache() | ||||
|  | ||||
|     if isinstance(mod, basestring): | ||||
|         try: | ||||
|             return __module_cache[mod] | ||||
|         except KeyError: | ||||
|             raise ValueError('Module "{}" does not exist'.format(mod)) | ||||
|     elif issubclass(mod, Module): | ||||
|         return mod | ||||
|     else: | ||||
|         raise ValueError('Not a valid module: {}'.format(mod)) | ||||
|  | ||||
|  | ||||
| def register_module(mod): | ||||
|     if not issubclass(mod, Module): | ||||
|         raise ValueError('A module must subclass devlib.Module') | ||||
|     if mod.name is None: | ||||
|         raise ValueError('A module must define a name') | ||||
|     if mod.name in __module_cache: | ||||
|         raise ValueError('Module {} already exists'.format(mod.name)) | ||||
|     __module_cache[mod.name] = mod | ||||
|  | ||||
|  | ||||
| def __load_cache(): | ||||
|     for module in walk_modules('devlib.module'): | ||||
|         for obj in vars(module).itervalues(): | ||||
|             if isclass(obj) and issubclass(obj, Module) and obj.name: | ||||
|                 register_module(obj) | ||||
							
								
								
									
										128
									
								
								devlib/module/android.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								devlib/module/android.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| # pylint: disable=attribute-defined-outside-init | ||||
| import os | ||||
| import time | ||||
| import tarfile | ||||
| import tempfile | ||||
|  | ||||
| from devlib.module import FlashModule | ||||
| from devlib.exception import HostError | ||||
| from devlib.utils.android import fastboot_flash_partition, fastboot_command | ||||
| from devlib.utils.misc import merge_dicts | ||||
|  | ||||
|  | ||||
| class FastbootFlashModule(FlashModule): | ||||
|  | ||||
|     name = 'fastboot' | ||||
|     description = """ | ||||
|     Enables automated flashing of images using the fastboot utility. | ||||
|  | ||||
|     To use this flasher, a set of image files to be flused are required. | ||||
|     In addition a mapping between partitions and image file is required. There are two ways | ||||
|     to specify those requirements: | ||||
|  | ||||
|     - Image mapping: In this mode, a mapping between partitions and images is given in the agenda. | ||||
|     - Image Bundle: In This mode a tarball is specified, which must contain all image files as well | ||||
|       as well as a partition file, named ``partitions.txt`` which contains the mapping between | ||||
|       partitions and images. | ||||
|  | ||||
|     The format of ``partitions.txt`` defines one mapping per line as such: :: | ||||
|  | ||||
|         kernel zImage-dtb | ||||
|         ramdisk ramdisk_image | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     delay = 0.5 | ||||
|     partitions_file_name = 'partitions.txt' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return target.os == 'android' | ||||
|  | ||||
|     def __call__(self, image_bundle=None, images=None, bootargs=None): | ||||
|         if bootargs: | ||||
|             raise ValueError('{} does not support boot configuration'.format(self.name)) | ||||
|         self.prelude_done = False | ||||
|         to_flash = {} | ||||
|         if image_bundle:  # pylint: disable=access-member-before-definition | ||||
|             image_bundle = expand_path(image_bundle) | ||||
|             to_flash = self._bundle_to_images(image_bundle) | ||||
|         to_flash = merge_dicts(to_flash, images or {}, should_normalize=False) | ||||
|         for partition, image_path in to_flash.iteritems(): | ||||
|             self.logger.debug('flashing {}'.format(partition)) | ||||
|             self._flash_image(self.target, partition, expand_path(image_path)) | ||||
|         fastboot_command('reboot') | ||||
|         self.target.connect(timeout=180) | ||||
|  | ||||
|     def _validate_image_bundle(self, image_bundle): | ||||
|         if not tarfile.is_tarfile(image_bundle): | ||||
|             raise HostError('File {} is not a tarfile'.format(image_bundle)) | ||||
|         with tarfile.open(image_bundle) as tar: | ||||
|             files = [tf.name for tf in tar.getmembers()] | ||||
|             if not any(pf in files for pf in (self.partitions_file_name, '{}/{}'.format(files[0], self.partitions_file_name))): | ||||
|                 HostError('Image bundle does not contain the required partition file (see documentation)') | ||||
|  | ||||
|     def _bundle_to_images(self, image_bundle): | ||||
|         """ | ||||
|         Extracts the bundle to a temporary location and creates a mapping between the contents of the bundle | ||||
|         and images to be flushed. | ||||
|         """ | ||||
|         self._validate_image_bundle(image_bundle) | ||||
|         extract_dir = tempfile.mkdtemp() | ||||
|         with tarfile.open(image_bundle) as tar: | ||||
|             tar.extractall(path=extract_dir) | ||||
|             files = [tf.name for tf in tar.getmembers()] | ||||
|             if self.partitions_file_name not in files: | ||||
|                 extract_dir = os.path.join(extract_dir, files[0]) | ||||
|         partition_file = os.path.join(extract_dir, self.partitions_file_name) | ||||
|         return get_mapping(extract_dir, partition_file) | ||||
|  | ||||
|     def _flash_image(self, target, partition, image_path): | ||||
|         if not self.prelude_done: | ||||
|             self._fastboot_prelude(target) | ||||
|         fastboot_flash_partition(partition, image_path) | ||||
|         time.sleep(self.delay) | ||||
|  | ||||
|     def _fastboot_prelude(self, target): | ||||
|         target.reset(fastboot=True) | ||||
|         time.sleep(self.delay) | ||||
|         self.prelude_done = True | ||||
|  | ||||
|  | ||||
| # utility functions | ||||
|  | ||||
| def expand_path(original_path): | ||||
|     path = os.path.abspath(os.path.expanduser(original_path)) | ||||
|     if not os.path.exists(path): | ||||
|         raise HostError('{} does not exist.'.format(path)) | ||||
|     return path | ||||
|  | ||||
|  | ||||
| def get_mapping(base_dir, partition_file): | ||||
|     mapping = {} | ||||
|     with open(partition_file) as pf: | ||||
|         for line in pf: | ||||
|             pair = line.split() | ||||
|             if len(pair) != 2: | ||||
|                 HostError('partitions.txt is not properly formated') | ||||
|             image_path = os.path.join(base_dir, pair[1]) | ||||
|             if not os.path.isfile(expand_path(image_path)): | ||||
|                 HostError('file {} was not found in the bundle or was misplaced'.format(pair[1])) | ||||
|             mapping[pair[0]] = image_path | ||||
|     return mapping | ||||
|  | ||||
							
								
								
									
										122
									
								
								devlib/module/biglittle.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								devlib/module/biglittle.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| from devlib.module import Module | ||||
|  | ||||
|  | ||||
| class BigLittleModule(Module): | ||||
|  | ||||
|     name = 'bl' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return target.big_core is not None | ||||
|  | ||||
|     @property | ||||
|     def bigs(self): | ||||
|         return [i for i, c in enumerate(self.target.platform.core_names) | ||||
|                 if c == self.target.platform.big_core] | ||||
|  | ||||
|     @property | ||||
|     def littles(self): | ||||
|         return [i for i, c in enumerate(self.target.platform.core_names) | ||||
|                 if c == self.target.platform.little_core] | ||||
|  | ||||
|     @property | ||||
|     def bigs_online(self): | ||||
|         return list(sorted(set(self.bigs).intersection(self.target.list_online_cpus()))) | ||||
|  | ||||
|     @property | ||||
|     def littles_online(self): | ||||
|         return list(sorted(set(self.littles).intersection(self.target.list_online_cpus()))) | ||||
|  | ||||
|     # hotplug | ||||
|  | ||||
|     def online_all_bigs(self): | ||||
|         self.target.hotplug.online(*self.bigs) | ||||
|  | ||||
|     def offline_all_bigs(self): | ||||
|         self.target.hotplug.offline(*self.bigs) | ||||
|  | ||||
|     def online_all_littles(self): | ||||
|         self.target.hotplug.online(*self.littles) | ||||
|  | ||||
|     def offline_all_littles(self): | ||||
|         self.target.hotplug.offline(*self.littles) | ||||
|  | ||||
|     # cpufreq | ||||
|  | ||||
|     def list_bigs_frequencies(self): | ||||
|         return self.target.cpufreq.list_frequencies(self.bigs_online[0]) | ||||
|  | ||||
|     def list_bigs_governors(self): | ||||
|         return self.target.cpufreq.list_governors(self.bigs_online[0]) | ||||
|  | ||||
|     def list_bigs_governor_tunables(self): | ||||
|         return self.target.cpufreq.list_governor_tunables(self.bigs_online[0]) | ||||
|  | ||||
|     def list_littles_frequencies(self): | ||||
|         return self.target.cpufreq.list_frequencies(self.littles_online[0]) | ||||
|  | ||||
|     def list_littles_governors(self): | ||||
|         return self.target.cpufreq.list_governors(self.littles_online[0]) | ||||
|  | ||||
|     def list_littles_governor_tunables(self): | ||||
|         return self.target.cpufreq.list_governor_tunables(self.littles_online[0]) | ||||
|  | ||||
|     def get_bigs_governor(self): | ||||
|         return self.target.cpufreq.get_governor(self.bigs_online[0]) | ||||
|  | ||||
|     def get_bigs_governor_tunables(self): | ||||
|         return self.target.cpufreq.get_governor_tunables(self.bigs_online[0]) | ||||
|  | ||||
|     def get_bigs_frequency(self): | ||||
|         return self.target.cpufreq.get_frequency(self.bigs_online[0]) | ||||
|  | ||||
|     def get_bigs_min_frequency(self): | ||||
|         return self.target.cpufreq.get_min_frequency(self.bigs_online[0]) | ||||
|  | ||||
|     def get_bigs_max_frequency(self): | ||||
|         return self.target.cpufreq.get_max_frequency(self.bigs_online[0]) | ||||
|  | ||||
|     def get_littles_governor(self): | ||||
|         return self.target.cpufreq.get_governor(self.littles_online[0]) | ||||
|  | ||||
|     def get_littles_governor_tunables(self): | ||||
|         return self.target.cpufreq.get_governor_tunables(self.littles_online[0]) | ||||
|  | ||||
|     def get_littles_frequency(self): | ||||
|         return self.target.cpufreq.get_frequency(self.littles_online[0]) | ||||
|  | ||||
|     def get_littles_min_frequency(self): | ||||
|         return self.target.cpufreq.get_min_frequency(self.littles_online[0]) | ||||
|  | ||||
|     def get_littles_max_frequency(self): | ||||
|         return self.target.cpufreq.get_max_frequency(self.littles_online[0]) | ||||
|  | ||||
|     def set_bigs_governor(self, governor, **kwargs): | ||||
|         self.target.cpufreq.set_governor(self.bigs_online[0], governor, **kwargs) | ||||
|  | ||||
|     def set_bigs_governor_tunables(self, governor, **kwargs): | ||||
|         self.target.cpufreq.set_governor_tunables(self.bigs_online[0], governor, **kwargs) | ||||
|  | ||||
|     def set_bigs_frequency(self, frequency, exact=True): | ||||
|         self.target.cpufreq.set_frequency(self.bigs_online[0], frequency, exact) | ||||
|  | ||||
|     def set_bigs_min_frequency(self, frequency, exact=True): | ||||
|         self.target.cpufreq.set_min_frequency(self.bigs_online[0], frequency, exact) | ||||
|  | ||||
|     def set_bigs_max_frequency(self, frequency, exact=True): | ||||
|         self.target.cpufreq.set_max_frequency(self.bigs_online[0], frequency, exact) | ||||
|  | ||||
|     def set_littles_governor(self, governor, **kwargs): | ||||
|         self.target.cpufreq.set_governor(self.littles_online[0], governor, **kwargs) | ||||
|  | ||||
|     def set_littles_governor_tunables(self, governor, **kwargs): | ||||
|         self.target.cpufreq.set_governor_tunables(self.littles_online[0], governor, **kwargs) | ||||
|  | ||||
|     def set_littles_frequency(self, frequency, exact=True): | ||||
|         self.target.cpufreq.set_frequency(self.littles_online[0], frequency, exact) | ||||
|  | ||||
|     def set_littles_min_frequency(self, frequency, exact=True): | ||||
|         self.target.cpufreq.set_min_frequency(self.littles_online[0], frequency, exact) | ||||
|  | ||||
|     def set_littles_max_frequency(self, frequency, exact=True): | ||||
|         self.target.cpufreq.set_max_frequency(self.littles_online[0], frequency, exact) | ||||
							
								
								
									
										206
									
								
								devlib/module/cgroups.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								devlib/module/cgroups.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| # pylint: disable=attribute-defined-outside-init | ||||
| import logging | ||||
| from collections import namedtuple | ||||
|  | ||||
| from devlib.module import Module | ||||
| from devlib.exception import TargetError | ||||
| from devlib.utils.misc import list_to_ranges, isiterable | ||||
| from devlib.utils.types import boolean | ||||
|  | ||||
|  | ||||
| class CgroupController(object): | ||||
|  | ||||
|     kind = 'cpuset' | ||||
|  | ||||
|     def __new__(cls, arg): | ||||
|         if isinstance(arg, cls): | ||||
|             return arg | ||||
|         else: | ||||
|             return object.__new__(cls, arg) | ||||
|  | ||||
|     def __init__(self, mount_name): | ||||
|         self.mount_point = None | ||||
|         self.mount_name = mount_name | ||||
|         self.logger = logging.getLogger(self.kind) | ||||
|  | ||||
|     def probe(self, target): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def mount(self, device, mount_root): | ||||
|         self.target = device | ||||
|         self.mount_point = device.path.join(mount_root, self.mount_name) | ||||
|         mounted = self.target.list_file_systems() | ||||
|         if self.mount_point in [e.mount_point for e in mounted]: | ||||
|             self.logger.debug('controller is already mounted.') | ||||
|         else: | ||||
|             self.target.execute('mkdir -p {} 2>/dev/null'.format(self.mount_point), | ||||
|                                 as_root=True) | ||||
|             self.target.execute('mount -t cgroup -o {} {} {}'.format(self.kind, | ||||
|                                                                      self.mount_name, | ||||
|                                                                      self.mount_point), | ||||
|                                 as_root=True) | ||||
|  | ||||
|  | ||||
| class CpusetGroup(object): | ||||
|  | ||||
|     def __init__(self, controller, name, cpus, mems): | ||||
|         self.controller = controller | ||||
|         self.target = controller.target | ||||
|         self.name = name | ||||
|         if name == 'root': | ||||
|             self.directory = controller.mount_point | ||||
|         else: | ||||
|             self.directory = self.target.path.join(controller.mount_point, name) | ||||
|             self.target.execute('mkdir -p {}'.format(self.directory), as_root=True) | ||||
|         self.cpus_file = self.target.path.join(self.directory, 'cpuset.cpus') | ||||
|         self.mems_file = self.target.path.join(self.directory, 'cpuset.mems') | ||||
|         self.tasks_file = self.target.path.join(self.directory, 'tasks') | ||||
|         self.set(cpus, mems) | ||||
|  | ||||
|     def set(self, cpus, mems): | ||||
|         if isiterable(cpus): | ||||
|             cpus = list_to_ranges(cpus) | ||||
|         if isiterable(mems): | ||||
|             mems = list_to_ranges(mems) | ||||
|         self.target.write_value(self.cpus_file, cpus) | ||||
|         self.target.write_value(self.mems_file, mems) | ||||
|  | ||||
|     def get(self): | ||||
|         cpus = self.target.read_value(self.cpus_file) | ||||
|         mems = self.target.read_value(self.mems_file) | ||||
|         return (cpus, mems) | ||||
|  | ||||
|     def get_tasks(self): | ||||
|         task_ids = self.target.read_value(self.tasks_file).split() | ||||
|         return map(int, task_ids) | ||||
|  | ||||
|     def add_tasks(self, tasks): | ||||
|         for tid in tasks: | ||||
|             self.add_task(tid) | ||||
|  | ||||
|     def add_task(self, tid): | ||||
|         self.target.write_value(self.tasks_file, tid, verify=False) | ||||
|  | ||||
|  | ||||
| class CpusetController(CgroupController): | ||||
|  | ||||
|     name = 'cpuset' | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(CpusetController, self).__init__(*args, **kwargs) | ||||
|         self.groups = {} | ||||
|  | ||||
|     def probe(self, target): | ||||
|         return target.config.is_enabled('cpusets') | ||||
|  | ||||
|     def mount(self, device, mount_root): | ||||
|         super(CpusetController, self).mount(device, mount_root) | ||||
|         self.create_group('root', self.target.list_online_cpus(), 0) | ||||
|  | ||||
|     def create_group(self, name, cpus, mems): | ||||
|         if not hasattr(self, 'target'): | ||||
|             raise RuntimeError('Attempting to create group for unmounted controller {}'.format(self.kind)) | ||||
|         if name in self.groups: | ||||
|             raise ValueError('Group {} already exists'.format(name)) | ||||
|         self.groups[name] = CpusetGroup(self, name, cpus, mems) | ||||
|  | ||||
|     def move_tasks(self, source, dest): | ||||
|         try: | ||||
|             source_group = self.groups[source] | ||||
|             dest_group = self.groups[dest] | ||||
|             command = 'for task in $(cat {}); do echo $task>{}; done' | ||||
|             self.target.execute(command.format(source_group.tasks_file, dest_group.tasks_file), | ||||
|                                 # this will always fail as some of the tasks | ||||
|                                 # are kthreads that cannot be migrated, but we | ||||
|                                 # don't care about those, so don't check exit | ||||
|                                 # code. | ||||
|                                 check_exit_code=False, as_root=True) | ||||
|         except KeyError as e: | ||||
|             raise ValueError('Unkown group: {}'.format(e)) | ||||
|  | ||||
|     def move_all_tasks_to(self, target_group): | ||||
|         for group in self.groups: | ||||
|             if group != target_group: | ||||
|                 self.move_tasks(group, target_group) | ||||
|  | ||||
|     def __getattr__(self, name): | ||||
|         try: | ||||
|             return self.groups[name] | ||||
|         except KeyError: | ||||
|             raise AttributeError(name) | ||||
|  | ||||
|  | ||||
|  | ||||
| CgroupSubsystemEntry = namedtuple('CgroupSubsystemEntry', 'name hierarchy num_cgroups enabled') | ||||
|  | ||||
|  | ||||
| class CgroupsModule(Module): | ||||
|  | ||||
|     name = 'cgroups' | ||||
|     controller_cls = [ | ||||
|         CpusetController, | ||||
|     ] | ||||
|  | ||||
|     cgroup_root = '/sys/fs/cgroup' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return target.config.has('cgroups') and target.is_rooted | ||||
|  | ||||
|  | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         super(CgroupsModule, self).__init__(target) | ||||
|         mounted = self.target.list_file_systems() | ||||
|         if self.cgroup_root not in [e.mount_point for e in mounted]: | ||||
|             self.target.execute('mount -t tmpfs {} {}'.format('cgroup_root', self.cgroup_root), | ||||
|                                 as_root=True) | ||||
|         else: | ||||
|             self.logger.debug('cgroup_root already mounted at {}'.format(self.cgroup_root)) | ||||
|         self.controllers = [] | ||||
|         for cls in self.controller_cls: | ||||
|             controller = cls('devlib_{}'.format(cls.name)) | ||||
|             if controller.probe(self.target): | ||||
|                 if controller.mount_name in [e.device for e in mounted]: | ||||
|                     self.logger.debug('controller {} is already mounted.'.format(controller.kind)) | ||||
|                 else: | ||||
|                     try: | ||||
|                         controller.mount(self.target, self.cgroup_root) | ||||
|                     except TargetError: | ||||
|                         message = 'cgroups {} controller is not supported by the target' | ||||
|                         raise TargetError(message.format(controller.kind)) | ||||
|  | ||||
|     def list_subsystems(self): | ||||
|         subsystems = [] | ||||
|         for line in self.target.execute('cat /proc/cgroups').split('\n')[1:]: | ||||
|             line = line.strip() | ||||
|             if not line or line.startswith('#'): | ||||
|                 continue | ||||
|             name, hierarchy, num_cgroups, enabled = line.split() | ||||
|             subsystems.append(CgroupSubsystemEntry(name, | ||||
|                                                    int(hierarchy), | ||||
|                                                    int(num_cgroups), | ||||
|                                                    boolean(enabled))) | ||||
|         return subsystems | ||||
|  | ||||
|  | ||||
|     def get_cgroup_controller(self, kind): | ||||
|         for controller in self.controllers: | ||||
|             if controller.kind == kind: | ||||
|                 return controller | ||||
|         raise ValueError(kind) | ||||
|  | ||||
							
								
								
									
										63
									
								
								devlib/module/cooling.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								devlib/module/cooling.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| from devlib.module import Module | ||||
| from devlib.utils.serial_port import open_serial_connection | ||||
|  | ||||
|  | ||||
| class MbedFanActiveCoolingModule(Module): | ||||
|  | ||||
|     name = 'mbed-fan' | ||||
|     timeout = 30 | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return True | ||||
|  | ||||
|     def __init__(self, target, port='/dev/ttyACM0', baud=115200, fan_pin=0): | ||||
|         super(MbedFanActiveCoolingModule, self).__init__(target) | ||||
|         self.port = port | ||||
|         self.baud = baud | ||||
|         self.fan_pin = fan_pin | ||||
|  | ||||
|     def start(self): | ||||
|         with open_serial_connection(timeout=self.timeout, | ||||
|                                     port=self.port, | ||||
|                                     baudrate=self.baud) as target: | ||||
|             target.sendline('motor_{}_1'.format(self.fan_pin)) | ||||
|  | ||||
|     def stop(self): | ||||
|         with open_serial_connection(timeout=self.timeout, | ||||
|                                     port=self.port, | ||||
|                                     baudrate=self.baud) as target: | ||||
|             target.sendline('motor_{}_0'.format(self.fan_pin)) | ||||
|  | ||||
|  | ||||
| class OdroidXU3ctiveCoolingModule(Module): | ||||
|  | ||||
|     name = 'odroidxu3-fan' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return target.file_exists('/sys/devices/odroid_fan.15/fan_mode') | ||||
|  | ||||
|     def start(self): | ||||
|         self.target.write_value('/sys/devices/odroid_fan.15/fan_mode', 0, verify=False) | ||||
|         self.target.write_value('/sys/devices/odroid_fan.15/pwm_duty', 255, verify=False) | ||||
|  | ||||
|     def stop(self): | ||||
|         self.target.write_value('/sys/devices/odroid_fan.15/fan_mode', 0, verify=False) | ||||
|         self.target.write_value('/sys/devices/odroid_fan.15/pwm_duty', 1, verify=False) | ||||
							
								
								
									
										315
									
								
								devlib/module/cpufreq.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								devlib/module/cpufreq.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,315 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| from devlib.module import Module | ||||
| from devlib.exception import TargetError | ||||
| from devlib.utils.misc import memoized | ||||
|  | ||||
|  | ||||
| # a dict of governor name and a list of it tunables that can't be read | ||||
| WRITE_ONLY_TUNABLES = { | ||||
|     'interactive': ['boostpulse'] | ||||
| } | ||||
|  | ||||
|  | ||||
| class CpufreqModule(Module): | ||||
|  | ||||
|     name = 'cpufreq' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         path = '/sys/devices/system/cpu/cpufreq' | ||||
|         return target.file_exists(path) | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         super(CpufreqModule, self).__init__(target) | ||||
|         self._governor_tunables = {} | ||||
|  | ||||
|     @memoized | ||||
|     def list_governors(self, cpu): | ||||
|         """Returns a list of governors supported by the cpu.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_available_governors'.format(cpu) | ||||
|         output = self.target.read_value(sysfile) | ||||
|         return output.strip().split() | ||||
|  | ||||
|     def get_governor(self, cpu): | ||||
|         """Returns the governor currently set for the specified CPU.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) | ||||
|         return self.target.read_value(sysfile) | ||||
|  | ||||
|     def set_governor(self, cpu, governor, **kwargs): | ||||
|         """ | ||||
|         Set the governor for the specified CPU. | ||||
|         See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt | ||||
|  | ||||
|         :param cpu: The CPU for which the governor is to be set. This must be | ||||
|                     the full name as it appears in sysfs, e.g. "cpu0". | ||||
|         :param governor: The name of the governor to be used. This must be | ||||
|                          supported by the specific device. | ||||
|  | ||||
|         Additional keyword arguments can be used to specify governor tunables for | ||||
|         governors that support them. | ||||
|  | ||||
|         :note: On big.LITTLE all cores in a cluster must be using the same governor. | ||||
|                Setting the governor on any core in a cluster will also set it on all | ||||
|                other cores in that cluster. | ||||
|  | ||||
|         :raises: TargetError if governor is not supported by the CPU, or if, | ||||
|                  for some reason, the governor could not be set. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         supported = self.list_governors(cpu) | ||||
|         if governor not in supported: | ||||
|             raise TargetError('Governor {} not supported for cpu {}'.format(governor, cpu)) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) | ||||
|         self.target.write_value(sysfile, governor) | ||||
|         self.set_governor_tunables(cpu, governor, **kwargs) | ||||
|  | ||||
|     def list_governor_tunables(self, cpu): | ||||
|         """Returns a list of tunables available for the governor on the specified CPU.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         governor = self.get_governor(cpu) | ||||
|         if governor not in self._governor_tunables: | ||||
|             try: | ||||
|                 tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor) | ||||
|                 self._governor_tunables[governor] = self.target.list_directory(tunables_path) | ||||
|             except TargetError:  # probably an older kernel | ||||
|                 try: | ||||
|                     tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor) | ||||
|                     self._governor_tunables[governor] = self.target.list_directory(tunables_path) | ||||
|                 except TargetError:  # governor does not support tunables | ||||
|                     self._governor_tunables[governor] = [] | ||||
|         return self._governor_tunables[governor] | ||||
|  | ||||
|     def get_governor_tunables(self, cpu): | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         governor = self.get_governor(cpu) | ||||
|         tunables = {} | ||||
|         for tunable in self.list_governor_tunables(cpu): | ||||
|             if tunable not in WRITE_ONLY_TUNABLES.get(governor, []): | ||||
|                 try: | ||||
|                     path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) | ||||
|                     tunables[tunable] = self.target.read_value(path) | ||||
|                 except TargetError:  # May be an older kernel | ||||
|                     path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) | ||||
|                     tunables[tunable] = self.target.read_value(path) | ||||
|         return tunables | ||||
|  | ||||
|     def set_governor_tunables(self, cpu, governor=None, **kwargs): | ||||
|         """ | ||||
|         Set tunables for the specified governor. Tunables should be specified as | ||||
|         keyword arguments. Which tunables and values are valid depends on the | ||||
|         governor. | ||||
|  | ||||
|         :param cpu: The cpu for which the governor will be set. This must be the | ||||
|                     full cpu name as it appears in sysfs, e.g. ``cpu0``. | ||||
|         :param governor: The name of the governor. Must be all lower case. | ||||
|  | ||||
|         The rest should be keyword parameters mapping tunable name onto the value to | ||||
|         be set for it. | ||||
|  | ||||
|         :raises: TargetError if governor specified is not a valid governor name, or if | ||||
|                  a tunable specified is not valid for the governor, or if could not set | ||||
|                  tunable. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         if governor is None: | ||||
|             governor = self.get_governor(cpu) | ||||
|         valid_tunables = self.list_governor_tunables(cpu) | ||||
|         for tunable, value in kwargs.iteritems(): | ||||
|             if tunable in valid_tunables: | ||||
|                 try: | ||||
|                     path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) | ||||
|                     self.target.write_value(path, value) | ||||
|                 except TargetError:  # May be an older kernel | ||||
|                     path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) | ||||
|                     self.target.write_value(path, value) | ||||
|             else: | ||||
|                 message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu) | ||||
|                 message += 'Available tunables are: {}'.format(valid_tunables) | ||||
|                 raise TargetError(message) | ||||
|  | ||||
|     @memoized | ||||
|     def list_frequencies(self, cpu): | ||||
|         """Returns a list of frequencies supported by the cpu or an empty list | ||||
|         if not could be found.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         try: | ||||
|             cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu) | ||||
|             output = self.target.execute(cmd) | ||||
|             available_frequencies = map(int, output.strip().split())  # pylint: disable=E1103 | ||||
|         except TargetError: | ||||
|             # On some devices scaling_frequencies  is not generated. | ||||
|             # http://adrynalyne-teachtofish.blogspot.co.uk/2011/11/how-to-enable-scalingavailablefrequenci.html | ||||
|             # Fall back to parsing stats/time_in_state | ||||
|             cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/stats/time_in_state'.format(cpu) | ||||
|             out_iter = iter(self.target.execute(cmd).strip().split()) | ||||
|             available_frequencies = map(int, reversed([f for f, _ in zip(out_iter, out_iter)])) | ||||
|         return available_frequencies | ||||
|  | ||||
|     def get_min_frequency(self, cpu): | ||||
|         """ | ||||
|         Returns the min frequency currently set for the specified CPU. | ||||
|  | ||||
|         Warning, this method does not check if the cpu is online or not. It will | ||||
|         try to read the minimum frequency and the following exception will be | ||||
|         raised :: | ||||
|  | ||||
|         :raises: TargetError if for some reason the frequency could not be read. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu) | ||||
|         return self.target.read_int(sysfile) | ||||
|  | ||||
|     def set_min_frequency(self, cpu, frequency, exact=True): | ||||
|         """ | ||||
|         Set's the minimum value for CPU frequency. Actual frequency will | ||||
|         depend on the Governor used and may vary during execution. The value should be | ||||
|         either an int or a string representing an integer. The Value must also be | ||||
|         supported by the device. The available frequencies can be obtained by calling | ||||
|         get_frequencies() or examining | ||||
|  | ||||
|         /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies | ||||
|  | ||||
|         on the device. | ||||
|  | ||||
|         :raises: TargetError if the frequency is not supported by the CPU, or if, for | ||||
|                  some reason, frequency could not be set. | ||||
|         :raises: ValueError if ``frequency`` is not an integer. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         available_frequencies = self.list_frequencies(cpu) | ||||
|         try: | ||||
|             value = int(frequency) | ||||
|             if exact and available_frequencies and value not in available_frequencies: | ||||
|                 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, | ||||
|                                                                                         value, | ||||
|                                                                                         available_frequencies)) | ||||
|             sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu) | ||||
|             self.target.write_value(sysfile, value) | ||||
|         except ValueError: | ||||
|             raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) | ||||
|  | ||||
|     def get_frequency(self, cpu): | ||||
|         """ | ||||
|         Returns the current frequency currently set for the specified CPU. | ||||
|  | ||||
|         Warning, this method does not check if the cpu is online or not. It will | ||||
|         try to read the current frequency and the following exception will be | ||||
|         raised :: | ||||
|  | ||||
|         :raises: TargetError if for some reason the frequency could not be read. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq'.format(cpu) | ||||
|         return self.target.read_int(sysfile) | ||||
|  | ||||
|     def set_frequency(self, cpu, frequency, exact=True): | ||||
|         """ | ||||
|         Set's the minimum value for CPU frequency. Actual frequency will | ||||
|         depend on the Governor used and may vary during execution. The value should be | ||||
|         either an int or a string representing an integer. | ||||
|  | ||||
|         If ``exact`` flag is set (the default), the Value must also be supported by | ||||
|         the device. The available frequencies can be obtained by calling | ||||
|         get_frequencies() or examining | ||||
|  | ||||
|         /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies | ||||
|  | ||||
|         on the device (if it exists). | ||||
|  | ||||
|         :raises: TargetError if the frequency is not supported by the CPU, or if, for | ||||
|                  some reason, frequency could not be set. | ||||
|         :raises: ValueError if ``frequency`` is not an integer. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         try: | ||||
|             value = int(frequency) | ||||
|             if exact: | ||||
|                 available_frequencies = self.list_frequencies(cpu) | ||||
|                 if available_frequencies and value not in available_frequencies: | ||||
|                     raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, | ||||
|                                                                                             value, | ||||
|                                                                                             available_frequencies)) | ||||
|             if self.get_governor(cpu) != 'userspace': | ||||
|                 raise TargetError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu)) | ||||
|             sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu) | ||||
|             self.target.write_value(sysfile, value, verify=False) | ||||
|         except ValueError: | ||||
|             raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) | ||||
|  | ||||
|     def get_max_frequency(self, cpu): | ||||
|         """ | ||||
|         Returns the max frequency currently set for the specified CPU. | ||||
|  | ||||
|         Warning, this method does not check if the cpu is online or not. It will | ||||
|         try to read the maximum frequency and the following exception will be | ||||
|         raised :: | ||||
|  | ||||
|         :raises: TargetError if for some reason the frequency could not be read. | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu) | ||||
|         return self.target.read_int(sysfile) | ||||
|  | ||||
|     def set_max_frequency(self, cpu, frequency, exact=True): | ||||
|         """ | ||||
|         Set's the minimum value for CPU frequency. Actual frequency will | ||||
|         depend on the Governor used and may vary during execution. The value should be | ||||
|         either an int or a string representing an integer. The Value must also be | ||||
|         supported by the device. The available frequencies can be obtained by calling | ||||
|         get_frequencies() or examining | ||||
|  | ||||
|         /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies | ||||
|  | ||||
|         on the device. | ||||
|  | ||||
|         :raises: TargetError if the frequency is not supported by the CPU, or if, for | ||||
|                  some reason, frequency could not be set. | ||||
|         :raises: ValueError if ``frequency`` is not an integer. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         available_frequencies = self.list_frequencies(cpu) | ||||
|         try: | ||||
|             value = int(frequency) | ||||
|             if exact and available_frequencies and value not in available_frequencies: | ||||
|                 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, | ||||
|                                                                                         value, | ||||
|                                                                                         available_frequencies)) | ||||
|             sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu) | ||||
|             self.target.write_value(sysfile, value) | ||||
|         except ValueError: | ||||
|             raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) | ||||
							
								
								
									
										138
									
								
								devlib/module/cpuidle.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								devlib/module/cpuidle.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| # pylint: disable=attribute-defined-outside-init | ||||
| from devlib.module import Module | ||||
| from devlib.utils.misc import memoized | ||||
| from devlib.utils.types import integer, boolean | ||||
|  | ||||
|  | ||||
| class CpuidleState(object): | ||||
|  | ||||
|     @property | ||||
|     def usage(self): | ||||
|         return integer(self.get('usage')) | ||||
|  | ||||
|     @property | ||||
|     def time(self): | ||||
|         return integer(self.get('time')) | ||||
|  | ||||
|     @property | ||||
|     def is_enabled(self): | ||||
|         return not boolean(self.get('disable')) | ||||
|  | ||||
|     @property | ||||
|     def ordinal(self): | ||||
|         i = len(self.id) | ||||
|         while self.id[i - 1].isdigit(): | ||||
|             i -= 1 | ||||
|             if not i: | ||||
|                 raise ValueError('invalid idle state name: "{}"'.format(self.id)) | ||||
|         return int(self.id[i:]) | ||||
|  | ||||
|     def __init__(self, target, index, path): | ||||
|         self.target = target | ||||
|         self.index = index | ||||
|         self.path = path | ||||
|         self.id = self.target.path.basename(self.path) | ||||
|         self.cpu = self.target.path.basename(self.target.path.dirname(path)) | ||||
|         self.desc = self.get('desc') | ||||
|         self.name = self.get('name') | ||||
|         self.latency = self.get('latency') | ||||
|         self.power = self.get('power') | ||||
|  | ||||
|     def enable(self): | ||||
|         self.set('disable', 0) | ||||
|  | ||||
|     def disable(self): | ||||
|         self.set('disable', 1) | ||||
|  | ||||
|     def get(self, prop): | ||||
|         property_path = self.target.path.join(self.path, prop) | ||||
|         return self.target.read_value(property_path) | ||||
|  | ||||
|     def set(self, prop, value): | ||||
|         property_path = self.target.path.join(self.path, prop) | ||||
|         self.target.write_value(property_path, value) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         if isinstance(other, CpuidleState): | ||||
|             return (self.name == other.name) and (self.desc == other.desc) | ||||
|         elif isinstance(other, basestring): | ||||
|             return (self.name == other) or (self.desc == other) | ||||
|         else: | ||||
|             return False | ||||
|  | ||||
|     def __ne__(self, other): | ||||
|         return not self.__eq__(other) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return 'CpuidleState({}, {})'.format(self.name, self.desc) | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class Cpuidle(Module): | ||||
|  | ||||
|     name = 'cpuidle' | ||||
|     root_path = '/sys/devices/system/cpu/cpuidle' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return target.file_exists(Cpuidle.root_path) | ||||
|  | ||||
|     def get_driver(self): | ||||
|         return self.target.read_value(self.target.path.join(self.root_path, 'current_driver')) | ||||
|  | ||||
|     def get_governor(self): | ||||
|         return self.target.read_value(self.target.path.join(self.root_path, 'current_governor_ro')) | ||||
|  | ||||
|     @memoized | ||||
|     def get_states(self, cpu=0): | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         states_dir = self.target.path.join(self.target.path.dirname(self.root_path), cpu, 'cpuidle') | ||||
|         idle_states = [] | ||||
|         for state in self.target.list_directory(states_dir): | ||||
|             if state.startswith('state'): | ||||
|                 index = int(state[5:]) | ||||
|                 idle_states.append(CpuidleState(self.target, index, self.target.path.join(states_dir, state))) | ||||
|         return idle_states | ||||
|  | ||||
|     def get_state(self, state, cpu=0): | ||||
|         if isinstance(state, int): | ||||
|             try: | ||||
|                 self.get_states(cpu)[state].enable() | ||||
|             except IndexError: | ||||
|                 raise ValueError('Cpuidle state {} does not exist'.format(state)) | ||||
|         else:  # assume string-like | ||||
|             for s in self.get_states(cpu): | ||||
|                 if state in [s.id, s.name, s.desc]: | ||||
|                     return s | ||||
|             raise ValueError('Cpuidle state {} does not exist'.format(state)) | ||||
|  | ||||
|     def enable(self, state, cpu=0): | ||||
|         self.get_state(state, cpu).enable() | ||||
|  | ||||
|     def disable(self, state, cpu=0): | ||||
|         self.get_state(state, cpu).disable() | ||||
|  | ||||
|     def enable_all(self, cpu=0): | ||||
|         for state in self.get_states(cpu): | ||||
|             state.enable() | ||||
|  | ||||
|     def disable_all(self, cpu=0): | ||||
|         for state in self.get_states(cpu): | ||||
|             state.disable() | ||||
|  | ||||
							
								
								
									
										35
									
								
								devlib/module/hotplug.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								devlib/module/hotplug.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| from devlib.module import Module | ||||
|  | ||||
|  | ||||
| class HotplugModule(Module): | ||||
|  | ||||
|     name = 'hotplug' | ||||
|     base_path = '/sys/devices/system/cpu' | ||||
|  | ||||
|     @classmethod | ||||
|     def probe(cls, target):  # pylint: disable=arguments-differ | ||||
|         path = cls._cpu_path(target, 0) | ||||
|         return target.file_exists(path) and target.is_rooted | ||||
|  | ||||
|     @classmethod | ||||
|     def _cpu_path(cls, target, cpu): | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         return target.path.join(cls.base_path, cpu, 'online') | ||||
|  | ||||
|     def online_all(self): | ||||
|         self.online(*range(self.target.number_of_cpus)) | ||||
|  | ||||
|     def online(self, *args): | ||||
|         for cpu in args: | ||||
|             self.hotplug(cpu, online=True) | ||||
|  | ||||
|     def offline(self, *args): | ||||
|         for cpu in args: | ||||
|             self.hotplug(cpu, online=False) | ||||
|  | ||||
|     def hotplug(self, cpu, online): | ||||
|         path = self._cpu_path(self.target, cpu) | ||||
|         value = 1 if online else 0 | ||||
|         self.target.write_value(path, value) | ||||
|  | ||||
							
								
								
									
										140
									
								
								devlib/module/hwmon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								devlib/module/hwmon.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| import re | ||||
| from collections import defaultdict | ||||
|  | ||||
| from devlib.module import Module | ||||
| from devlib.utils.types import integer | ||||
|  | ||||
|  | ||||
| HWMON_ROOT = '/sys/class/hwmon' | ||||
| HWMON_FILE_REGEX = re.compile(r'(?P<kind>\w+?)(?P<number>\d+)_(?P<item>\w+)') | ||||
|  | ||||
|  | ||||
| class HwmonSensor(object): | ||||
|  | ||||
|     def __init__(self, device, path, kind, number): | ||||
|         self.device = device | ||||
|         self.path = path | ||||
|         self.kind = kind | ||||
|         self.number = number | ||||
|         self.target = self.device.target | ||||
|         self.name = '{}/{}{}'.format(self.device.name, self.kind, self.number) | ||||
|         self.label = self.name | ||||
|         self.items = set() | ||||
|  | ||||
|     def add(self, item): | ||||
|         self.items.add(item) | ||||
|         if item == 'label': | ||||
|             self.label = self.get('label') | ||||
|  | ||||
|     def get(self, item): | ||||
|         path = self.get_file(item) | ||||
|         value = self.target.read_value(path) | ||||
|         try: | ||||
|             return  integer(value) | ||||
|         except (TypeError, ValueError): | ||||
|             return value | ||||
|  | ||||
|     def set(self, item, value): | ||||
|         path = self.get_file(item) | ||||
|         self.target.write_value(path, value) | ||||
|  | ||||
|     def get_file(self, item): | ||||
|         if item not in self.items: | ||||
|             raise ValueError('item "{}" does not exist for {}'.format(item, self.name)) | ||||
|         filename = '{}{}_{}'.format(self.kind, self.number, item) | ||||
|         return self.target.path.join(self.path, filename) | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.name != self.label: | ||||
|             text = 'HS({}, {})'.format(self.name, self.label) | ||||
|         else: | ||||
|             text = 'HS({})'.format(self.name) | ||||
|         return text | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class HwmonDevice(object): | ||||
|  | ||||
|     @property | ||||
|     def sensors(self): | ||||
|         all_sensors = [] | ||||
|         for sensors_of_kind in self._sensors.itervalues(): | ||||
|             all_sensors.extend(sensors_of_kind.values()) | ||||
|         return all_sensors | ||||
|  | ||||
|     def __init__(self, target, path): | ||||
|         self.target = target | ||||
|         self.path = path | ||||
|         self.name = self.target.read_value(self.target.path.join(self.path, 'name')) | ||||
|         self._sensors = defaultdict(dict) | ||||
|         path = self.path | ||||
|         if not path.endswith(self.target.path.sep): | ||||
|             path += self.target.path.sep | ||||
|         for entry in self.target.list_directory(path): | ||||
|             match = HWMON_FILE_REGEX.search(entry) | ||||
|             if match: | ||||
|                 kind = match.group('kind') | ||||
|                 number = int(match.group('number')) | ||||
|                 item = match.group('item') | ||||
|                 if number not in self._sensors[kind]: | ||||
|                     sensor = HwmonSensor(self, self.path, kind, number) | ||||
|                     self._sensors[kind][number] = sensor | ||||
|                 self._sensors[kind][number].add(item) | ||||
|  | ||||
|     def get(self, kind, number=None): | ||||
|         if number is None: | ||||
|             return [s for _, s in sorted(self._sensors[kind].iteritems(), | ||||
|                                          key=lambda x: x[0])] | ||||
|         else: | ||||
|             return self._sensors[kind].get(number) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return 'HD({})'.format(self.name) | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class HwmonModule(Module): | ||||
|  | ||||
|     name = 'hwmon' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return target.file_exists(HWMON_ROOT) | ||||
|  | ||||
|     @property | ||||
|     def sensors(self): | ||||
|         all_sensors = [] | ||||
|         for device in self.devices: | ||||
|             all_sensors.extend(device.sensors) | ||||
|         return all_sensors | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         super(HwmonModule, self).__init__(target) | ||||
|         self.root = HWMON_ROOT | ||||
|         self.devices = [] | ||||
|         self.scan() | ||||
|  | ||||
|     def scan(self): | ||||
|         for entry in self.target.list_directory(self.root): | ||||
|             if entry.startswith('hwmon'): | ||||
|                 entry_path = self.target.path.join(self.root, entry) | ||||
|                 if self.target.file_exists(self.target.path.join(entry_path, 'name')): | ||||
|                     device = HwmonDevice(self.target, entry_path) | ||||
|                     self.devices.append(device) | ||||
|  | ||||
							
								
								
									
										386
									
								
								devlib/module/vexpress.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								devlib/module/vexpress.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,386 @@ | ||||
| # | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| import os | ||||
| import time | ||||
| import tarfile | ||||
| import shutil | ||||
|  | ||||
| from devlib.module import HardRestModule, BootModule, FlashModule | ||||
| from devlib.exception import TargetError, HostError | ||||
| from devlib.utils.serial_port import open_serial_connection, pulse_dtr, write_characters | ||||
| from devlib.utils.uefi import UefiMenu, UefiConfig | ||||
| from devlib.utils.uboot import UbootMenu | ||||
|  | ||||
|  | ||||
| AUTOSTART_MESSAGE = 'Press Enter to stop auto boot...' | ||||
| POWERUP_MESSAGE = 'Powering up system...' | ||||
| DEFAULT_MCC_PROMPT = 'Cmd>' | ||||
|  | ||||
|  | ||||
| class VexpressDtrHardReset(HardRestModule): | ||||
|  | ||||
|     name = 'vexpress-dtr' | ||||
|     stage = 'early' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return True | ||||
|  | ||||
|     def __init__(self, target, port='/dev/ttyS0', baudrate=115200, | ||||
|                  mcc_prompt=DEFAULT_MCC_PROMPT, timeout=300): | ||||
|         super(VexpressDtrHardReset, self).__init__(target) | ||||
|         self.port = port | ||||
|         self.baudrate = baudrate | ||||
|         self.mcc_prompt = mcc_prompt | ||||
|         self.timeout = timeout | ||||
|  | ||||
|     def __call__(self): | ||||
|         try: | ||||
|             if self.target.is_connected: | ||||
|                 self.target.execute('sync') | ||||
|         except TargetError: | ||||
|             pass | ||||
|         with open_serial_connection(port=self.port, | ||||
|                                     baudrate=self.baudrate, | ||||
|                                     timeout=self.timeout, | ||||
|                                     init_dtr=0, | ||||
|                                     get_conn=True) as (_, conn): | ||||
|             pulse_dtr(conn, state=True, duration=0.1)  # TRM specifies a pulse of >=100ms | ||||
|  | ||||
|  | ||||
| class VexpressReboottxtHardReset(HardRestModule): | ||||
|  | ||||
|     name = 'vexpress-reboottxt' | ||||
|     stage = 'early' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return True | ||||
|  | ||||
|     def __init__(self, target, | ||||
|                  port='/dev/ttyS0', baudrate=115200, | ||||
|                  path='/media/VEMSD', | ||||
|                  mcc_prompt=DEFAULT_MCC_PROMPT, timeout=30, short_delay=1): | ||||
|         super(VexpressReboottxtHardReset, self).__init__(target) | ||||
|         self.port = port | ||||
|         self.baudrate = baudrate | ||||
|         self.path = path | ||||
|         self.mcc_prompt = mcc_prompt | ||||
|         self.timeout = timeout | ||||
|         self.short_delay = short_delay | ||||
|         self.filepath = os.path.join(path, 'reboot.txt') | ||||
|  | ||||
|     def __call__(self): | ||||
|         try: | ||||
|             if self.target.is_connected: | ||||
|                 self.target.execute('sync') | ||||
|         except TargetError: | ||||
|             pass | ||||
|  | ||||
|         if not os.path.exists(self.path): | ||||
|             self.logger.debug('{} does not exisit; attempting to mount...'.format(self.path)) | ||||
|             with open_serial_connection(port=self.port, | ||||
|                                         baudrate=self.baudrate, | ||||
|                                         timeout=self.timeout, | ||||
|                                         init_dtr=0) as tty: | ||||
|                 wait_for_vemsd(self.path, tty, self.mcc_prompt, self.short_delay) | ||||
|         with open(self.filepath, 'w'): | ||||
|             pass | ||||
|  | ||||
|  | ||||
| class VexpressBootModule(BootModule): | ||||
|  | ||||
|     stage = 'early' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         return True | ||||
|  | ||||
|     def __init__(self, target, uefi_entry=None, | ||||
|                  port='/dev/ttyS0', baudrate=115200, | ||||
|                  mcc_prompt=DEFAULT_MCC_PROMPT, | ||||
|                  timeout=120, short_delay=1): | ||||
|         super(VexpressBootModule, self).__init__(target) | ||||
|         self.port = port | ||||
|         self.baudrate = baudrate | ||||
|         self.uefi_entry = uefi_entry | ||||
|         self.mcc_prompt = mcc_prompt | ||||
|         self.timeout = timeout | ||||
|         self.short_delay = short_delay | ||||
|  | ||||
|     def __call__(self): | ||||
|         with open_serial_connection(port=self.port, | ||||
|                                     baudrate=self.baudrate, | ||||
|                                     timeout=self.timeout, | ||||
|                                     init_dtr=0) as tty: | ||||
|             self.get_through_early_boot(tty) | ||||
|             self.perform_boot_sequence(tty) | ||||
|             self.wait_for_android_prompt(tty) | ||||
|  | ||||
|     def perform_boot_sequence(self, tty): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def get_through_early_boot(self, tty): | ||||
|         self.logger.debug('Establishing initial state...') | ||||
|         tty.sendline('') | ||||
|         i = tty.expect([AUTOSTART_MESSAGE, POWERUP_MESSAGE, self.mcc_prompt]) | ||||
|         if i == 2: | ||||
|             self.logger.debug('Saw MCC prompt.') | ||||
|             time.sleep(self.short_delay) | ||||
|             tty.sendline('reboot') | ||||
|         elif i == 1: | ||||
|             self.logger.debug('Saw powering up message (assuming soft reboot).') | ||||
|         else: | ||||
|             self.logger.debug('Saw auto boot message.') | ||||
|             tty.sendline('') | ||||
|             time.sleep(self.short_delay) | ||||
|             tty.sendline('reboot') | ||||
|  | ||||
|     def get_uefi_menu(self, tty): | ||||
|         menu = UefiMenu(tty) | ||||
|         self.logger.debug('Waiting for UEFI menu...') | ||||
|         menu.wait(timeout=self.timeout) | ||||
|         return menu | ||||
|  | ||||
|     def wait_for_android_prompt(self, tty): | ||||
|         self.logger.debug('Waiting for the Android prompt.') | ||||
|         tty.expect(self.target.shell_prompt, timeout=self.timeout) | ||||
|         # This delay is needed to allow the platform some time to finish | ||||
|         # initilizing; querying the ip address too early from connect() may | ||||
|         # result in a bogus address being assigned to eth0. | ||||
|         time.sleep(5) | ||||
|  | ||||
|  | ||||
| class VexpressUefiBoot(VexpressBootModule): | ||||
|  | ||||
|     name = 'vexpress-uefi' | ||||
|  | ||||
|     def __init__(self, target, uefi_entry, | ||||
|                  image, fdt, bootargs, initrd, | ||||
|                  *args, **kwargs): | ||||
|         super(VexpressUefiBoot, self).__init__(target, uefi_entry=uefi_entry, | ||||
|                                                *args, **kwargs) | ||||
|         self.uefi_config = self._create_config(image, fdt, bootargs, initrd) | ||||
|  | ||||
|     def perform_boot_sequence(self, tty): | ||||
|         menu = self.get_uefi_menu(tty) | ||||
|         try: | ||||
|             menu.select(self.uefi_entry) | ||||
|         except LookupError: | ||||
|             self.logger.debug('{} UEFI entry not found.'.format(self.uefi_entry)) | ||||
|             self.logger.debug('Attempting to create one using default flasher configuration.') | ||||
|             menu.create_entry(self.uefi_entry, self.uefi_config) | ||||
|             menu.select(self.uefi_entry) | ||||
|  | ||||
|     def _create_config(self, image, fdt, bootargs, initrd):  # pylint: disable=R0201 | ||||
|         config_dict = { | ||||
|             'image_name': image, | ||||
|             'image_args': bootargs, | ||||
|             'initrd': initrd, | ||||
|         } | ||||
|  | ||||
|         if fdt: | ||||
|             config_dict['fdt_support'] = True | ||||
|             config_dict['fdt_path'] = fdt | ||||
|         else: | ||||
|             config_dict['fdt_support'] = False | ||||
|  | ||||
|         return UefiConfig(config_dict) | ||||
|  | ||||
|  | ||||
| class VexpressUefiShellBoot(VexpressBootModule): | ||||
|  | ||||
|     name = 'vexpress-uefi-shell' | ||||
|  | ||||
|     def __init__(self, target, uefi_entry='^Shell$', | ||||
|                  efi_shell_prompt='Shell>', | ||||
|                  image='kernel', bootargs=None, | ||||
|                  *args, **kwargs): | ||||
|         super(VexpressUefiShellBoot, self).__init__(target, uefi_entry=uefi_entry, | ||||
|                                                     *args, **kwargs) | ||||
|         self.efi_shell_prompt = efi_shell_prompt | ||||
|         self.image = image | ||||
|         self.bootargs = bootargs | ||||
|  | ||||
|     def perform_boot_sequence(self, tty): | ||||
|         menu = self.get_uefi_menu(tty) | ||||
|         try: | ||||
|             menu.select(self.uefi_entry) | ||||
|         except LookupError: | ||||
|             raise TargetError('Did not see "{}" UEFI entry.'.format(self.uefi_entry)) | ||||
|         tty.expect(self.efi_shell_prompt, timeout=self.timeout) | ||||
|         if self.bootargs: | ||||
|             tty.sendline('')  # stop default boot | ||||
|             time.sleep(self.short_delay) | ||||
|             efi_shell_command = '{} {}'.format(self.image, self.bootargs) | ||||
|             self.logger.debug(efi_shell_command) | ||||
|             write_characters(tty, efi_shell_command) | ||||
|             tty.sendline('\r\n') | ||||
|  | ||||
|  | ||||
| class VexpressUBoot(VexpressBootModule): | ||||
|  | ||||
|     name = 'vexpress-u-boot' | ||||
|  | ||||
|     def __init__(self, target, env=None, | ||||
|                  *args, **kwargs): | ||||
|         super(VexpressUBoot, self).__init__(target, *args, **kwargs) | ||||
|         self.env = env | ||||
|  | ||||
|     def perform_boot_sequence(self, tty): | ||||
|         if self.env is None: | ||||
|             return  # Will boot automatically | ||||
|  | ||||
|         menu = UbootMenu(tty) | ||||
|         self.logger.debug('Waiting for U-Boot prompt...') | ||||
|         menu.open(timeout=120) | ||||
|         for var, value in self.env.iteritems(): | ||||
|             menu.setenv(var, value) | ||||
|         menu.boot() | ||||
|  | ||||
|  | ||||
| class VexpressBootmon(VexpressBootModule): | ||||
|  | ||||
|     name = 'vexpress-bootmon' | ||||
|  | ||||
|     def __init__(self, target, | ||||
|                  image, fdt, initrd, bootargs, | ||||
|                  uses_bootscript=False, | ||||
|                  bootmon_prompt='>', | ||||
|                  *args, **kwargs): | ||||
|         super(VexpressBootmon, self).__init__(target, *args, **kwargs) | ||||
|         self.image = image | ||||
|         self.fdt = fdt | ||||
|         self.initrd = initrd | ||||
|         self.bootargs = bootargs | ||||
|         self.uses_bootscript = uses_bootscript | ||||
|         self.bootmon_prompt = bootmon_prompt | ||||
|  | ||||
|     def perform_boot_sequence(self, tty): | ||||
|         if self.uses_bootscript: | ||||
|             return  # Will boot automatically | ||||
|  | ||||
|         time.sleep(self.short_delay) | ||||
|         tty.expect(self.bootmon_prompt, timeout=self.timeout) | ||||
|         with open_serial_connection(port=self.port, | ||||
|                                     baudrate=self.baudrate, | ||||
|                                     timeout=self.timeout, | ||||
|                                     init_dtr=0) as tty: | ||||
|             write_characters(tty, 'fl linux fdt {}'.format(self.fdt)) | ||||
|             write_characters(tty, 'fl linux initrd {}'.format(self.initrd)) | ||||
|             write_characters(tty, 'fl linux boot {} {}'.format(self.image, | ||||
|                                                                self.bootargs)) | ||||
|  | ||||
|  | ||||
| class VersatileExpressFlashModule(FlashModule): | ||||
|  | ||||
|     name = 'vexpress-vemsd' | ||||
|     description = """ | ||||
|     Enables flashing of kernels and firmware to ARM Versatile Express devices. | ||||
|  | ||||
|     This modules enables flashing of image bundles or individual images to ARM | ||||
|     Versatile Express-based devices (e.g. JUNO) via host-mounted MicroSD on the | ||||
|     board. | ||||
|  | ||||
|     The bundle, if specified, must reflect the directory structure of the MicroSD | ||||
|     and will be extracted directly into the location it is mounted on the host. The | ||||
|     images, if  specified, must be a dict mapping the absolute path of the image on | ||||
|     the host to the destination path within the board's MicroSD; the destination path | ||||
|     may be either absolute, or relative to the MicroSD mount location. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     stage = 'early' | ||||
|  | ||||
|     @staticmethod | ||||
|     def probe(target): | ||||
|         if not target.has('hard_reset'): | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|     def __init__(self, target, vemsd_mount, mcc_prompt=DEFAULT_MCC_PROMPT, timeout=30, short_delay=1): | ||||
|         super(VersatileExpressFlashModule, self).__init__(target) | ||||
|         self.vemsd_mount = vemsd_mount | ||||
|         self.mcc_prompt = mcc_prompt | ||||
|         self.timeout = timeout | ||||
|         self.short_delay = short_delay | ||||
|  | ||||
|     def __call__(self, image_bundle=None, images=None, bootargs=None): | ||||
|         self.target.hard_reset() | ||||
|         with open_serial_connection(port=self.target.platform.serial_port, | ||||
|                                     baudrate=self.target.platform.baudrate, | ||||
|                                     timeout=self.timeout, | ||||
|                                     init_dtr=0) as tty: | ||||
|             i = tty.expect([self.mcc_prompt, AUTOSTART_MESSAGE]) | ||||
|             if i: | ||||
|                 tty.sendline('') | ||||
|             wait_for_vemsd(self.vemsd_mount, tty, self.mcc_prompt, self.short_delay) | ||||
|         try: | ||||
|             if image_bundle: | ||||
|                 self._deploy_image_bundle(image_bundle) | ||||
|             if images: | ||||
|                 self._overlay_images(images) | ||||
|             os.system('sync') | ||||
|         except (IOError, OSError), e: | ||||
|             msg = 'Could not deploy images to {}; got: {}' | ||||
|             raise TargetError(msg.format(self.vemsd_mount, e)) | ||||
|         self.target.boot() | ||||
|         self.target.connect(timeout=30) | ||||
|  | ||||
|     def _deploy_image_bundle(self, bundle): | ||||
|         self.logger.debug('Validating {}'.format(bundle)) | ||||
|         validate_image_bundle(bundle) | ||||
|         self.logger.debug('Extracting {} into {}...'.format(bundle, self.vemsd_mount)) | ||||
|         with tarfile.open(bundle) as tar: | ||||
|             tar.extractall(self.vemsd_mount) | ||||
|  | ||||
|     def _overlay_images(self, images): | ||||
|         for dest, src in images.iteritems(): | ||||
|             dest = os.path.join(self.vemsd_mount, dest) | ||||
|             self.logger.debug('Copying {} to {}'.format(src, dest)) | ||||
|             shutil.copy(src, dest) | ||||
|  | ||||
|  | ||||
| # utility functions | ||||
|  | ||||
| def validate_image_bundle(bundle): | ||||
|     if not tarfile.is_tarfile(bundle): | ||||
|         raise HostError('Image bundle {} does not appear to be a valid TAR file.'.format(bundle)) | ||||
|     with tarfile.open(bundle) as tar: | ||||
|         try: | ||||
|             tar.getmember('config.txt') | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 tar.getmember('./config.txt') | ||||
|             except KeyError: | ||||
|                 msg = 'Tarball {} does not appear to be a valid image bundle (did not see config.txt).' | ||||
|                 raise HostError(msg.format(bundle)) | ||||
|  | ||||
|  | ||||
| def wait_for_vemsd(vemsd_mount, tty, mcc_prompt=DEFAULT_MCC_PROMPT, short_delay=1, retries=3): | ||||
|     attempts = 1 + retries | ||||
|     path = os.path.join(vemsd_mount, 'config.txt') | ||||
|     if os.path.exists(path): | ||||
|         return | ||||
|     for _ in xrange(attempts): | ||||
|         tty.sendline('')  # clear any garbage | ||||
|         tty.expect(mcc_prompt, timeout=short_delay) | ||||
|         tty.sendline('usb_on') | ||||
|         time.sleep(short_delay * 3) | ||||
|         if os.path.exists(path): | ||||
|             return | ||||
|     raise TargetError('Could not mount {}'.format(vemsd_mount)) | ||||
|  | ||||
							
								
								
									
										81
									
								
								devlib/platform/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								devlib/platform/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| import logging | ||||
|  | ||||
|  | ||||
| class Platform(object): | ||||
|  | ||||
|     @property | ||||
|     def number_of_clusters(self): | ||||
|         return len(set(self.core_clusters)) | ||||
|  | ||||
|     def __init__(self, | ||||
|                  name=None, | ||||
|                  core_names=None, | ||||
|                  core_clusters=None, | ||||
|                  big_core=None, | ||||
|                  model=None, | ||||
|                  modules=None, | ||||
|                  ): | ||||
|         self.name = name | ||||
|         self.core_names = core_names or [] | ||||
|         self.core_clusters = core_clusters or [] | ||||
|         self.big_core = big_core | ||||
|         self.little_core = None | ||||
|         self.model = model | ||||
|         self.modules = modules or [] | ||||
|         self.logger = logging.getLogger(self.name) | ||||
|         if not self.core_clusters and self.core_names: | ||||
|             self._set_core_clusters_from_core_names() | ||||
|         self._validate() | ||||
|  | ||||
|     def init_target_connection(self, target): | ||||
|         # May be ovewritten by subclasses to provide target-specific | ||||
|         # connection initialisation. | ||||
|         pass | ||||
|  | ||||
|     def update_from_target(self, target): | ||||
|         if not self.core_names: | ||||
|             self.core_names = target.cpuinfo.cpu_names | ||||
|             self._set_core_clusters_from_core_names() | ||||
|         if not self.big_core and self.number_of_clusters == 2: | ||||
|             big_idx = self.core_clusters.index(max(self.core_clusters)) | ||||
|             self.big_core = self.core_names[big_idx] | ||||
|         if not self.core_clusters and self.core_names: | ||||
|             self._set_core_clusters_from_core_names() | ||||
|         if not self.model: | ||||
|             self._set_model_from_target(target) | ||||
|         if not self.name: | ||||
|             self.name = self.model | ||||
|         self._validate() | ||||
|  | ||||
|     def _set_core_clusters_from_core_names(self): | ||||
|         self.core_clusters = [] | ||||
|         clusters = [] | ||||
|         for cn in self.core_names: | ||||
|             if cn not in clusters: | ||||
|                 clusters.append(cn) | ||||
|             self.core_clusters.append(clusters.index(cn)) | ||||
|  | ||||
|     def _set_model_from_target(self, target): | ||||
|         if target.os == 'android': | ||||
|             self.model = target.getprop('ro.product.model') | ||||
|         elif target.is_rooted: | ||||
|             try: | ||||
|                 self.model = target.execute('dmidecode -s system-version', | ||||
|                                             as_root=True).strip() | ||||
|             except Exception:  # pylint: disable=broad-except | ||||
|                 pass  # this is best-effort | ||||
|  | ||||
|     def _validate(self): | ||||
|         if len(self.core_names) != len(self.core_clusters): | ||||
|             raise ValueError('core_names and core_clusters are of different lengths.') | ||||
|         if self.big_core and self.number_of_clusters != 2: | ||||
|             raise ValueError('attempting to set big_core on non-big.LITTLE device. ' | ||||
|                              '(number of clusters  is not 2)') | ||||
|         if self.big_core and self.big_core not in self.core_names: | ||||
|             message = 'Invalid big_core value "{}"; must be in [{}]' | ||||
|             raise ValueError(message.format(self.big_core, | ||||
|                                             ', '.join(set(self.core_names)))) | ||||
|         if self.big_core: | ||||
|             little_idx = self.core_clusters.index(min(self.core_clusters)) | ||||
|             self.little_core = self.core_names[little_idx] | ||||
|  | ||||
							
								
								
									
										280
									
								
								devlib/platform/arm.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								devlib/platform/arm.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| from __future__ import division | ||||
| import os | ||||
| import tempfile | ||||
| import csv | ||||
| import time | ||||
| import pexpect | ||||
|  | ||||
| from devlib.platform import Platform | ||||
| from devlib.instrument import Instrument, InstrumentChannel, MeasurementsCsv, CONTINUOUS | ||||
| from devlib.exception import TargetError, HostError | ||||
| from devlib.host import PACKAGE_BIN_DIRECTORY | ||||
| from devlib.utils.serial_port import open_serial_connection | ||||
|  | ||||
|  | ||||
| class VersatileExpressPlatform(Platform): | ||||
|  | ||||
|     def __init__(self, name,  # pylint: disable=too-many-locals | ||||
|  | ||||
|                  core_names=None, | ||||
|                  core_clusters=None, | ||||
|                  big_core=None, | ||||
|                  modules=None, | ||||
|  | ||||
|                  # serial settings | ||||
|                  serial_port='/dev/ttyS0', | ||||
|                  baudrate=115200, | ||||
|  | ||||
|                  # VExpress MicroSD mount point | ||||
|                  vemsd_mount=None, | ||||
|  | ||||
|                  # supported: dtr, reboottxt | ||||
|                  hard_reset_method=None, | ||||
|                  # supported: uefi, uefi-shell, u-boot, bootmon | ||||
|                  bootloader=None, | ||||
|                  # supported: vemsd | ||||
|                  flash_method='vemsd', | ||||
|  | ||||
|                  image=None, | ||||
|                  fdt=None, | ||||
|                  initrd=None, | ||||
|                  bootargs=None, | ||||
|  | ||||
|                  uefi_entry=None,  # only used if bootloader is "uefi" | ||||
|                  ready_timeout=60, | ||||
|                  ): | ||||
|         super(VersatileExpressPlatform, self).__init__(name, | ||||
|                                                        core_names, | ||||
|                                                        core_clusters, | ||||
|                                                        big_core, | ||||
|                                                        modules) | ||||
|         self.serial_port = serial_port | ||||
|         self.baudrate = baudrate | ||||
|         self.vemsd_mount = vemsd_mount | ||||
|         self.image = image | ||||
|         self.fdt = fdt | ||||
|         self.initrd = initrd | ||||
|         self.bootargs = bootargs | ||||
|         self.uefi_entry = uefi_entry | ||||
|         self.ready_timeout = ready_timeout | ||||
|         self.bootloader = None | ||||
|         self.hard_reset_method = None | ||||
|         self._set_bootloader(bootloader) | ||||
|         self._set_hard_reset_method(hard_reset_method) | ||||
|         self._set_flash_method(flash_method) | ||||
|  | ||||
|     def init_target_connection(self, target): | ||||
|         if target.os == 'android': | ||||
|             self._init_android_target(target) | ||||
|         else: | ||||
|             self._init_linux_target(target) | ||||
|  | ||||
|     def _init_android_target(self, target): | ||||
|         if target.connection_settings.get('device') is None: | ||||
|             addr = self._get_target_ip_address(target) | ||||
|             target.connection_settings['device'] = addr + ':5555' | ||||
|  | ||||
|     def _init_linux_target(self, target): | ||||
|         if target.connection_settings.get('host') is None: | ||||
|             addr = self._get_target_ip_address(target) | ||||
|             target.connection_settings['host'] = addr | ||||
|  | ||||
|     def _get_target_ip_address(self, target): | ||||
|         with open_serial_connection(port=self.serial_port, | ||||
|                                     baudrate=self.baudrate, | ||||
|                                     timeout=30, | ||||
|                                     init_dtr=0) as tty: | ||||
|             tty.sendline('') | ||||
|             self.logger.debug('Waiting for the Android shell prompt.') | ||||
|             tty.expect(target.shell_prompt) | ||||
|  | ||||
|             self.logger.debug('Waiting for IP address...') | ||||
|             wait_start_time = time.time() | ||||
|             while True: | ||||
|                 tty.sendline('ip addr list eth0') | ||||
|                 time.sleep(1) | ||||
|                 try: | ||||
|                     tty.expect(r'inet ([1-9]\d*.\d+.\d+.\d+)', timeout=10) | ||||
|                     return tty.match.group(1) | ||||
|                 except pexpect.TIMEOUT: | ||||
|                     pass  # We have our own timeout -- see below. | ||||
|                 if (time.time() - wait_start_time) > self.ready_timeout: | ||||
|                     raise TargetError('Could not acquire IP address.') | ||||
|  | ||||
|     def _set_hard_reset_method(self, hard_reset_method): | ||||
|         if hard_reset_method == 'dtr': | ||||
|             self.modules.append({'vexpress-dtr': {'port': self.serial_port, | ||||
|                                                   'baudrate': self.baudrate, | ||||
|                                                   }}) | ||||
|         elif hard_reset_method == 'reboottxt': | ||||
|             self.modules.append({'vexpress-reboottxt': {'port': self.serial_port, | ||||
|                                                         'baudrate': self.baudrate, | ||||
|                                                         'path': self.vemsd_mount, | ||||
|                                                         }}) | ||||
|         else: | ||||
|             ValueError('Invalid hard_reset_method: {}'.format(hard_reset_method)) | ||||
|  | ||||
|     def _set_bootloader(self, bootloader): | ||||
|         self.bootloader = bootloader | ||||
|         if self.bootloader == 'uefi': | ||||
|             self.modules.append({'vexpress-uefi': {'port': self.serial_port, | ||||
|                                                    'baudrate': self.baudrate, | ||||
|                                                    'image': self.image, | ||||
|                                                    'fdt': self.fdt, | ||||
|                                                    'initrd': self.initrd, | ||||
|                                                    'bootargs': self.bootargs, | ||||
|                                                    }}) | ||||
|         elif self.bootloader == 'uefi-shell': | ||||
|             self.modules.append({'vexpress-uefi-shell': {'port': self.serial_port, | ||||
|                                                          'baudrate': self.baudrate, | ||||
|                                                          'image': self.image, | ||||
|                                                          'bootargs': self.bootargs, | ||||
|                                                          }}) | ||||
|         elif self.bootloader == 'u-boot': | ||||
|             self.modules.append({'vexpress-u-boot': {'port': self.serial_port, | ||||
|                                                      'baudrate': self.baudrate, | ||||
|                                                      'env': {'bootargs': self.bootargs}, | ||||
|                                                      }}) | ||||
|         elif self.bootloader == 'bootmon': | ||||
|             self.modules.append({'vexpress-bootmon': {'port': self.serial_port, | ||||
|                                                       'baudrate': self.baudrate, | ||||
|                                                       'image': self.image, | ||||
|                                                       'fdt': self.fdt, | ||||
|                                                       'initrd': self.initrd, | ||||
|                                                       'bootargs': self.bootargs, | ||||
|                                                       }}) | ||||
|         else: | ||||
|             ValueError('Invalid hard_reset_method: {}'.format(bootloader)) | ||||
|  | ||||
|     def _set_flash_method(self, flash_method): | ||||
|         if flash_method == 'vemsd': | ||||
|             self.modules.append({'vexpress-vemsd': {'vemsd_mount': self.vemsd_mount}}) | ||||
|         else: | ||||
|             ValueError('Invalid flash_method: {}'.format(flash_method)) | ||||
|  | ||||
|  | ||||
| class Juno(VersatileExpressPlatform): | ||||
|  | ||||
|     def __init__(self, | ||||
|                  vemsd_mount='/media/JUNO', | ||||
|                  baudrate=115200, | ||||
|                  bootloader='u-boot', | ||||
|                  hard_reset_method='dtr', | ||||
|                  **kwargs | ||||
|                  ): | ||||
|         super(Juno, self).__init__('juno', | ||||
|                                    vemsd_mount=vemsd_mount, | ||||
|                                    baudrate=baudrate, | ||||
|                                    bootloader=bootloader, | ||||
|                                    hard_reset_method=hard_reset_method, | ||||
|                                    **kwargs) | ||||
|  | ||||
|  | ||||
| class TC2(VersatileExpressPlatform): | ||||
|  | ||||
|     def __init__(self, | ||||
|                  vemsd_mount='/media/VEMSD', | ||||
|                  baudrate=38400, | ||||
|                  bootloader='bootmon', | ||||
|                  hard_reset_method='reboottxt', | ||||
|                  **kwargs | ||||
|                  ): | ||||
|         super(TC2, self).__init__('tc2', | ||||
|                                   vemsd_mount=vemsd_mount, | ||||
|                                   baudrate=baudrate, | ||||
|                                   bootloader=bootloader, | ||||
|                                   hard_reset_method=hard_reset_method, | ||||
|                                   **kwargs) | ||||
|  | ||||
|  | ||||
| class JunoEnergyInstrument(Instrument): | ||||
|  | ||||
|     binname = 'readenergy' | ||||
|     mode = CONTINUOUS | ||||
|  | ||||
|     _channels = [ | ||||
|         InstrumentChannel('sys_curr', 'sys', 'current'), | ||||
|         InstrumentChannel('a57_curr', 'a57', 'current'), | ||||
|         InstrumentChannel('a53_curr', 'a53', 'current'), | ||||
|         InstrumentChannel('gpu_curr', 'gpu', 'current'), | ||||
|         InstrumentChannel('sys_volt', 'sys', 'voltage'), | ||||
|         InstrumentChannel('a57_volt', 'a57', 'voltage'), | ||||
|         InstrumentChannel('a53_volt', 'a53', 'voltage'), | ||||
|         InstrumentChannel('gpu_volt', 'gpu', 'voltage'), | ||||
|         InstrumentChannel('sys_pow', 'sys', 'power'), | ||||
|         InstrumentChannel('a57_pow', 'a57', 'power'), | ||||
|         InstrumentChannel('a53_pow', 'a53', 'power'), | ||||
|         InstrumentChannel('gpu_pow', 'gpu', 'power'), | ||||
|         InstrumentChannel('sys_cenr', 'sys', 'energy'), | ||||
|         InstrumentChannel('a57_cenr', 'a57', 'energy'), | ||||
|         InstrumentChannel('a53_cenr', 'a53', 'energy'), | ||||
|         InstrumentChannel('gpu_cenr', 'gpu', 'energy'), | ||||
|     ] | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         super(JunoEnergyInstrument, self).__init__(target) | ||||
|         self.on_target_file = None | ||||
|         self.command = None | ||||
|         self.binary = self.target.bin(self.binname) | ||||
|         for chan in self._channels: | ||||
|             self.channels[chan.name] = chan | ||||
|         self.on_target_file = self.target.tempfile('energy', '.csv') | ||||
|         self.command = '{} -o {}'.format(self.binary, self.on_target_file) | ||||
|  | ||||
|     def setup(self): | ||||
|         self.binary = self.target.install(os.path.join(PACKAGE_BIN_DIRECTORY, | ||||
|                                                        self.target.abi, self.binname)) | ||||
|  | ||||
|     def reset(self, sites=None, kinds=None): | ||||
|         super(JunoEnergyInstrument, self).reset(sites, kinds) | ||||
|         self.target.killall(self.binname, as_root=True) | ||||
|  | ||||
|     def start(self): | ||||
|         self.target.kick_off(self.command, as_root=True) | ||||
|  | ||||
|     def stop(self): | ||||
|         self.target.killall(self.binname, signal='TERM', as_root=True) | ||||
|  | ||||
|     def get_data(self, output_file): | ||||
|         temp_file = tempfile.mktemp() | ||||
|         self.target.pull(self.on_target_file, temp_file) | ||||
|         self.target.remove(self.on_target_file) | ||||
|  | ||||
|         with open(temp_file, 'rb') as fh: | ||||
|             reader = csv.reader(fh) | ||||
|             headings = reader.next() | ||||
|  | ||||
|             # Figure out which columns from the collected csv we actually want | ||||
|             select_columns = [] | ||||
|             for chan in self.active_channels: | ||||
|                 try: | ||||
|                     select_columns.append(headings.index(chan.name)) | ||||
|                 except ValueError: | ||||
|                     raise HostError('Channel "{}" is not in {}'.format(chan.name, temp_file)) | ||||
|  | ||||
|             with open(output_file, 'wb') as wfh: | ||||
|                 write_headings = ['{}_{}'.format(c.site, c.kind) | ||||
|                                   for c in self.active_channels] | ||||
|                 writer = csv.writer(wfh) | ||||
|                 writer.writerow(write_headings) | ||||
|                 for row in reader: | ||||
|                     write_row = [row[c] for c in select_columns] | ||||
|                     writer.writerow(write_row) | ||||
|  | ||||
|         return MeasurementsCsv(output_file, self.active_channels) | ||||
|  | ||||
|  | ||||
							
								
								
									
										980
									
								
								devlib/target.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										980
									
								
								devlib/target.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,980 @@ | ||||
| import os | ||||
| import re | ||||
| import time | ||||
| import logging | ||||
| import posixpath | ||||
| import subprocess | ||||
| import tempfile | ||||
| import threading | ||||
| from collections import namedtuple | ||||
|  | ||||
| from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY | ||||
| from devlib.module import get_module | ||||
| from devlib.platform import Platform | ||||
| from devlib.exception import TargetError, TargetNotRespondingError, TimeoutError | ||||
| from devlib.utils.ssh import SshConnection | ||||
| from devlib.utils.android import AdbConnection, AndroidProperties, adb_command, adb_disconnect | ||||
| from devlib.utils.misc import memoized, isiterable, convert_new_lines, merge_lists | ||||
| from devlib.utils.misc import ABI_MAP, get_cpu_name, ranges_to_list, escape_double_quotes | ||||
| from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_string | ||||
|  | ||||
|  | ||||
| FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (\S+) type (\S+) \((\S+)\)') | ||||
| ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn)=([0-9]+|true|false)', | ||||
|                                         re.IGNORECASE) | ||||
| ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)' | ||||
|                                              r'\s+(?P<width>\d+)x(?P<height>\d+)') | ||||
| DEFAULT_SHELL_PROMPT = re.compile(r'^.*(shell|root)@.*:/\S* [#$] ', | ||||
|                                   re.MULTILINE) | ||||
|  | ||||
|  | ||||
| class Target(object): | ||||
|  | ||||
|     conn_cls = None | ||||
|     path = None | ||||
|     os = None | ||||
|  | ||||
|     default_modules = [ | ||||
|         'hotplug', | ||||
|         'cpufreq', | ||||
|         'cpuidle', | ||||
|         'cgroups', | ||||
|         'hwmon', | ||||
|     ] | ||||
|  | ||||
|     @property | ||||
|     def core_names(self): | ||||
|         return self.platform.core_names | ||||
|  | ||||
|     @property | ||||
|     def core_clusters(self): | ||||
|         return self.platform.core_clusters | ||||
|  | ||||
|     @property | ||||
|     def big_core(self): | ||||
|         return self.platform.big_core | ||||
|  | ||||
|     @property | ||||
|     def little_core(self): | ||||
|         return self.platform.little_core | ||||
|  | ||||
|     @property | ||||
|     def is_connected(self): | ||||
|         return self.conn is not None | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def connected_as_root(self): | ||||
|         result = self.execute('id') | ||||
|         return 'uid=0(' in result | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def is_rooted(self): | ||||
|         if self.connected_as_root: | ||||
|             return True | ||||
|         try: | ||||
|             self.execute('ls /', timeout=2, as_root=True) | ||||
|             return True | ||||
|         except (TargetError, TimeoutError): | ||||
|             return False | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def kernel_version(self): | ||||
|         return KernelVersion(self.execute('uname -r -v').strip()) | ||||
|  | ||||
|     @property | ||||
|     def os_version(self):  # pylint: disable=no-self-use | ||||
|         return {} | ||||
|  | ||||
|     @property | ||||
|     def abi(self):  # pylint: disable=no-self-use | ||||
|         return None | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def cpuinfo(self): | ||||
|         return Cpuinfo(self.execute('cat /proc/cpuinfo')) | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def number_of_cpus(self): | ||||
|         num_cpus = 0 | ||||
|         corere = re.compile(r'^\s*cpu\d+\s*$') | ||||
|         output = self.execute('ls /sys/devices/system/cpu') | ||||
|         for entry in output.split(): | ||||
|             if corere.match(entry): | ||||
|                 num_cpus += 1 | ||||
|         return num_cpus | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def config(self): | ||||
|         try: | ||||
|             return KernelConfig(self.execute('zcat /proc/config.gz')) | ||||
|         except TargetError: | ||||
|             for path in ['/boot/config', '/boot/config-$(uname -r)']: | ||||
|                 try: | ||||
|                     return KernelConfig(self.execute('cat {}'.format(path))) | ||||
|                 except TargetError: | ||||
|                     pass | ||||
|         return KernelConfig('') | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def user(self): | ||||
|         return self.getenv('USER') | ||||
|  | ||||
|     @property | ||||
|     def conn(self): | ||||
|         if self._connections: | ||||
|             tid = id(threading.current_thread()) | ||||
|             if tid not in self._connections: | ||||
|                 self._connections[tid] = self.get_connection() | ||||
|             return self._connections[tid] | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def __init__(self, | ||||
|                  connection_settings=None, | ||||
|                  platform=None, | ||||
|                  working_directory=None, | ||||
|                  executables_directory=None, | ||||
|                  connect=True, | ||||
|                  modules=None, | ||||
|                  load_default_modules=True, | ||||
|                  shell_prompt=DEFAULT_SHELL_PROMPT, | ||||
|                  ): | ||||
|         self.connection_settings = connection_settings or {} | ||||
|         self.platform = platform or Platform() | ||||
|         self.working_directory = working_directory | ||||
|         self.executables_directory = executables_directory | ||||
|         self.modules = modules or [] | ||||
|         self.load_default_modules = load_default_modules | ||||
|         self.shell_prompt = shell_prompt | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|         self._installed_binaries = {} | ||||
|         self._installed_modules = {} | ||||
|         self._cache = {} | ||||
|         self._connections = {} | ||||
|         self.busybox = None | ||||
|  | ||||
|         if load_default_modules: | ||||
|             module_lists = [self.default_modules] | ||||
|         else: | ||||
|             module_lists = [] | ||||
|         module_lists += [self.modules, self.platform.modules] | ||||
|         self.modules = merge_lists(*module_lists, duplicates='first') | ||||
|         self._update_modules('early') | ||||
|         if connect: | ||||
|             self.connect() | ||||
|  | ||||
|     # connection and initialization | ||||
|  | ||||
|     def connect(self, timeout=None): | ||||
|         self.platform.init_target_connection(self) | ||||
|         tid = id(threading.current_thread()) | ||||
|         self._connections[tid] = self.get_connection(timeout=timeout) | ||||
|         self.busybox = self.get_installed('busybox') | ||||
|         self._update_modules('connected') | ||||
|         self.platform.update_from_target(self) | ||||
|         if self.platform.big_core and self.load_default_modules: | ||||
|             self._install_module(get_module('bl')) | ||||
|  | ||||
|     def disconnect(self): | ||||
|         for conn in self._connections.itervalues(): | ||||
|             conn.close() | ||||
|         self._connections = {} | ||||
|  | ||||
|     def get_connection(self, timeout=None): | ||||
|         if self.conn_cls is None: | ||||
|             raise NotImplementedError('conn_cls must be set by the subclass of Target') | ||||
|         return self.conn_cls(timeout=timeout, **self.connection_settings)  # pylint: disable=not-callable | ||||
|  | ||||
|     def setup(self, executables=None): | ||||
|         self.execute('mkdir -p {}'.format(self.working_directory)) | ||||
|         self.execute('mkdir -p {}'.format(self.executables_directory)) | ||||
|         self.busybox = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, self.abi, 'busybox')) | ||||
|         for host_exe in (executables or []):  # pylint: disable=superfluous-parens | ||||
|             self.install(host_exe) | ||||
|  | ||||
|     def reboot(self, hard=False, connect=True, timeout=180): | ||||
|         if hard: | ||||
|             if not self.has('hard_reset'): | ||||
|                 raise TargetError('Hard reset not supported for this target.') | ||||
|             self.hard_reset()  # pylint: disable=no-member | ||||
|         else: | ||||
|             if not self.is_connected: | ||||
|                 message = 'Cannot reboot target becuase it is disconnected. ' +\ | ||||
|                           'Either connect() first, or specify hard=True ' +\ | ||||
|                           '(in which case, a hard_reset module must be installed)' | ||||
|                 raise TargetError(message) | ||||
|             self.reset() | ||||
|         if self.has('boot'): | ||||
|             self.boot()  # pylint: disable=no-member | ||||
|         if connect: | ||||
|             self.connect(timeout=timeout) | ||||
|  | ||||
|     # file transfer | ||||
|  | ||||
|     def push(self, source, dest, timeout=None): | ||||
|         return self.conn.push(source, dest, timeout=timeout) | ||||
|  | ||||
|     def pull(self, source, dest, timeout=None): | ||||
|         return self.conn.pull(source, dest, timeout=timeout) | ||||
|  | ||||
|     # execution | ||||
|  | ||||
|     def execute(self, command, timeout=None, check_exit_code=True, as_root=False): | ||||
|         return self.conn.execute(command, timeout, check_exit_code, as_root) | ||||
|  | ||||
|     def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False): | ||||
|         return self.conn.background(command, stdout, stderr, as_root) | ||||
|  | ||||
|     def invoke(self, binary, args=None, in_directory=None, on_cpus=None, | ||||
|                as_root=False, timeout=30): | ||||
|         """ | ||||
|         Executes the specified binary under the specified conditions. | ||||
|  | ||||
|         :binary: binary to execute. Must be present and executable on the device. | ||||
|         :args: arguments to be passed to the binary. The can be either a list or | ||||
|                a string. | ||||
|         :in_directory:  execute the binary in the  specified directory. This must | ||||
|                         be an absolute path. | ||||
|         :on_cpus:  taskset the binary to these CPUs. This may be a single ``int`` (in which | ||||
|                    case, it will be interpreted as the mask), a list of ``ints``, in which | ||||
|                    case this will be interpreted as the list of cpus, or string, which | ||||
|                    will be interpreted as a comma-separated list of cpu ranges, e.g. | ||||
|                    ``"0,4-7"``. | ||||
|         :as_root: Specify whether the command should be run as root | ||||
|         :timeout: If the invocation does not terminate within this number of seconds, | ||||
|                   a ``TimeoutError`` exception will be raised. Set to ``None`` if the | ||||
|                   invocation should not timeout. | ||||
|  | ||||
|         """ | ||||
|         command = binary | ||||
|         if args: | ||||
|             if isiterable(args): | ||||
|                 args = ' '.join(args) | ||||
|             command = '{} {}'.format(command, args) | ||||
|         if on_cpus: | ||||
|             on_cpus = bitmask(on_cpus) | ||||
|             command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command) | ||||
|         if in_directory: | ||||
|             command = 'cd {} && {}'.format(in_directory, command) | ||||
|         return self.execute(command, as_root=as_root, timeout=timeout) | ||||
|  | ||||
|     def kick_off(self, command, as_root=False): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     # sysfs interaction | ||||
|  | ||||
|     def read_value(self, path, kind=None): | ||||
|         output = self.execute('cat \'{}\''.format(path), as_root=self.is_rooted).strip()  # pylint: disable=E1103 | ||||
|         if kind: | ||||
|             return kind(output) | ||||
|         else: | ||||
|             return output | ||||
|  | ||||
|     def read_int(self, path): | ||||
|         return self.read_value(path, kind=integer) | ||||
|  | ||||
|     def read_bool(self, path): | ||||
|         return self.read_value(path, kind=boolean) | ||||
|  | ||||
|     def write_value(self, path, value, verify=True): | ||||
|         value = str(value) | ||||
|         self.execute('echo {} > \'{}\''.format(value, path), check_exit_code=False, as_root=True) | ||||
|         if verify: | ||||
|             output = self.read_value(path) | ||||
|             if not output == value: | ||||
|                 message = 'Could not set the value of {} to "{}" (read "{}")'.format(path, value, output) | ||||
|                 raise TargetError(message) | ||||
|  | ||||
|     def reset(self): | ||||
|         try: | ||||
|             self.execute('reboot', as_root=self.is_rooted, timeout=2) | ||||
|         except (TargetError, TimeoutError, subprocess.CalledProcessError): | ||||
|             # on some targets "reboot" doesn't return gracefully | ||||
|             pass | ||||
|  | ||||
|     def check_responsive(self): | ||||
|         try: | ||||
|             self.conn.execute('ls /', timeout=5) | ||||
|         except (TimeoutError, subprocess.CalledProcessError): | ||||
|             raise TargetNotRespondingError(self.conn.name) | ||||
|  | ||||
|     # process management | ||||
|  | ||||
|     def kill(self, pid, signal=None, as_root=False): | ||||
|         signal_string = '-s {}'.format(signal) if signal else '' | ||||
|         self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root) | ||||
|  | ||||
|     def killall(self, process_name, signal=None, as_root=False): | ||||
|         for pid in self.get_pids_of(process_name): | ||||
|             self.kill(pid, signal=signal, as_root=as_root) | ||||
|  | ||||
|     def get_pids_of(self, process_name): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def ps(self, **kwargs): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     # files | ||||
|  | ||||
|     def file_exists(self, filepath): | ||||
|         command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi' | ||||
|         return boolean(self.execute(command.format(filepath)).strip()) | ||||
|  | ||||
|     def list_file_systems(self): | ||||
|         output = self.execute('mount') | ||||
|         fstab = [] | ||||
|         for line in output.split('\n'): | ||||
|             line = line.strip() | ||||
|             if not line: | ||||
|                 continue | ||||
|             match = FSTAB_ENTRY_REGEX.search(line) | ||||
|             if match: | ||||
|                 fstab.append(FstabEntry(match.group(1), match.group(2), | ||||
|                                         match.group(3), match.group(4), | ||||
|                                         None, None)) | ||||
|             else:  # assume pre-M Android | ||||
|                 fstab.append(FstabEntry(*line.split())) | ||||
|         return fstab | ||||
|  | ||||
|     def list_directory(self, path, as_root=False): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def get_workpath(self, name): | ||||
|         return self.path.join(self.working_directory, name) | ||||
|  | ||||
|     def tempfile(self, prefix='', suffix=''): | ||||
|         names = tempfile._get_candidate_names()  # pylint: disable=W0212 | ||||
|         for _ in xrange(tempfile.TMP_MAX): | ||||
|             name = names.next() | ||||
|             path = self.get_workpath(prefix + name + suffix) | ||||
|             if not self.file_exists(path): | ||||
|                 return path | ||||
|         raise IOError('No usable temporary filename found') | ||||
|  | ||||
|     def remove(self, path, as_root=False): | ||||
|         self.execute('rm -rf {}'.format(path), as_root=as_root) | ||||
|  | ||||
|     # misc | ||||
|     def core_cpus(self, core): | ||||
|         return [i for i, c in enumerate(self.core_names) if c == core] | ||||
|  | ||||
|     def list_online_cpus(self, core=None): | ||||
|         path = self.path.join('/sys/devices/system/cpu/online') | ||||
|         output = self.read_value(path) | ||||
|         all_online = ranges_to_list(output) | ||||
|         if core: | ||||
|             cpus = self.core_cpus(core) | ||||
|             if not cpus: | ||||
|                 raise ValueError(core) | ||||
|             return [o for o in all_online if o in cpus] | ||||
|         else: | ||||
|             return all_online | ||||
|  | ||||
|     def list_offline_cpus(self): | ||||
|         online = self.list_online_cpus() | ||||
|         return [c for c in xrange(self.number_of_cpus) | ||||
|                 if c not in online] | ||||
|  | ||||
|     def getenv(self, variable): | ||||
|         return self.execute('echo ${}'.format(variable)).rstrip('\r\n') | ||||
|  | ||||
|     def capture_screen(self, filepath): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def install(self, filepath, timeout=None, with_name=None): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def uninstall(self, name): | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def get_installed(self, name): | ||||
|         for path in self.getenv('PATH').split(self.path.pathsep): | ||||
|             try: | ||||
|                 if  name in self.list_directory(path): | ||||
|                     return self.path.join(path, name) | ||||
|             except TargetError: | ||||
|                 pass  # directory does not exist or no executable premssions | ||||
|         if name in self.list_directory(self.executables_directory): | ||||
|             return self.path.join(self.executables_directory, name) | ||||
|  | ||||
|     which = get_installed | ||||
|  | ||||
|     def is_installed(self, name): | ||||
|         return bool(self.get_installed(name)) | ||||
|  | ||||
|     def bin(self, name): | ||||
|         return self._installed_binaries.get(name, name) | ||||
|  | ||||
|     def has(self, modname): | ||||
|         return hasattr(self, identifier(modname)) | ||||
|  | ||||
|     def _update_modules(self, stage): | ||||
|         for mod in self.modules: | ||||
|             if isinstance(mod, dict): | ||||
|                 mod, params = mod.items()[0] | ||||
|             else: | ||||
|                 params = {} | ||||
|             mod = get_module(mod) | ||||
|             if not mod.stage == stage: | ||||
|                 continue | ||||
|             if mod.probe(self): | ||||
|                 self._install_module(mod, **params) | ||||
|             else: | ||||
|                 self.logger.debug('Module {} is not supported by the target'.format(mod.name)) | ||||
|  | ||||
|     def _install_module(self, mod, **params): | ||||
|         if mod.name not in self._installed_modules: | ||||
|             self.logger.debug('Installing module {}'.format(mod.name)) | ||||
|             mod.install(self, **params) | ||||
|             self._installed_modules[mod.name] = mod | ||||
|         else: | ||||
|             self.logger.debug('Module {} is already installed.'.format(mod.name)) | ||||
|  | ||||
|  | ||||
| class LinuxTarget(Target): | ||||
|  | ||||
|     conn_cls = SshConnection | ||||
|     path = posixpath | ||||
|     os = 'linux' | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def abi(self): | ||||
|         value = self.execute('uname -m').strip() | ||||
|         for abi, architectures in ABI_MAP.iteritems(): | ||||
|             if value in architectures: | ||||
|                 result = abi | ||||
|                 break | ||||
|         else: | ||||
|             result = value | ||||
|         return result | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def os_version(self): | ||||
|         os_version = {} | ||||
|         try: | ||||
|             command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null' | ||||
|             version_files = self.execute(command, check_exit_code=False).strip().split() | ||||
|             for vf in version_files: | ||||
|                 name = self.path.basename(vf) | ||||
|                 output = self.read_value(vf) | ||||
|                 os_version[name] = output.strip().replace('\n', ' ') | ||||
|         except TargetError: | ||||
|             raise | ||||
|         return os_version | ||||
|  | ||||
|     def connect(self, timeout=None): | ||||
|         super(LinuxTarget, self).connect(timeout=timeout) | ||||
|         if self.working_directory is None: | ||||
|             if self.connected_as_root: | ||||
|                 self.working_directory = '/root/devlib-target' | ||||
|             else: | ||||
|                 self.working_directory = '/home/{}/devlib-target'.format(self.user) | ||||
|         if self.executables_directory is None: | ||||
|             self.executables_directory = self.path.join(self.working_directory, 'bin') | ||||
|  | ||||
|     def kick_off(self, command, as_root=False): | ||||
|         command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command)) | ||||
|         return self.conn.execute(command, as_root=as_root) | ||||
|  | ||||
|     def get_pids_of(self, process_name): | ||||
|         """Returns a list of PIDs of all processes with the specified name.""" | ||||
|         # result should be a column of PIDs with the first row as "PID" header | ||||
|         result = self.execute('ps -C {} -o pid'.format(process_name),  # NOQA | ||||
|                               check_exit_code=False).strip().split() | ||||
|         if len(result) >= 2:  # at least one row besides the header | ||||
|             return map(int, result[1:]) | ||||
|         else: | ||||
|             return [] | ||||
|  | ||||
|     def ps(self, **kwargs): | ||||
|         command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname' | ||||
|         lines = iter(convert_new_lines(self.execute(command)).split('\n')) | ||||
|         lines.next()  # header | ||||
|  | ||||
|         result = [] | ||||
|         for line in lines: | ||||
|             parts = re.split(r'\s+', line, maxsplit=8) | ||||
|             if parts and parts != ['']: | ||||
|                 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:]))) | ||||
|  | ||||
|         if not kwargs: | ||||
|             return result | ||||
|         else: | ||||
|             filtered_result = [] | ||||
|             for entry in result: | ||||
|                 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()): | ||||
|                     filtered_result.append(entry) | ||||
|             return filtered_result | ||||
|  | ||||
|     def list_directory(self, path, as_root=False): | ||||
|         contents = self.execute('ls -1 {}'.format(path), as_root=as_root) | ||||
|         return [x.strip() for x in contents.split('\n') if x.strip()] | ||||
|  | ||||
|     def install(self, filepath, timeout=None, with_name=None):  # pylint: disable=W0221 | ||||
|         destpath = self.path.join(self.executables_directory, | ||||
|                                   with_name and with_name or self.path.basename(filepath)) | ||||
|         self.push(filepath, destpath) | ||||
|         self.execute('chmod a+x {}'.format(destpath), timeout=timeout) | ||||
|         self._installed_binaries[self.path.basename(destpath)] = destpath | ||||
|         return destpath | ||||
|  | ||||
|     def uninstall(self, name): | ||||
|         path = self.path.join(self.executables_directory, name) | ||||
|         self.remove(path) | ||||
|  | ||||
|     def capture_screen(self, filepath): | ||||
|         if not self.is_installed('scrot'): | ||||
|             self.logger.debug('Could not take screenshot as scrot is not installed.') | ||||
|             return | ||||
|         try: | ||||
|  | ||||
|             tmpfile = self.tempfile() | ||||
|             self.execute('DISPLAY=:0.0 scrot {}'.format(tmpfile)) | ||||
|             self.pull(tmpfile, filepath) | ||||
|             self.remove(tmpfile) | ||||
|         except TargetError as e: | ||||
|             if "Can't open X dispay." not in e.message: | ||||
|                 raise e | ||||
|             message = e.message.split('OUTPUT:', 1)[1].strip()  # pylint: disable=no-member | ||||
|             self.logger.debug('Could not take screenshot: {}'.format(message)) | ||||
|  | ||||
|  | ||||
| class AndroidTarget(Target): | ||||
|  | ||||
|     conn_cls = AdbConnection | ||||
|     path = posixpath | ||||
|     os = 'android' | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def abi(self): | ||||
|         return self.getprop()['ro.product.cpu.abi'].split('-')[0] | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def os_version(self): | ||||
|         os_version = {} | ||||
|         for k, v in self.getprop().iteritems(): | ||||
|             if k.startswith('ro.build.version'): | ||||
|                 part = k.split('.')[-1] | ||||
|                 os_version[part] = v | ||||
|         return os_version | ||||
|  | ||||
|     @property | ||||
|     def adb_name(self): | ||||
|         return self.conn.device | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def screen_resolution(self): | ||||
|         output = self.execute('dumpsys window') | ||||
|         match = ANDROID_SCREEN_RESOLUTION_REGEX.search(output) | ||||
|         if match: | ||||
|             return (int(match.group('width')), | ||||
|                     int(match.group('height'))) | ||||
|         else: | ||||
|             return (0, 0) | ||||
|  | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(AndroidTarget, self).__init__(*args, **kwargs) | ||||
|         self._file_transfer_cache = None | ||||
|  | ||||
|     def reset(self, fastboot=False):  # pylint: disable=arguments-differ | ||||
|         try: | ||||
|             self.execute('reboot {}'.format(fastboot and 'fastboot' or ''), | ||||
|                          as_root=self.is_rooted, timeout=2) | ||||
|         except (TargetError, TimeoutError, subprocess.CalledProcessError): | ||||
|             # on some targets "reboot" doesn't return gracefully | ||||
|             pass | ||||
|  | ||||
|     def connect(self, timeout=10, check_boot_completed=True):  # pylint: disable=arguments-differ | ||||
|         start = time.time() | ||||
|         device = self.connection_settings.get('device') | ||||
|         if device and ':' in device: | ||||
|             # ADB does not automatically remove a network device from it's | ||||
|             # devices list when the connection is broken by the remote, so the | ||||
|             # adb connection may have gone "stale", resulting in adb blocking | ||||
|             # indefinitely when making calls to the device. To avoid this, | ||||
|             # always disconnect first. | ||||
|             adb_disconnect(device) | ||||
|         super(AndroidTarget, self).connect(timeout=timeout) | ||||
|         if self.working_directory is None: | ||||
|             self.working_directory = '/data/local/tmp/devlib-target' | ||||
|         self._file_transfer_cache = self.path.join(self.working_directory, '.file-cache') | ||||
|         if self.executables_directory is None: | ||||
|             self.executables_directory = self.path.join(self.working_directory, 'bin') | ||||
|  | ||||
|         if check_boot_completed: | ||||
|             boot_completed = boolean(self.getprop('sys.boot_completed')) | ||||
|             while not boot_completed and timeout >= time.time() - start: | ||||
|                 time.sleep(5) | ||||
|                 boot_completed = boolean(self.getprop('sys.boot_completed')) | ||||
|             if not boot_completed: | ||||
|                 raise TargetError('Connected but Android did not fully boot.') | ||||
|  | ||||
|     def setup(self, executables=None): | ||||
|         super(AndroidTarget, self).setup(executables) | ||||
|         self.execute('mkdir -p {}'.format(self._file_transfer_cache)) | ||||
|  | ||||
|     def kick_off(self, command, as_root=False): | ||||
|         """ | ||||
|         Like execute but closes adb session and returns immediately, leaving the command running on the | ||||
|         device (this is different from execute(background=True) which keeps adb connection open and returns | ||||
|         a subprocess object). | ||||
|  | ||||
|         .. note:: This relies on busybox's nohup applet and so won't work on unrooted devices. | ||||
|  | ||||
|         """ | ||||
|         if not self.is_rooted: | ||||
|             raise TargetError('kick_off uses busybox\'s nohup applet and so can only be run a rooted device.') | ||||
|         try: | ||||
|             command = 'cd {} && {} nohup {}'.format(self.working_directory, self.bin('busybox'), command) | ||||
|             output = self.execute(command, timeout=1, as_root=as_root) | ||||
|         except TimeoutError: | ||||
|             pass | ||||
|         else: | ||||
|             raise ValueError('Background command exited before timeout; got "{}"'.format(output)) | ||||
|  | ||||
|     def list_directory(self, path, as_root=False): | ||||
|         contents = self.execute('ls {}'.format(path), as_root=as_root) | ||||
|         return [x.strip() for x in contents.split('\n') if x.strip()] | ||||
|  | ||||
|     def install(self, filepath, timeout=None, with_name=None):  # pylint: disable=W0221 | ||||
|         ext = os.path.splitext(filepath)[1].lower() | ||||
|         if ext == '.apk': | ||||
|             return self.install_apk(filepath, timeout) | ||||
|         else: | ||||
|             return self.install_executable(filepath, with_name) | ||||
|  | ||||
|     def uninstall(self, name): | ||||
|         if self.package_is_installed(name): | ||||
|             self.uninstall_package(name) | ||||
|         else: | ||||
|             self.uninstall_executable(name) | ||||
|  | ||||
|     def get_pids_of(self, process_name): | ||||
|         result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip() | ||||
|         if result and 'not found' not in result: | ||||
|             return [int(x.split()[1]) for x in result.split('\n')[1:]] | ||||
|         else: | ||||
|             return [] | ||||
|  | ||||
|     def ps(self, **kwargs): | ||||
|         lines = iter(convert_new_lines(self.execute('ps')).split('\n')) | ||||
|         lines.next()  # header | ||||
|         result = [] | ||||
|         for line in lines: | ||||
|             parts = line.split() | ||||
|             if parts: | ||||
|                 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:]))) | ||||
|         if not kwargs: | ||||
|             return result | ||||
|         else: | ||||
|             filtered_result = [] | ||||
|             for entry in result: | ||||
|                 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()): | ||||
|                     filtered_result.append(entry) | ||||
|             return filtered_result | ||||
|  | ||||
|     def capture_screen(self, filepath): | ||||
|         on_device_file = self.path.join(self.working_directory, 'screen_capture.png') | ||||
|         self.execute('screencap -p  {}'.format(on_device_file)) | ||||
|         self.pull(on_device_file, filepath) | ||||
|         self.remove(on_device_file) | ||||
|  | ||||
|     def push(self, source, dest, as_root=False, timeout=None):  # pylint: disable=arguments-differ | ||||
|         if not as_root: | ||||
|             self.conn.push(source, dest, timeout=timeout) | ||||
|         else: | ||||
|             device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep)) | ||||
|             self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile))) | ||||
|             self.conn.push(source, device_tempfile, timeout=timeout) | ||||
|             self.execute('cp {} {}'.format(device_tempfile, dest), as_root=True) | ||||
|  | ||||
|     def pull(self, source, dest, as_root=False, timeout=None):  # pylint: disable=arguments-differ | ||||
|         if not as_root: | ||||
|             self.conn.pull(source, dest, timeout=timeout) | ||||
|         else: | ||||
|             device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep)) | ||||
|             self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile))) | ||||
|             self.execute('cp {} {}'.format(source, device_tempfile), as_root=True) | ||||
|             self.conn.pull(device_tempfile, dest, timeout=timeout) | ||||
|  | ||||
|     # Android-specific | ||||
|  | ||||
|     def swipe_to_unlock(self): | ||||
|         width, height = self.screen_resolution | ||||
|         swipe_heigh = height * 2 // 3 | ||||
|         start = 100 | ||||
|         stop = width - start | ||||
|         command = 'input swipe {} {} {} {}' | ||||
|         self.execute(command.format(start, swipe_heigh, stop, swipe_heigh)) | ||||
|  | ||||
|     def getprop(self, prop=None): | ||||
|         props = AndroidProperties(self.execute('getprop')) | ||||
|         if prop: | ||||
|             return props[prop] | ||||
|         return props | ||||
|  | ||||
|     def is_installed(self, name): | ||||
|         return super(AndroidTarget, self).is_installed(name) or self.package_is_installed(name) | ||||
|  | ||||
|     def package_is_installed(self, package_name): | ||||
|         return package_name in self.list_packages() | ||||
|  | ||||
|     def list_packages(self): | ||||
|         output = self.execute('pm list packages') | ||||
|         output = output.replace('package:', '') | ||||
|         return output.split() | ||||
|  | ||||
|     def get_package_version(self, package): | ||||
|         output = self.execute('dumpsys package {}'.format(package)) | ||||
|         for line in convert_new_lines(output).split('\n'): | ||||
|             if 'versionName' in line: | ||||
|                 return line.split('=', 1)[1] | ||||
|         return None | ||||
|  | ||||
|     def install_apk(self, filepath, timeout=None):  # pylint: disable=W0221 | ||||
|         ext = os.path.splitext(filepath)[1].lower() | ||||
|         if ext == '.apk': | ||||
|             return adb_command(self.adb_name, "install {}".format(filepath), timeout=timeout) | ||||
|         else: | ||||
|             raise TargetError('Can\'t install {}: unsupported format.'.format(filepath)) | ||||
|  | ||||
|     def install_executable(self, filepath, with_name=None): | ||||
|         self._ensure_executables_directory_is_writable() | ||||
|         executable_name = with_name or os.path.basename(filepath) | ||||
|         on_device_file = self.path.join(self.working_directory, executable_name) | ||||
|         on_device_executable = self.path.join(self.executables_directory, executable_name) | ||||
|         self.push(filepath, on_device_file) | ||||
|         if on_device_file != on_device_executable: | ||||
|             self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.is_rooted) | ||||
|             self.remove(on_device_file, as_root=self.is_rooted) | ||||
|         self.execute('chmod 0777 {}'.format(on_device_executable), as_root=self.is_rooted) | ||||
|         self._installed_binaries[executable_name] = on_device_executable | ||||
|         return on_device_executable | ||||
|  | ||||
|     def uninstall_package(self, package): | ||||
|         adb_command(self.adb_name, "uninstall {}".format(package), timeout=30) | ||||
|  | ||||
|     def uninstall_executable(self, executable_name): | ||||
|         on_device_executable = self.path.join(self.executables_directory, executable_name) | ||||
|         self._ensure_executables_directory_is_writable() | ||||
|         self.remove(on_device_executable, as_root=self.is_rooted) | ||||
|  | ||||
|     def dump_logcat(self, filepath, filter=None, append=False, timeout=30):  # pylint: disable=redefined-builtin | ||||
|         op = '>>' if append == True else '>' | ||||
|         filtstr = ' -s {}'.format(filter) if filter else '' | ||||
|         command = 'logcat -d{} {} {}'.format(filtstr, op, filepath) | ||||
|         adb_command(self.adb_name, command, timeout=timeout) | ||||
|  | ||||
|     def clear_logcat(self): | ||||
|         adb_command(self.adb_name, 'logcat -c', timeout=30) | ||||
|  | ||||
|     def is_screen_on(self): | ||||
|         output = self.execute('dumpsys power') | ||||
|         match = ANDROID_SCREEN_STATE_REGEX.search(output) | ||||
|         if match: | ||||
|             return boolean(match.group(1)) | ||||
|         else: | ||||
|             raise TargetError('Could not establish screen state.') | ||||
|  | ||||
|     def ensure_screen_is_on(self): | ||||
|         if not self.is_screen_on(): | ||||
|             self.execute('input keyevent 26') | ||||
|  | ||||
|     def _ensure_executables_directory_is_writable(self): | ||||
|         matched = [] | ||||
|         for entry in self.list_file_systems(): | ||||
|             if self.executables_directory.rstrip('/').startswith(entry.mount_point): | ||||
|                 matched.append(entry) | ||||
|         if matched: | ||||
|             entry = sorted(matched, key=lambda x: len(x.mount_point))[-1] | ||||
|             if 'rw' not in entry.options: | ||||
|                 self.execute('mount -o rw,remount {} {}'.format(entry.device, | ||||
|                                                                 entry.mount_point), | ||||
|                              as_root=True) | ||||
|         else: | ||||
|             message = 'Could not find mount point for executables directory {}' | ||||
|             raise TargetError(message.format(self.executables_directory)) | ||||
|  | ||||
|  | ||||
| FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num']) | ||||
| PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name') | ||||
|  | ||||
|  | ||||
| class Cpuinfo(object): | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def architecture(self): | ||||
|         for section in self.sections: | ||||
|             if 'CPU architecture' in section: | ||||
|                 return section['CPU architecture'] | ||||
|             if 'architecture' in section: | ||||
|                 return section['architecture'] | ||||
|  | ||||
|     @property | ||||
|     @memoized | ||||
|     def cpu_names(self): | ||||
|         cpu_names = [] | ||||
|         global_name = None | ||||
|         for section in self.sections: | ||||
|             if 'processor' in section: | ||||
|                 if 'CPU part' in section: | ||||
|                     cpu_names.append(_get_part_name(section)) | ||||
|                 elif 'model name' in section: | ||||
|                     cpu_names.append(_get_model_name(section)) | ||||
|                 else: | ||||
|                     cpu_names.append(None) | ||||
|             elif 'CPU part' in section: | ||||
|                 global_name = _get_part_name(section) | ||||
|         return [caseless_string(c or global_name) for c in cpu_names] | ||||
|  | ||||
|     def __init__(self, text): | ||||
|         self.sections = None | ||||
|         self.text = None | ||||
|         self.parse(text) | ||||
|  | ||||
|     @memoized | ||||
|     def get_cpu_features(self, cpuid=0): | ||||
|         global_features = [] | ||||
|         for section in self.sections: | ||||
|             if 'processor' in section: | ||||
|                 if int(section.get('processor')) != cpuid: | ||||
|                     continue | ||||
|                 if 'Features' in section: | ||||
|                     return section.get('Features').split() | ||||
|             elif 'Features' in section: | ||||
|                 global_features = section.get('Features').split() | ||||
|         return global_features | ||||
|  | ||||
|     def parse(self, text): | ||||
|         self.sections = [] | ||||
|         current_section = {} | ||||
|         self.text = text.strip() | ||||
|         for line in self.text.split('\n'): | ||||
|             line = line.strip() | ||||
|             if line: | ||||
|                 key, value = line.split(':', 1) | ||||
|                 current_section[key.strip()] = value.strip() | ||||
|             else:  # not line | ||||
|                 self.sections.append(current_section) | ||||
|                 current_section = {} | ||||
|         self.sections.append(current_section) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return 'CpuInfo({})'.format(self.cpu_names) | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class KernelVersion(object): | ||||
|  | ||||
|     def __init__(self, version_string): | ||||
|         if ' #' in version_string: | ||||
|             release, version = version_string.split(' #') | ||||
|             self.release = release | ||||
|             self.version = version | ||||
|         elif version_string.startswith('#'): | ||||
|             self.release = '' | ||||
|             self.version = version_string | ||||
|         else: | ||||
|             self.release = version_string | ||||
|             self.version = '' | ||||
|  | ||||
|     def __str__(self): | ||||
|         return '{} {}'.format(self.release, self.version) | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class KernelConfig(object): | ||||
|  | ||||
|     not_set_regex = re.compile(r'# (\S+) is not set') | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_config_name(name): | ||||
|         name = name.upper() | ||||
|         if not name.startswith('CONFIG_'): | ||||
|             name = 'CONFIG_' + name | ||||
|         return name | ||||
|  | ||||
|     def iteritems(self): | ||||
|         return self._config.iteritems() | ||||
|  | ||||
|     def __init__(self, text): | ||||
|         self.text = text | ||||
|         self._config = {} | ||||
|         for line in text.split('\n'): | ||||
|             line = line.strip() | ||||
|             if line.startswith('#'): | ||||
|                 match = self.not_set_regex.search(line) | ||||
|                 if match: | ||||
|                     self._config[match.group(1)] = 'n' | ||||
|             elif '=' in line: | ||||
|                 name, value = line.split('=', 1) | ||||
|                 self._config[name.strip()] = value.strip() | ||||
|  | ||||
|     def get(self, name): | ||||
|         return self._config.get(self.get_config_name(name)) | ||||
|  | ||||
|     def like(self, name): | ||||
|         regex = re.compile(name, re.I) | ||||
|         result = {} | ||||
|         for k, v in self._config.iteritems(): | ||||
|             if regex.search(k): | ||||
|                 result[k] = v | ||||
|         return result | ||||
|  | ||||
|     def is_enabled(self, name): | ||||
|         return self.get(name) == 'y' | ||||
|  | ||||
|     def is_module(self, name): | ||||
|         return self.get(name) == 'm' | ||||
|  | ||||
|     def is_not_set(self, name): | ||||
|         return self.get(name) == 'n' | ||||
|  | ||||
|     def has(self, name): | ||||
|         return self.get(name) in ['m', 'y'] | ||||
|  | ||||
|  | ||||
| class LocalLinuxTarget(LinuxTarget): | ||||
|  | ||||
|     conn_cls = LocalConnection | ||||
|  | ||||
|     def connect(self, timeout=None): | ||||
|         if self.working_directory is None: | ||||
|             self.working_directory = '/tmp' | ||||
|         if self.executables_directory is None: | ||||
|             self.executables_directory = '/tmp' | ||||
|         super(LocalLinuxTarget, self).connect(timeout) | ||||
|  | ||||
|  | ||||
| def _get_model_name(section): | ||||
|     name_string = section['model name'] | ||||
|     parts = name_string.split('@')[0].strip().split() | ||||
|     return ' '.join([p for p in parts | ||||
|                      if '(' not in p and p != 'CPU']) | ||||
|  | ||||
|  | ||||
| def _get_part_name(section): | ||||
|     implementer = section.get('CPU implementer', '0x0') | ||||
|     part = section['CPU part'] | ||||
|     variant = section.get('CPU variant', '0x0') | ||||
|     name = get_cpu_name(*map(integer, [implementer, part, variant])) | ||||
|     if name is None: | ||||
|         name = '{}/{}/{}'.format(implementer, part, variant) | ||||
|     return name | ||||
|  | ||||
							
								
								
									
										20
									
								
								devlib/trace/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								devlib/trace/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import logging | ||||
|  | ||||
|  | ||||
| class TraceCollector(object): | ||||
|  | ||||
|     def __init__(self, target): | ||||
|         self.target = target | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|  | ||||
|     def reset(self): | ||||
|         pass | ||||
|  | ||||
|     def start(self): | ||||
|         pass | ||||
|  | ||||
|     def stop(self): | ||||
|         pass | ||||
|  | ||||
|     def get_trace(self, outfile): | ||||
|         pass | ||||
							
								
								
									
										199
									
								
								devlib/trace/ftrace.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								devlib/trace/ftrace.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| from __future__ import division | ||||
| import os | ||||
| import time | ||||
| import subprocess | ||||
|  | ||||
| from devlib.trace import TraceCollector | ||||
| from devlib.host import PACKAGE_BIN_DIRECTORY | ||||
| from devlib.exception import TargetError, HostError | ||||
| from devlib.utils.misc import check_output, which | ||||
|  | ||||
|  | ||||
| TRACE_MARKER_START = 'TRACE_MARKER_START' | ||||
| TRACE_MARKER_STOP = 'TRACE_MARKER_STOP' | ||||
| OUTPUT_TRACE_FILE = 'trace.dat' | ||||
| DEFAULT_EVENTS = [ | ||||
|     'cpu_frequency', | ||||
|     'cpu_idle', | ||||
|     'sched_migrate_task', | ||||
|     'sched_process_exec', | ||||
|     'sched_process_fork', | ||||
|     'sched_stat_iowait', | ||||
|     'sched_switch', | ||||
|     'sched_wakeup', | ||||
|     'sched_wakeup_new', | ||||
| ] | ||||
| TIMEOUT = 180 | ||||
|  | ||||
|  | ||||
| class FtraceCollector(TraceCollector): | ||||
|  | ||||
|     def __init__(self, target, | ||||
|                  events=None, | ||||
|                  buffer_size=None, | ||||
|                  buffer_size_step=1000, | ||||
|                  buffer_size_file='/sys/kernel/debug/tracing/buffer_size_kb', | ||||
|                  marker_file='/sys/kernel/debug/tracing/trace_marker', | ||||
|                  automark=True, | ||||
|                  autoreport=True, | ||||
|                  autoview=False, | ||||
|                  no_install=False, | ||||
|                  ): | ||||
|         super(FtraceCollector, self).__init__(target) | ||||
|         self.events = events if events is not None else DEFAULT_EVENTS | ||||
|         self.buffer_size = buffer_size | ||||
|         self.buffer_size_step = buffer_size_step | ||||
|         self.buffer_size_file = buffer_size_file | ||||
|         self.marker_file = marker_file | ||||
|         self.automark = automark | ||||
|         self.autoreport = autoreport | ||||
|         self.autoview = autoview | ||||
|         self.target_output_file = os.path.join(self.target.working_directory, OUTPUT_TRACE_FILE) | ||||
|         self.target_binary = None | ||||
|         self.host_binary = None | ||||
|         self.start_time = None | ||||
|         self.stop_time = None | ||||
|         self.event_string = _build_trace_events(self.events) | ||||
|         self._reset_needed = True | ||||
|  | ||||
|         self.host_binary = which('trace-cmd') | ||||
|         self.kernelshark = which('kernelshark') | ||||
|  | ||||
|         if not self.target.is_rooted: | ||||
|             raise TargetError('trace-cmd instrument cannot be used on an unrooted device.') | ||||
|         if self.autoreport and self.host_binary is None: | ||||
|             raise HostError('trace-cmd binary must be installed on the host if autoreport=True.') | ||||
|         if self.autoview and self.kernelshark is None: | ||||
|             raise HostError('kernelshark binary must be installed on the host if autoview=True.') | ||||
|         if not no_install: | ||||
|             host_file = os.path.join(PACKAGE_BIN_DIRECTORY, self.target.abi, 'trace-cmd') | ||||
|             self.target_binary = self.target.install(host_file) | ||||
|         else: | ||||
|             if not self.target.is_installed('trace-cmd'): | ||||
|                 raise TargetError('No trace-cmd found on device and no_install=True is specified.') | ||||
|             self.target_binary = 'trace-cmd' | ||||
|  | ||||
|     def reset(self): | ||||
|         if self.buffer_size: | ||||
|             self._set_buffer_size() | ||||
|         self.target.execute('{} reset'.format(self.target_binary), as_root=True, timeout=TIMEOUT) | ||||
|         self._reset_needed = False | ||||
|  | ||||
|     def start(self): | ||||
|         self.start_time = time.time() | ||||
|         if self._reset_needed: | ||||
|             self.reset() | ||||
|         if self.automark: | ||||
|             self.mark_start() | ||||
|         self.target.execute('{} start {}'.format(self.target_binary, self.event_string), as_root=True) | ||||
|  | ||||
|     def stop(self): | ||||
|         self.stop_time = time.time() | ||||
|         if self.automark: | ||||
|             self.mark_stop() | ||||
|         self.target.execute('{} stop'.format(self.target_binary), timeout=TIMEOUT, as_root=True) | ||||
|         self._reset_needed = True | ||||
|  | ||||
|     def get_trace(self, outfile): | ||||
|         if os.path.isdir(outfile): | ||||
|             outfile = os.path.join(outfile, os.path.dirname(self.target_output_file)) | ||||
|         self.target.execute('{} extract -o {}'.format(self.target_binary, self.target_output_file), | ||||
|                             timeout=TIMEOUT, as_root=True) | ||||
|  | ||||
|         # The size of trace.dat will depend on how long trace-cmd was running. | ||||
|         # Therefore timout for the pull command must also be adjusted | ||||
|         # accordingly. | ||||
|         pull_timeout = self.stop_time - self.start_time | ||||
|         self.target.pull(self.target_output_file, outfile, timeout=pull_timeout) | ||||
|         if not os.path.isfile(outfile): | ||||
|             self.logger.warning('Binary trace not pulled from device.') | ||||
|         else: | ||||
|             if self.autoreport: | ||||
|                 textfile = os.path.splitext(outfile)[0] + '.txt' | ||||
|                 self.report(outfile, textfile) | ||||
|             if self.autoview: | ||||
|                 self.view(outfile) | ||||
|  | ||||
|     def report(self, binfile, destfile): | ||||
|         # To get the output of trace.dat, trace-cmd must be installed | ||||
|         # This is done host-side because the generated file is very large | ||||
|         try: | ||||
|             command = '{} report {} > {}'.format(self.host_binary, binfile, destfile) | ||||
|             self.logger.debug(command) | ||||
|             process = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True) | ||||
|             _, error = process.communicate() | ||||
|             if process.returncode: | ||||
|                 raise TargetError('trace-cmd returned non-zero exit code {}'.format(process.returncode)) | ||||
|             if error: | ||||
|                 # logged at debug level, as trace-cmd always outputs some | ||||
|                 # errors that seem benign. | ||||
|                 self.logger.debug(error) | ||||
|             if os.path.isfile(destfile): | ||||
|                 self.logger.debug('Verifying traces.') | ||||
|                 with open(destfile) as fh: | ||||
|                     for line in fh: | ||||
|                         if 'EVENTS DROPPED' in line: | ||||
|                             self.logger.warning('Dropped events detected.') | ||||
|                             break | ||||
|                     else: | ||||
|                         self.logger.debug('Trace verified.') | ||||
|             else: | ||||
|                 self.logger.warning('Could not generate trace.txt.') | ||||
|         except OSError: | ||||
|             raise HostError('Could not find trace-cmd. Please make sure it is installed and is in PATH.') | ||||
|  | ||||
|     def view(self, binfile): | ||||
|         check_output('{} {}'.format(self.kernelshark, binfile), shell=True) | ||||
|  | ||||
|     def teardown(self): | ||||
|         self.target.remove(self.target.path.join(self.target.working_directory, OUTPUT_TRACE_FILE)) | ||||
|  | ||||
|     def mark_start(self): | ||||
|         self.target.write_value(self.marker_file, TRACE_MARKER_START, verify=False) | ||||
|  | ||||
|     def mark_stop(self): | ||||
|         self.target.write_value(self.marker_file, TRACE_MARKER_STOP, verify=False) | ||||
|  | ||||
|     def _set_buffer_size(self): | ||||
|         target_buffer_size = self.buffer_size | ||||
|         attempt_buffer_size = target_buffer_size | ||||
|         buffer_size = 0 | ||||
|         floor = 1000 if target_buffer_size > 1000 else target_buffer_size | ||||
|         while attempt_buffer_size >= floor: | ||||
|             self.target.write_value(self.buffer_size_file, attempt_buffer_size, verify=False) | ||||
|             buffer_size = self.target.read_int(self.buffer_size_file) | ||||
|             if buffer_size == attempt_buffer_size: | ||||
|                 break | ||||
|             else: | ||||
|                 attempt_buffer_size -= self.buffer_size_step | ||||
|         if buffer_size == target_buffer_size: | ||||
|             return | ||||
|         while attempt_buffer_size < target_buffer_size: | ||||
|             attempt_buffer_size += self.buffer_size_step | ||||
|             self.target.write_value(self.buffer_size_file, attempt_buffer_size, verify=False) | ||||
|             buffer_size = self.target.read_int(self.buffer_size_file) | ||||
|             if attempt_buffer_size != buffer_size: | ||||
|                 message = 'Failed to set trace buffer size to {}, value set was {}' | ||||
|                 self.logger.warning(message.format(target_buffer_size, buffer_size)) | ||||
|                 break | ||||
|  | ||||
|  | ||||
| def _build_trace_events(events): | ||||
|     event_string = ' '.join(['-e {}'.format(e) for e in events]) | ||||
|     return event_string | ||||
|  | ||||
							
								
								
									
										16
									
								
								devlib/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								devlib/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
							
								
								
									
										428
									
								
								devlib/utils/android.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										428
									
								
								devlib/utils/android.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,428 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| """ | ||||
| Utility functions for working with Android devices through adb. | ||||
|  | ||||
| """ | ||||
| # pylint: disable=E1103 | ||||
| import os | ||||
| import time | ||||
| import subprocess | ||||
| import logging | ||||
| import re | ||||
| from collections import defaultdict | ||||
|  | ||||
| from devlib.exception import TargetError, HostError | ||||
| from devlib.utils.misc import check_output, which | ||||
| from devlib.utils.misc import escape_single_quotes, escape_double_quotes | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger('android') | ||||
|  | ||||
| MAX_ATTEMPTS = 5 | ||||
| AM_START_ERROR = re.compile(r"Error: Activity class {[\w|.|/]*} does not exist") | ||||
|  | ||||
| # See: | ||||
| # http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels | ||||
| ANDROID_VERSION_MAP = { | ||||
|     22: 'LOLLYPOP_MR1', | ||||
|     21: 'LOLLYPOP', | ||||
|     20: 'KITKAT_WATCH', | ||||
|     19: 'KITKAT', | ||||
|     18: 'JELLY_BEAN_MR2', | ||||
|     17: 'JELLY_BEAN_MR1', | ||||
|     16: 'JELLY_BEAN', | ||||
|     15: 'ICE_CREAM_SANDWICH_MR1', | ||||
|     14: 'ICE_CREAM_SANDWICH', | ||||
|     13: 'HONEYCOMB_MR2', | ||||
|     12: 'HONEYCOMB_MR1', | ||||
|     11: 'HONEYCOMB', | ||||
|     10: 'GINGERBREAD_MR1', | ||||
|     9: 'GINGERBREAD', | ||||
|     8: 'FROYO', | ||||
|     7: 'ECLAIR_MR1', | ||||
|     6: 'ECLAIR_0_1', | ||||
|     5: 'ECLAIR', | ||||
|     4: 'DONUT', | ||||
|     3: 'CUPCAKE', | ||||
|     2: 'BASE_1_1', | ||||
|     1: 'BASE', | ||||
| } | ||||
|  | ||||
|  | ||||
| # Initialized in functions near the botton of the file | ||||
| android_home = None | ||||
| platform_tools = None | ||||
| adb = None | ||||
| aapt = None | ||||
| fastboot = None | ||||
|  | ||||
|  | ||||
| class AndroidProperties(object): | ||||
|  | ||||
|     def __init__(self, text): | ||||
|         self._properties = {} | ||||
|         self.parse(text) | ||||
|  | ||||
|     def parse(self, text): | ||||
|         self._properties = dict(re.findall(r'\[(.*?)\]:\s+\[(.*?)\]', text)) | ||||
|  | ||||
|     def iteritems(self): | ||||
|         return self._properties.iteritems() | ||||
|  | ||||
|     def __iter__(self): | ||||
|         return iter(self._properties) | ||||
|  | ||||
|     def __getattr__(self, name): | ||||
|         return self._properties.get(name) | ||||
|  | ||||
|     __getitem__ = __getattr__ | ||||
|  | ||||
|  | ||||
| class AdbDevice(object): | ||||
|  | ||||
|     def __init__(self, name, status): | ||||
|         self.name = name | ||||
|         self.status = status | ||||
|  | ||||
|     def __cmp__(self, other): | ||||
|         if isinstance(other, AdbDevice): | ||||
|             return cmp(self.name, other.name) | ||||
|         else: | ||||
|             return cmp(self.name, other) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return 'AdbDevice({}, {})'.format(self.name, self.status) | ||||
|  | ||||
|     __repr__ = __str__ | ||||
|  | ||||
|  | ||||
| class ApkInfo(object): | ||||
|  | ||||
|     version_regex = re.compile(r"name='(?P<name>[^']+)' versionCode='(?P<vcode>[^']+)' versionName='(?P<vname>[^']+)'") | ||||
|     name_regex = re.compile(r"name='(?P<name>[^']+)'") | ||||
|  | ||||
|     def __init__(self, path=None): | ||||
|         self.path = path | ||||
|         self.package = None | ||||
|         self.activity = None | ||||
|         self.label = None | ||||
|         self.version_name = None | ||||
|         self.version_code = None | ||||
|         self.parse(path) | ||||
|  | ||||
|     def parse(self, apk_path): | ||||
|         _check_env() | ||||
|         command = [aapt, 'dump', 'badging', apk_path] | ||||
|         logger.debug(' '.join(command)) | ||||
|         output = subprocess.check_output(command) | ||||
|         for line in output.split('\n'): | ||||
|             if line.startswith('application-label:'): | ||||
|                 self.label = line.split(':')[1].strip().replace('\'', '') | ||||
|             elif line.startswith('package:'): | ||||
|                 match = self.version_regex.search(line) | ||||
|                 if match: | ||||
|                     self.package = match.group('name') | ||||
|                     self.version_code = match.group('vcode') | ||||
|                     self.version_name = match.group('vname') | ||||
|             elif line.startswith('launchable-activity:'): | ||||
|                 match = self.name_regex.search(line) | ||||
|                 self.activity = match.group('name') | ||||
|             else: | ||||
|                 pass  # not interested | ||||
|  | ||||
|  | ||||
| class AdbConnection(object): | ||||
|  | ||||
|     # maintains the count of parallel active connections to a device, so that | ||||
|     # adb disconnect is not invoked untill all connections are closed | ||||
|     active_connections = defaultdict(int) | ||||
|  | ||||
|     @property | ||||
|     def name(self): | ||||
|         return self.device | ||||
|  | ||||
|     def __init__(self, device=None, timeout=10): | ||||
|         self.timeout = timeout | ||||
|         if device is None: | ||||
|             device = adb_get_device(timeout=timeout) | ||||
|         self.device = device | ||||
|         adb_connect(self.device) | ||||
|         AdbConnection.active_connections[self.device] += 1 | ||||
|  | ||||
|     def push(self, source, dest, timeout=None): | ||||
|         if timeout is None: | ||||
|             timeout = self.timeout | ||||
|         command = 'push {} {}'.format(source, dest) | ||||
|         return adb_command(self.device, command, timeout=timeout) | ||||
|  | ||||
|     def pull(self, source, dest, timeout=None): | ||||
|         if timeout is None: | ||||
|             timeout = self.timeout | ||||
|         command = 'pull {} {}'.format(source, dest) | ||||
|         return adb_command(self.device, command, timeout=timeout) | ||||
|  | ||||
|     def execute(self, command, timeout=None, check_exit_code=False, as_root=False): | ||||
|         return adb_shell(self.device, command, timeout, check_exit_code, as_root) | ||||
|  | ||||
|     def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False): | ||||
|         return adb_background_shell(self.device, command, stdout, stderr, as_root) | ||||
|  | ||||
|     def close(self): | ||||
|         AdbConnection.active_connections[self.device] -= 1 | ||||
|         if AdbConnection.active_connections[self.device] <= 0: | ||||
|             adb_disconnect(self.device) | ||||
|             del AdbConnection.active_connections[self.device] | ||||
|  | ||||
|     def cancel_running_command(self): | ||||
|         # adbd multiplexes commands so that they don't interfer with each | ||||
|         # other, so there is no need to explicitly cancel a running command | ||||
|         # before the next one can be issued. | ||||
|         pass | ||||
|  | ||||
|  | ||||
| def fastboot_command(command, timeout=None): | ||||
|     _check_env() | ||||
|     full_command = "fastboot {}".format(command) | ||||
|     logger.debug(full_command) | ||||
|     output, _ = check_output(full_command, timeout, shell=True) | ||||
|     return output | ||||
|  | ||||
|  | ||||
| def fastboot_flash_partition(partition, path_to_image): | ||||
|     command = 'flash {} {}'.format(partition, path_to_image) | ||||
|     fastboot_command(command) | ||||
|  | ||||
|  | ||||
| def adb_get_device(timeout=None): | ||||
|     """ | ||||
|     Returns the serial number of a connected android device. | ||||
|  | ||||
|     If there are more than one device connected to the machine, or it could not | ||||
|     find any device connected, :class:`devlib.exceptions.HostError` is raised. | ||||
|     """ | ||||
|     # TODO this is a hacky way to issue a adb command to all listed devices | ||||
|  | ||||
|     # The output of calling adb devices consists of a heading line then | ||||
|     # a list of the devices sperated by new line | ||||
|     # The last line is a blank new line. in otherwords, if there is a device found | ||||
|     # then the output length is 2 + (1 for each device) | ||||
|     start = time.time() | ||||
|     while True: | ||||
|         output = adb_command(None, "devices").splitlines()  # pylint: disable=E1103 | ||||
|         output_length = len(output) | ||||
|         if output_length == 3: | ||||
|             # output[1] is the 2nd line in the output which has the device name | ||||
|             # Splitting the line by '\t' gives a list of two indexes, which has | ||||
|             # device serial in 0 number and device type in 1. | ||||
|             return output[1].split('\t')[0] | ||||
|         elif output_length > 3: | ||||
|             message = '{} Android devices found; either explicitly specify ' +\ | ||||
|                     'the device you want, or make sure only one is connected.' | ||||
|             raise HostError(message.format(output_length - 2)) | ||||
|         else: | ||||
|             if timeout < time.time() - start: | ||||
|                 raise HostError('No device is connected and available') | ||||
|             time.sleep(1) | ||||
|  | ||||
|  | ||||
| def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS): | ||||
|     _check_env() | ||||
|     tries = 0 | ||||
|     output = None | ||||
|     while tries <= attempts: | ||||
|         tries += 1 | ||||
|         if device: | ||||
|             command = 'adb connect {}'.format(device) | ||||
|             logger.debug(command) | ||||
|             output, _ = check_output(command, shell=True, timeout=timeout) | ||||
|         if _ping(device): | ||||
|             break | ||||
|         time.sleep(10) | ||||
|     else:  # did not connect to the device | ||||
|         message = 'Could not connect to {}'.format(device or 'a device') | ||||
|         if output: | ||||
|             message += '; got: "{}"'.format(output) | ||||
|         raise HostError(message) | ||||
|  | ||||
|  | ||||
| def adb_disconnect(device): | ||||
|     _check_env() | ||||
|     if not device: | ||||
|         return | ||||
|     if ":" in device: | ||||
|         command = "adb disconnect " + device | ||||
|         logger.debug(command) | ||||
|         retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True) | ||||
|         if retval: | ||||
|             raise TargetError('"{}" returned {}'.format(command, retval)) | ||||
|  | ||||
|  | ||||
| def _ping(device): | ||||
|     _check_env() | ||||
|     device_string = ' -s {}'.format(device) if device else '' | ||||
|     command = "adb{} shell \"ls / > /dev/null\"".format(device_string) | ||||
|     logger.debug(command) | ||||
|     result = subprocess.call(command, stderr=subprocess.PIPE, shell=True) | ||||
|     if not result: | ||||
|         return True | ||||
|     else: | ||||
|         return False | ||||
|  | ||||
|  | ||||
| def adb_shell(device, command, timeout=None, check_exit_code=False, as_root=False):  # NOQA | ||||
|     _check_env() | ||||
|     if as_root: | ||||
|         command = 'echo "{}" | su'.format(escape_double_quotes(command)) | ||||
|     device_string = ' -s {}'.format(device) if device else '' | ||||
|     full_command = 'adb{} shell "{}"'.format(device_string, | ||||
|                                              escape_double_quotes(command)) | ||||
|     logger.debug(full_command) | ||||
|     if check_exit_code: | ||||
|         actual_command = "adb{} shell '({}); echo $?'".format(device_string, | ||||
|                                                               escape_single_quotes(command)) | ||||
|         raw_output, error = check_output(actual_command, timeout, shell=True) | ||||
|         if raw_output: | ||||
|             try: | ||||
|                 output, exit_code, _ = raw_output.rsplit('\n', 2) | ||||
|             except ValueError: | ||||
|                 exit_code, _ = raw_output.rsplit('\n', 1) | ||||
|                 output = '' | ||||
|         else:  # raw_output is empty | ||||
|             exit_code = '969696'  # just because | ||||
|             output = '' | ||||
|  | ||||
|         exit_code = exit_code.strip() | ||||
|         if exit_code.isdigit(): | ||||
|             if int(exit_code): | ||||
|                 message = 'Got exit code {}\nfrom: {}\nSTDOUT: {}\nSTDERR: {}' | ||||
|                 raise TargetError(message.format(exit_code, full_command, output, error)) | ||||
|             elif AM_START_ERROR.findall(output): | ||||
|                 message = 'Could not start activity; got the following:' | ||||
|                 message += '\n{}'.format(AM_START_ERROR.findall(output)[0]) | ||||
|                 raise TargetError(message) | ||||
|         else:  # not all digits | ||||
|             if AM_START_ERROR.findall(output): | ||||
|                 message = 'Could not start activity; got the following:\n{}' | ||||
|                 raise TargetError(message.format(AM_START_ERROR.findall(output)[0])) | ||||
|             else: | ||||
|                 message = 'adb has returned early; did not get an exit code. '\ | ||||
|                           'Was kill-server invoked?' | ||||
|                 raise TargetError(message) | ||||
|     else:  # do not check exit code | ||||
|         output, _ = check_output(full_command, timeout, shell=True) | ||||
|     return output | ||||
|  | ||||
|  | ||||
| def adb_background_shell(device, command, | ||||
|                          stdout=subprocess.PIPE, | ||||
|                          stderr=subprocess.PIPE, | ||||
|                          as_root=False): | ||||
|     """Runs the sepcified command in a subprocess, returning the the Popen object.""" | ||||
|     _check_env() | ||||
|     if as_root: | ||||
|         command = 'echo \'{}\' | su'.format(escape_single_quotes(command)) | ||||
|     device_string = ' -s {}'.format(device) if device else '' | ||||
|     full_command = 'adb{} shell "{}"'.format(device_string, escape_double_quotes(command)) | ||||
|     logger.debug(full_command) | ||||
|     return subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True) | ||||
|  | ||||
|  | ||||
| def adb_list_devices(): | ||||
|     output = adb_command(None, 'devices') | ||||
|     devices = [] | ||||
|     for line in output.splitlines(): | ||||
|         parts = [p.strip() for p in line.split()] | ||||
|         if len(parts) == 2: | ||||
|             devices.append(AdbDevice(*parts)) | ||||
|     return devices | ||||
|  | ||||
|  | ||||
| def adb_command(device, command, timeout=None): | ||||
|     _check_env() | ||||
|     device_string = ' -s {}'.format(device) if device else '' | ||||
|     full_command = "adb{} {}".format(device_string, command) | ||||
|     logger.debug(full_command) | ||||
|     output, _ = check_output(full_command, timeout, shell=True) | ||||
|     return output | ||||
|  | ||||
|  | ||||
| # Messy environment initialisation stuff... | ||||
|  | ||||
| class _AndroidEnvironment(object): | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.android_home = None | ||||
|         self.platform_tools = None | ||||
|         self.adb = None | ||||
|         self.aapt = None | ||||
|         self.fastboot = None | ||||
|  | ||||
|  | ||||
| def _initialize_with_android_home(env): | ||||
|     logger.debug('Using ANDROID_HOME from the environment.') | ||||
|     env.android_home = android_home | ||||
|     env.platform_tools = os.path.join(android_home, 'platform-tools') | ||||
|     os.environ['PATH'] += os.pathsep + env.platform_tools | ||||
|     _init_common(env) | ||||
|     return env | ||||
|  | ||||
|  | ||||
| def _initialize_without_android_home(env): | ||||
|     if which('adb'): | ||||
|         env.adb = 'adb' | ||||
|     else: | ||||
|         raise HostError('ANDROID_HOME is not set and adb is not in PATH. ' | ||||
|                         'Have you installed Android SDK?') | ||||
|     logger.debug('Discovering ANDROID_HOME from adb path.') | ||||
|     env.platform_tools = os.path.dirname(env.adb) | ||||
|     env.android_home = os.path.dirname(env.platform_tools) | ||||
|     _init_common(env) | ||||
|     return env | ||||
|  | ||||
|  | ||||
| def _init_common(env): | ||||
|     logger.debug('ANDROID_HOME: {}'.format(env.android_home)) | ||||
|     build_tools_directory = os.path.join(env.android_home, 'build-tools') | ||||
|     if not os.path.isdir(build_tools_directory): | ||||
|         msg = '''ANDROID_HOME ({}) does not appear to have valid Android SDK install | ||||
|                  (cannot find build-tools)''' | ||||
|         raise HostError(msg.format(env.android_home)) | ||||
|     versions = os.listdir(build_tools_directory) | ||||
|     for version in reversed(sorted(versions)): | ||||
|         aapt_path = os.path.join(build_tools_directory, version, 'aapt') | ||||
|         if os.path.isfile(aapt_path): | ||||
|             logger.debug('Using aapt for version {}'.format(version)) | ||||
|             env.aapt = aapt_path | ||||
|             break | ||||
|     else: | ||||
|         raise HostError('aapt not found. Please make sure at least one Android ' | ||||
|                         'platform is installed.') | ||||
|  | ||||
|  | ||||
| def _check_env(): | ||||
|     global android_home, platform_tools, adb, aapt  # pylint: disable=W0603 | ||||
|     if not android_home: | ||||
|         android_home = os.getenv('ANDROID_HOME') | ||||
|         if android_home: | ||||
|             _env = _initialize_with_android_home(_AndroidEnvironment()) | ||||
|         else: | ||||
|             _env = _initialize_without_android_home(_AndroidEnvironment()) | ||||
|         android_home = _env.android_home | ||||
|         platform_tools = _env.platform_tools | ||||
|         adb = _env.adb | ||||
|         aapt = _env.aapt | ||||
							
								
								
									
										552
									
								
								devlib/utils/misc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										552
									
								
								devlib/utils/misc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,552 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| """ | ||||
| Miscellaneous functions that don't fit anywhere else. | ||||
|  | ||||
| """ | ||||
| from __future__ import division | ||||
| import os | ||||
| import sys | ||||
| import re | ||||
| import string | ||||
| import threading | ||||
| import signal | ||||
| import subprocess | ||||
| import pkgutil | ||||
| import logging | ||||
| import random | ||||
| from operator import itemgetter | ||||
| from itertools import groupby | ||||
| from functools import partial | ||||
|  | ||||
|  | ||||
| # ABI --> architectures list | ||||
| ABI_MAP = { | ||||
|     'armeabi': ['armeabi', 'armv7', 'armv7l', 'armv7el', 'armv7lh'], | ||||
|     'arm64': ['arm64', 'armv8', 'arm64-v8a', 'aarch64'], | ||||
| } | ||||
|  | ||||
| # Vendor ID --> CPU part ID --> CPU variant ID --> Core Name | ||||
| # None means variant is not used. | ||||
| CPU_PART_MAP = { | ||||
|     0x41: {  # ARM | ||||
|         0x926: {None: 'ARM926'}, | ||||
|         0x946: {None: 'ARM946'}, | ||||
|         0x966: {None: 'ARM966'}, | ||||
|         0xb02: {None: 'ARM11MPCore'}, | ||||
|         0xb36: {None: 'ARM1136'}, | ||||
|         0xb56: {None: 'ARM1156'}, | ||||
|         0xb76: {None: 'ARM1176'}, | ||||
|         0xc05: {None: 'A5'}, | ||||
|         0xc07: {None: 'A7'}, | ||||
|         0xc08: {None: 'A8'}, | ||||
|         0xc09: {None: 'A9'}, | ||||
|         0xc0f: {None: 'A15'}, | ||||
|         0xc14: {None: 'R4'}, | ||||
|         0xc15: {None: 'R5'}, | ||||
|         0xc20: {None: 'M0'}, | ||||
|         0xc21: {None: 'M1'}, | ||||
|         0xc23: {None: 'M3'}, | ||||
|         0xc24: {None: 'M4'}, | ||||
|         0xc27: {None: 'M7'}, | ||||
|         0xd03: {None: 'A53'}, | ||||
|         0xd07: {None: 'A57'}, | ||||
|         0xd08: {None: 'A72'}, | ||||
|     }, | ||||
|     0x4e: {  # Nvidia | ||||
|         0x0: {None: 'Denver'}, | ||||
|     }, | ||||
|     0x51: {  # Qualcomm | ||||
|         0x02d: {None: 'Scorpion'}, | ||||
|         0x04d: {None: 'MSM8960'}, | ||||
|         0x06f: {  # Krait | ||||
|             0x2: 'Krait400', | ||||
|             0x3: 'Krait450', | ||||
|         }, | ||||
|     }, | ||||
|     0x56: {  # Marvell | ||||
|         0x131: { | ||||
|             0x2: 'Feroceon 88F6281', | ||||
|         } | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
| def get_cpu_name(implementer, part, variant): | ||||
|     part_data = CPU_PART_MAP.get(implementer, {}).get(part, {}) | ||||
|     if None in part_data:  # variant does not determine core Name for this vendor | ||||
|         name = part_data[None] | ||||
|     else: | ||||
|         name = part_data.get(variant) | ||||
|     return name | ||||
|  | ||||
|  | ||||
| def preexec_function(): | ||||
|     # Ignore the SIGINT signal by setting the handler to the standard | ||||
|     # signal handler SIG_IGN. | ||||
|     signal.signal(signal.SIGINT, signal.SIG_IGN) | ||||
|     # Change process group in case we have to kill the subprocess and all of | ||||
|     # its children later. | ||||
|     # TODO: this is Unix-specific; would be good to find an OS-agnostic way | ||||
|     #       to do this in case we wanna port WA to Windows. | ||||
|     os.setpgrp() | ||||
|  | ||||
|  | ||||
| check_output_logger = logging.getLogger('check_output') | ||||
|  | ||||
|  | ||||
| # Defined here rather than in devlib.exceptions due to module load dependencies | ||||
| class TimeoutError(Exception): | ||||
|     """Raised when a subprocess command times out. This is basically a ``WAError``-derived version | ||||
|     of ``subprocess.CalledProcessError``, the thinking being that while a timeout could be due to | ||||
|     programming error (e.g. not setting long enough timers), it is often due to some failure in the | ||||
|     environment, and there fore should be classed as a "user error".""" | ||||
|  | ||||
|     def __init__(self, command, output): | ||||
|         super(TimeoutError, self).__init__('Timed out: {}'.format(command)) | ||||
|         self.command = command | ||||
|         self.output = output | ||||
|  | ||||
|     def __str__(self): | ||||
|         return '\n'.join([self.message, 'OUTPUT:', self.output or '']) | ||||
|  | ||||
|  | ||||
| def check_output(command, timeout=None, ignore=None, inputtext=None, **kwargs): | ||||
|     """This is a version of subprocess.check_output that adds a timeout parameter to kill | ||||
|     the subprocess if it does not return within the specified time.""" | ||||
|     # pylint: disable=too-many-branches | ||||
|     if ignore is None: | ||||
|         ignore = [] | ||||
|     elif isinstance(ignore, int): | ||||
|         ignore = [ignore] | ||||
|     elif not isinstance(ignore, list) and ignore != 'all': | ||||
|         message = 'Invalid value for ignore parameter: "{}"; must be an int or a list' | ||||
|         raise ValueError(message.format(ignore)) | ||||
|     if 'stdout' in kwargs: | ||||
|         raise ValueError('stdout argument not allowed, it will be overridden.') | ||||
|  | ||||
|     def callback(pid): | ||||
|         try: | ||||
|             check_output_logger.debug('{} timed out; sending SIGKILL'.format(pid)) | ||||
|             os.killpg(pid, signal.SIGKILL) | ||||
|         except OSError: | ||||
|             pass  # process may have already terminated. | ||||
|  | ||||
|     process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||||
|                                stdin=subprocess.PIPE, | ||||
|                                preexec_fn=preexec_function, **kwargs) | ||||
|  | ||||
|     if timeout: | ||||
|         timer = threading.Timer(timeout, callback, [process.pid, ]) | ||||
|         timer.start() | ||||
|  | ||||
|     try: | ||||
|         output, error = process.communicate(inputtext) | ||||
|     finally: | ||||
|         if timeout: | ||||
|             timer.cancel() | ||||
|  | ||||
|     retcode = process.poll() | ||||
|     if retcode: | ||||
|         if retcode == -9:  # killed, assume due to timeout callback | ||||
|             raise TimeoutError(command, output='\n'.join([output, error])) | ||||
|         elif ignore != 'all' and retcode not in ignore: | ||||
|             raise subprocess.CalledProcessError(retcode, command, output='\n'.join([output, error])) | ||||
|     return output, error | ||||
|  | ||||
|  | ||||
| def walk_modules(path): | ||||
|     """ | ||||
|     Given package name, return a list of all modules (including submodules, etc) | ||||
|     in that package. | ||||
|  | ||||
|     """ | ||||
|     root_mod = __import__(path, {}, {}, ['']) | ||||
|     mods = [root_mod] | ||||
|     for _, name, ispkg in pkgutil.iter_modules(root_mod.__path__): | ||||
|         submod_path = '.'.join([path, name]) | ||||
|         if ispkg: | ||||
|             mods.extend(walk_modules(submod_path)) | ||||
|         else: | ||||
|             submod = __import__(submod_path, {}, {}, ['']) | ||||
|             mods.append(submod) | ||||
|     return mods | ||||
|  | ||||
|  | ||||
| def ensure_directory_exists(dirpath): | ||||
|     """A filter for directory paths to ensure they exist.""" | ||||
|     if not os.path.isdir(dirpath): | ||||
|         os.makedirs(dirpath) | ||||
|     return dirpath | ||||
|  | ||||
|  | ||||
| def ensure_file_directory_exists(filepath): | ||||
|     """ | ||||
|     A filter for file paths to ensure the directory of the | ||||
|     file exists and the file can be created there. The file | ||||
|     itself is *not* going to be created if it doesn't already | ||||
|     exist. | ||||
|  | ||||
|     """ | ||||
|     ensure_directory_exists(os.path.dirname(filepath)) | ||||
|     return filepath | ||||
|  | ||||
|  | ||||
| def merge_dicts(*args, **kwargs): | ||||
|     if not len(args) >= 2: | ||||
|         raise ValueError('Must specify at least two dicts to merge.') | ||||
|     func = partial(_merge_two_dicts, **kwargs) | ||||
|     return reduce(func, args) | ||||
|  | ||||
|  | ||||
| def _merge_two_dicts(base, other, list_duplicates='all', match_types=False,  # pylint: disable=R0912,R0914 | ||||
|                      dict_type=dict, should_normalize=True, should_merge_lists=True): | ||||
|     """Merge dicts normalizing their keys.""" | ||||
|     merged = dict_type() | ||||
|     base_keys = base.keys() | ||||
|     other_keys = other.keys() | ||||
|     norm = normalize if should_normalize else lambda x, y: x | ||||
|  | ||||
|     base_only = [] | ||||
|     other_only = [] | ||||
|     both = [] | ||||
|     union = [] | ||||
|     for k in base_keys: | ||||
|         if k in other_keys: | ||||
|             both.append(k) | ||||
|         else: | ||||
|             base_only.append(k) | ||||
|             union.append(k) | ||||
|     for k in other_keys: | ||||
|         if k in base_keys: | ||||
|             union.append(k) | ||||
|         else: | ||||
|             union.append(k) | ||||
|             other_only.append(k) | ||||
|  | ||||
|     for k in union: | ||||
|         if k in base_only: | ||||
|             merged[k] = norm(base[k], dict_type) | ||||
|         elif k in other_only: | ||||
|             merged[k] = norm(other[k], dict_type) | ||||
|         elif k in both: | ||||
|             base_value = base[k] | ||||
|             other_value = other[k] | ||||
|             base_type = type(base_value) | ||||
|             other_type = type(other_value) | ||||
|             if (match_types and (base_type != other_type) and | ||||
|                     (base_value is not None) and (other_value is not None)): | ||||
|                 raise ValueError('Type mismatch for {} got {} ({}) and {} ({})'.format(k, base_value, base_type, | ||||
|                                                                                        other_value, other_type)) | ||||
|             if isinstance(base_value, dict): | ||||
|                 merged[k] = _merge_two_dicts(base_value, other_value, list_duplicates, match_types, dict_type) | ||||
|             elif isinstance(base_value, list): | ||||
|                 if should_merge_lists: | ||||
|                     merged[k] = _merge_two_lists(base_value, other_value, list_duplicates, dict_type) | ||||
|                 else: | ||||
|                     merged[k] = _merge_two_lists([], other_value, list_duplicates, dict_type) | ||||
|  | ||||
|             elif isinstance(base_value, set): | ||||
|                 merged[k] = norm(base_value.union(other_value), dict_type) | ||||
|             else: | ||||
|                 merged[k] = norm(other_value, dict_type) | ||||
|         else:  # Should never get here | ||||
|             raise AssertionError('Unexpected merge key: {}'.format(k)) | ||||
|  | ||||
|     return merged | ||||
|  | ||||
|  | ||||
| def merge_lists(*args, **kwargs): | ||||
|     if not len(args) >= 2: | ||||
|         raise ValueError('Must specify at least two lists to merge.') | ||||
|     func = partial(_merge_two_lists, **kwargs) | ||||
|     return reduce(func, args) | ||||
|  | ||||
|  | ||||
| def _merge_two_lists(base, other, duplicates='all', dict_type=dict):  # pylint: disable=R0912 | ||||
|     """ | ||||
|     Merge lists, normalizing their entries. | ||||
|  | ||||
|     parameters: | ||||
|  | ||||
|         :base, other: the two lists to be merged. ``other`` will be merged on | ||||
|                       top of base. | ||||
|         :duplicates: Indicates the strategy of handling entries that appear | ||||
|                      in both lists. ``all`` will keep occurrences from both | ||||
|                      lists; ``first`` will only keep occurrences from | ||||
|                      ``base``; ``last`` will only keep occurrences from | ||||
|                      ``other``; | ||||
|  | ||||
|                      .. note:: duplicate entries that appear in the *same* list | ||||
|                                will never be removed. | ||||
|  | ||||
|     """ | ||||
|     if not isiterable(base): | ||||
|         base = [base] | ||||
|     if not isiterable(other): | ||||
|         other = [other] | ||||
|     if duplicates == 'all': | ||||
|         merged_list = [] | ||||
|         for v in normalize(base, dict_type) + normalize(other, dict_type): | ||||
|             if not _check_remove_item(merged_list, v): | ||||
|                 merged_list.append(v) | ||||
|         return merged_list | ||||
|     elif duplicates == 'first': | ||||
|         base_norm = normalize(base, dict_type) | ||||
|         merged_list = normalize(base, dict_type) | ||||
|         for v in base_norm: | ||||
|             _check_remove_item(merged_list, v) | ||||
|         for v in normalize(other, dict_type): | ||||
|             if not _check_remove_item(merged_list, v): | ||||
|                 if v not in base_norm: | ||||
|                     merged_list.append(v)  # pylint: disable=no-member | ||||
|         return merged_list | ||||
|     elif duplicates == 'last': | ||||
|         other_norm = normalize(other, dict_type) | ||||
|         merged_list = [] | ||||
|         for v in normalize(base, dict_type): | ||||
|             if not _check_remove_item(merged_list, v): | ||||
|                 if v not in other_norm: | ||||
|                     merged_list.append(v) | ||||
|         for v in other_norm: | ||||
|             if not _check_remove_item(merged_list, v): | ||||
|                 merged_list.append(v) | ||||
|         return merged_list | ||||
|     else: | ||||
|         raise ValueError('Unexpected value for list duplicates argument: {}. '.format(duplicates) + | ||||
|                          'Must be in {"all", "first", "last"}.') | ||||
|  | ||||
|  | ||||
| def _check_remove_item(the_list, item): | ||||
|     """Helper function for merge_lists that implements checking wether an items | ||||
|     should be removed from the list and doing so if needed. Returns ``True`` if | ||||
|     the item has been removed and ``False`` otherwise.""" | ||||
|     if not isinstance(item, basestring): | ||||
|         return False | ||||
|     if not item.startswith('~'): | ||||
|         return False | ||||
|     actual_item = item[1:] | ||||
|     if actual_item in the_list: | ||||
|         del the_list[the_list.index(actual_item)] | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def normalize(value, dict_type=dict): | ||||
|     """Normalize values. Recursively normalizes dict keys to be lower case, | ||||
|     no surrounding whitespace, underscore-delimited strings.""" | ||||
|     if isinstance(value, dict): | ||||
|         normalized = dict_type() | ||||
|         for k, v in value.iteritems(): | ||||
|             key = k.strip().lower().replace(' ', '_') | ||||
|             normalized[key] = normalize(v, dict_type) | ||||
|         return normalized | ||||
|     elif isinstance(value, list): | ||||
|         return [normalize(v, dict_type) for v in value] | ||||
|     elif isinstance(value, tuple): | ||||
|         return tuple([normalize(v, dict_type) for v in value]) | ||||
|     else: | ||||
|         return value | ||||
|  | ||||
|  | ||||
| def convert_new_lines(text): | ||||
|     """ Convert new lines to a common format.  """ | ||||
|     return text.replace('\r\n', '\n').replace('\r', '\n') | ||||
|  | ||||
|  | ||||
| def escape_quotes(text): | ||||
|     """Escape quotes, and escaped quotes, in the specified text.""" | ||||
|     return re.sub(r'\\("|\')', r'\\\\\1', text).replace('\'', '\\\'').replace('\"', '\\\"') | ||||
|  | ||||
|  | ||||
| def escape_single_quotes(text): | ||||
|     """Escape single quotes, and escaped single quotes, in the specified text.""" | ||||
|     return re.sub(r'\\("|\')', r'\\\\\1', text).replace('\'', '\'\\\'\'') | ||||
|  | ||||
|  | ||||
| def escape_double_quotes(text): | ||||
|     """Escape double quotes, and escaped double quotes, in the specified text.""" | ||||
|     return re.sub(r'\\("|\')', r'\\\\\1', text).replace('\"', '\\\"') | ||||
|  | ||||
|  | ||||
| def getch(count=1): | ||||
|     """Read ``count`` characters from standard input.""" | ||||
|     if os.name == 'nt': | ||||
|         import msvcrt  # pylint: disable=F0401 | ||||
|         return ''.join([msvcrt.getch() for _ in xrange(count)]) | ||||
|     else:  # assume Unix | ||||
|         import tty  # NOQA | ||||
|         import termios  # NOQA | ||||
|         fd = sys.stdin.fileno() | ||||
|         old_settings = termios.tcgetattr(fd) | ||||
|         try: | ||||
|             tty.setraw(sys.stdin.fileno()) | ||||
|             ch = sys.stdin.read(count) | ||||
|         finally: | ||||
|             termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) | ||||
|         return ch | ||||
|  | ||||
|  | ||||
| def isiterable(obj): | ||||
|     """Returns ``True`` if the specified object is iterable and | ||||
|     *is not a string type*, ``False`` otherwise.""" | ||||
|     return hasattr(obj, '__iter__') and not isinstance(obj, basestring) | ||||
|  | ||||
|  | ||||
| def as_relative(path): | ||||
|     """Convert path to relative by stripping away the leading '/' on UNIX or | ||||
|     the equivant on other platforms.""" | ||||
|     path = os.path.splitdrive(path)[1] | ||||
|     return path.lstrip(os.sep) | ||||
|  | ||||
|  | ||||
| def get_cpu_mask(cores): | ||||
|     """Return a string with the hex for the cpu mask for the specified core numbers.""" | ||||
|     mask = 0 | ||||
|     for i in cores: | ||||
|         mask |= 1 << i | ||||
|     return '0x{0:x}'.format(mask) | ||||
|  | ||||
|  | ||||
| def which(name): | ||||
|     """Platform-independent version of UNIX which utility.""" | ||||
|     if os.name == 'nt': | ||||
|         paths = os.getenv('PATH').split(os.pathsep) | ||||
|         exts = os.getenv('PATHEXT').split(os.pathsep) | ||||
|         for path in paths: | ||||
|             testpath = os.path.join(path, name) | ||||
|             if os.path.isfile(testpath): | ||||
|                 return testpath | ||||
|             for ext in exts: | ||||
|                 testpathext = testpath + ext | ||||
|                 if os.path.isfile(testpathext): | ||||
|                     return testpathext | ||||
|         return None | ||||
|     else:  # assume UNIX-like | ||||
|         try: | ||||
|             return check_output(['which', name])[0].strip()  # pylint: disable=E1103 | ||||
|         except subprocess.CalledProcessError: | ||||
|             return None | ||||
|  | ||||
|  | ||||
| _bash_color_regex = re.compile('\x1b\\[[0-9;]+m') | ||||
|  | ||||
|  | ||||
| def strip_bash_colors(text): | ||||
|     return _bash_color_regex.sub('', text) | ||||
|  | ||||
|  | ||||
| def get_random_string(length): | ||||
|     """Returns a random ASCII string of the specified length).""" | ||||
|     return ''.join(random.choice(string.ascii_letters + string.digits) for _ in xrange(length)) | ||||
|  | ||||
|  | ||||
| class LoadSyntaxError(Exception): | ||||
|  | ||||
|     def __init__(self, message, filepath, lineno): | ||||
|         super(LoadSyntaxError, self).__init__(message) | ||||
|         self.filepath = filepath | ||||
|         self.lineno = lineno | ||||
|  | ||||
|     def __str__(self): | ||||
|         message = 'Syntax Error in {}, line {}:\n\t{}' | ||||
|         return message.format(self.filepath, self.lineno, self.message) | ||||
|  | ||||
|  | ||||
| RAND_MOD_NAME_LEN = 30 | ||||
| BAD_CHARS = string.punctuation + string.whitespace | ||||
| TRANS_TABLE = string.maketrans(BAD_CHARS, '_' * len(BAD_CHARS)) | ||||
|  | ||||
|  | ||||
| def to_identifier(text): | ||||
|     """Converts text to a valid Python identifier by replacing all | ||||
|     whitespace and punctuation.""" | ||||
|     return re.sub('_+', '_', text.translate(TRANS_TABLE)) | ||||
|  | ||||
|  | ||||
| def unique(alist): | ||||
|     """ | ||||
|     Returns a list containing only unique elements from the input list (but preserves | ||||
|     order, unlike sets). | ||||
|  | ||||
|     """ | ||||
|     result = [] | ||||
|     for item in alist: | ||||
|         if item not in result: | ||||
|             result.append(item) | ||||
|     return result | ||||
|  | ||||
|  | ||||
| def ranges_to_list(ranges_string): | ||||
|     """Converts a sysfs-style ranges string, e.g. ``"0,2-4"``, into a list ,e.g ``[0,2,3,4]``""" | ||||
|     values = [] | ||||
|     for rg in ranges_string.split(','): | ||||
|         if '-' in rg: | ||||
|             first, last = map(int, rg.split('-')) | ||||
|             values.extend(xrange(first, last + 1)) | ||||
|         else: | ||||
|             values.append(int(rg)) | ||||
|     return values | ||||
|  | ||||
|  | ||||
| def list_to_ranges(values): | ||||
|     """Converts a list, e.g ``[0,2,3,4]``, into a sysfs-style ranges string, e.g. ``"0,2-4"``""" | ||||
|     range_groups = [] | ||||
|     for _, g in groupby(enumerate(values), lambda (i, x): i - x): | ||||
|         range_groups.append(map(itemgetter(1), g)) | ||||
|     range_strings = [] | ||||
|     for group in range_groups: | ||||
|         if len(group) == 1: | ||||
|             range_strings.append(str(group[0])) | ||||
|         else: | ||||
|             range_strings.append('{}-{}'.format(group[0], group[-1])) | ||||
|     return ','.join(range_strings) | ||||
|  | ||||
|  | ||||
| def list_to_mask(values, base=0x0): | ||||
|     """Converts the specified list of integer values into | ||||
|     a bit mask for those values. Optinally, the list can be | ||||
|     applied to an existing mask.""" | ||||
|     for v in values: | ||||
|         base |= (1 << v) | ||||
|     return base | ||||
|  | ||||
|  | ||||
| def mask_to_list(mask): | ||||
|     """Converts the specfied integer bitmask into a list of | ||||
|     indexes of bits that are set in the mask.""" | ||||
|     size = len(bin(mask)) - 2  # because of "0b" | ||||
|     return [size - i - 1 for i in xrange(size) | ||||
|             if mask & (1 << size - i - 1)] | ||||
|  | ||||
|  | ||||
| __memo_cache = {} | ||||
|  | ||||
|  | ||||
| def memoized(func): | ||||
|     """A decorator for memoizing functions and methods.""" | ||||
|     func_id = repr(func) | ||||
|  | ||||
|     def memoize_wrapper(*args, **kwargs): | ||||
|         id_string = func_id + ','.join([str(id(a)) for a in  args]) | ||||
|         id_string += ','.join('{}={}'.format(k, v) | ||||
|                               for k, v in kwargs.iteritems()) | ||||
|         if id_string not in __memo_cache: | ||||
|             __memo_cache[id_string] = func(*args, **kwargs) | ||||
|         return __memo_cache[id_string] | ||||
|  | ||||
|     return memoize_wrapper | ||||
|  | ||||
							
								
								
									
										107
									
								
								devlib/utils/serial_port.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								devlib/utils/serial_port.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| import time | ||||
| from contextlib import contextmanager | ||||
| from logging import Logger | ||||
|  | ||||
| import serial | ||||
| import fdpexpect | ||||
| # Adding pexpect exceptions into this module's namespace | ||||
| from pexpect import EOF, TIMEOUT  # NOQA pylint: disable=W0611 | ||||
|  | ||||
| from devlib.exception import HostError | ||||
|  | ||||
|  | ||||
| def pulse_dtr(conn, state=True, duration=0.1): | ||||
|     """Set the DTR line of the specified serial connection to the specified state | ||||
|     for the specified duration (note: the initial state of the line is *not* checked.""" | ||||
|     conn.setDTR(state) | ||||
|     time.sleep(duration) | ||||
|     conn.setDTR(not state) | ||||
|  | ||||
|  | ||||
| def get_connection(timeout, init_dtr=None, logcls=Logger, | ||||
|                    *args, **kwargs): | ||||
|     if init_dtr is not None: | ||||
|         kwargs['dsrdtr'] = True | ||||
|     try: | ||||
|         conn = serial.Serial(*args, **kwargs) | ||||
|     except serial.SerialException as e: | ||||
|         raise HostError(e.message) | ||||
|     if init_dtr is not None: | ||||
|         conn.setDTR(init_dtr) | ||||
|     conn.nonblocking() | ||||
|     conn.flushOutput() | ||||
|     target = fdpexpect.fdspawn(conn.fileno(), timeout=timeout) | ||||
|     target.logfile_read = logcls('read') | ||||
|     target.logfile_send = logcls('send') | ||||
|  | ||||
|     # Monkey-patching sendline to introduce a short delay after | ||||
|     # chacters are sent to the serial. If two sendline s are issued | ||||
|     # one after another the second one might start putting characters | ||||
|     # into the serial device before the first one has finished, causing | ||||
|     # corruption. The delay prevents that. | ||||
|     tsln = target.sendline | ||||
|  | ||||
|     def sendline(x): | ||||
|         tsln(x) | ||||
|         time.sleep(0.1) | ||||
|  | ||||
|     target.sendline = sendline | ||||
|     return target, conn | ||||
|  | ||||
|  | ||||
| def write_characters(conn, line, delay=0.05): | ||||
|     """Write a single line out to serial charcter-by-character. This will ensure that nothing will | ||||
|     be dropped for longer lines.""" | ||||
|     line = line.rstrip('\r\n') | ||||
|     for c in line: | ||||
|         conn.send(c) | ||||
|         time.sleep(delay) | ||||
|     conn.sendline('') | ||||
|  | ||||
|  | ||||
| @contextmanager | ||||
| def open_serial_connection(timeout, get_conn=False, init_dtr=None, | ||||
|                            logcls=Logger, *args, **kwargs): | ||||
|     """ | ||||
|     Opens a serial connection to a device. | ||||
|  | ||||
|     :param timeout: timeout for the fdpexpect spawn object. | ||||
|     :param conn: ``bool`` that specfies whether the underlying connection | ||||
|                  object should be yielded as well. | ||||
|     :param init_dtr: specifies the initial DTR state stat should be set. | ||||
|  | ||||
|     All arguments are passed into the __init__ of serial.Serial. See | ||||
|     pyserial documentation for details: | ||||
|  | ||||
|         http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial | ||||
|  | ||||
|     :returns: a pexpect spawn object connected to the device. | ||||
|               See: http://pexpect.sourceforge.net/pexpect.html | ||||
|  | ||||
|     """ | ||||
|     target, conn = get_connection(timeout, init_dtr=init_dtr, | ||||
|                                   logcls=logcls, *args, **kwargs) | ||||
|     if get_conn: | ||||
|         yield target, conn | ||||
|     else: | ||||
|         yield target | ||||
|  | ||||
|     target.close()  # Closes the file descriptor used by the conn. | ||||
|     del conn | ||||
|  | ||||
							
								
								
									
										261
									
								
								devlib/utils/ssh.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								devlib/utils/ssh.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,261 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| import os | ||||
| import stat | ||||
| import logging | ||||
| import subprocess | ||||
| import re | ||||
| import threading | ||||
| import tempfile | ||||
| import shutil | ||||
|  | ||||
| import pxssh | ||||
| from pexpect import EOF, TIMEOUT, spawn | ||||
|  | ||||
| from devlib.exception import HostError, TargetError, TimeoutError | ||||
| from devlib.utils.misc import which, strip_bash_colors, escape_single_quotes, check_output | ||||
|  | ||||
|  | ||||
| ssh = None | ||||
| scp = None | ||||
| sshpass = None | ||||
|  | ||||
| logger = logging.getLogger('ssh') | ||||
|  | ||||
|  | ||||
| def ssh_get_shell(host, username, password=None, keyfile=None, port=None, timeout=10, telnet=False): | ||||
|     _check_env() | ||||
|     if telnet: | ||||
|         if keyfile: | ||||
|             raise ValueError('keyfile may not be used with a telnet connection.') | ||||
|         conn = TelnetConnection() | ||||
|     else:  # ssh | ||||
|         conn = pxssh.pxssh() | ||||
|     try: | ||||
|         if keyfile: | ||||
|             conn.login(host, username, ssh_key=keyfile, port=port, login_timeout=timeout) | ||||
|         else: | ||||
|             conn.login(host, username, password, port=port, login_timeout=timeout) | ||||
|     except EOF: | ||||
|         raise TargetError('Could not connect to {}; is the host name correct?'.format(host)) | ||||
|     return conn | ||||
|  | ||||
|  | ||||
| class TelnetConnection(pxssh.pxssh): | ||||
|     # pylint: disable=arguments-differ | ||||
|  | ||||
|     def login(self, server, username, password='', original_prompt=r'[#$]', login_timeout=10, | ||||
|               auto_prompt_reset=True, sync_multiplier=1): | ||||
|         cmd = 'telnet -l {} {}'.format(username, server) | ||||
|  | ||||
|         spawn._spawn(self, cmd)  # pylint: disable=protected-access | ||||
|         i = self.expect('(?i)(?:password)', timeout=login_timeout) | ||||
|         if i == 0: | ||||
|             self.sendline(password) | ||||
|             i = self.expect([original_prompt, 'Login incorrect'], timeout=login_timeout) | ||||
|         else: | ||||
|             raise pxssh.ExceptionPxssh('could not log in: did not see a password prompt') | ||||
|  | ||||
|         if i: | ||||
|             raise pxssh.ExceptionPxssh('could not log in: password was incorrect') | ||||
|  | ||||
|         if not self.sync_original_prompt(sync_multiplier): | ||||
|             self.close() | ||||
|             raise pxssh.ExceptionPxssh('could not synchronize with original prompt') | ||||
|  | ||||
|         if auto_prompt_reset: | ||||
|             if not self.set_unique_prompt(): | ||||
|                 self.close() | ||||
|                 message = 'could not set shell prompt (recieved: {}, expected: {}).' | ||||
|                 raise pxssh.ExceptionPxssh(message.format(self.before, self.PROMPT)) | ||||
|         return True | ||||
|  | ||||
|  | ||||
| def check_keyfile(keyfile): | ||||
|     """ | ||||
|     keyfile must have the right access premissions in order to be useable. If the specified | ||||
|     file doesn't, create a temporary copy and set the right permissions for that. | ||||
|  | ||||
|     Returns either the ``keyfile`` (if the permissions on it are correct) or the path to a | ||||
|     temporary copy with the right permissions. | ||||
|     """ | ||||
|     desired_mask = stat.S_IWUSR | stat.S_IRUSR | ||||
|     actual_mask = os.stat(keyfile).st_mode & 0xFF | ||||
|     if actual_mask != desired_mask: | ||||
|         tmp_file = os.path.join(tempfile.gettempdir(), os.path.basename(keyfile)) | ||||
|         shutil.copy(keyfile, tmp_file) | ||||
|         os.chmod(tmp_file, desired_mask) | ||||
|         return tmp_file | ||||
|     else:  # permissions on keyfile are OK | ||||
|         return keyfile | ||||
|  | ||||
|  | ||||
| class SshConnection(object): | ||||
|  | ||||
|     default_password_prompt = '[sudo] password' | ||||
|     max_cancel_attempts = 5 | ||||
|  | ||||
|     @property | ||||
|     def name(self): | ||||
|         return self.host | ||||
|  | ||||
|     def __init__(self, | ||||
|                  host, | ||||
|                  username, | ||||
|                  password=None, | ||||
|                  keyfile=None, | ||||
|                  port=None, | ||||
|                  timeout=10, | ||||
|                  telnet=False, | ||||
|                  password_prompt=None, | ||||
|                  ): | ||||
|         self.host = host | ||||
|         self.username = username | ||||
|         self.password = password | ||||
|         self.keyfile = check_keyfile(keyfile) if keyfile else keyfile | ||||
|         self.port = port | ||||
|         self.lock = threading.Lock() | ||||
|         self.password_prompt = password_prompt if password_prompt is not None else self.default_password_prompt | ||||
|         logger.debug('Logging in {}@{}'.format(username, host)) | ||||
|         self.conn = ssh_get_shell(host, username, password, self.keyfile, port, timeout, telnet) | ||||
|  | ||||
|     def push(self, source, dest, timeout=30): | ||||
|         dest = '{}@{}:{}'.format(self.username, self.host, dest) | ||||
|         return self._scp(source, dest, timeout) | ||||
|  | ||||
|     def pull(self, source, dest, timeout=30): | ||||
|         source = '{}@{}:{}'.format(self.username, self.host, source) | ||||
|         return self._scp(source, dest, timeout) | ||||
|  | ||||
|     def execute(self, command, timeout=None, check_exit_code=True, as_root=False, strip_colors=True): | ||||
|         with self.lock: | ||||
|             output = self._execute_and_wait_for_prompt(command, timeout, as_root, strip_colors) | ||||
|             if check_exit_code: | ||||
|                 exit_code_text = self._execute_and_wait_for_prompt('echo $?', strip_colors=strip_colors, log=False) | ||||
|                 try: | ||||
|                     exit_code = int(exit_code_text.split()[0]) | ||||
|                     if exit_code: | ||||
|                         message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}' | ||||
|                         raise TargetError(message.format(exit_code, command, output)) | ||||
|                 except (ValueError, IndexError): | ||||
|                     logger.warning('Could not get exit code for "{}",\ngot: "{}"'.format(command, exit_code_text)) | ||||
|             return output | ||||
|  | ||||
|     def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE): | ||||
|         port_string = '-p {}'.format(self.port) if self.port else '' | ||||
|         keyfile_string = '-i {}'.format(self.keyfile) if self.keyfile else '' | ||||
|         command = '{} {} {} {}@{} {}'.format(ssh, keyfile_string, port_string, self.username, self.host, command) | ||||
|         logger.debug(command) | ||||
|         if self.password: | ||||
|             command = _give_password(self.password, command) | ||||
|         return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True) | ||||
|  | ||||
|     def close(self): | ||||
|         logger.debug('Logging out {}@{}'.format(self.username, self.host)) | ||||
|         self.conn.logout() | ||||
|  | ||||
|     def cancel_running_command(self): | ||||
|         # simulate impatiently hitting ^C until command prompt appears | ||||
|         logger.debug('Sending ^C') | ||||
|         for _ in xrange(self.max_cancel_attempts): | ||||
|             self.conn.sendline(chr(3)) | ||||
|             if self.conn.prompt(0.1): | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
|     def _execute_and_wait_for_prompt(self, command, timeout=None, as_root=False, strip_colors=True, log=True): | ||||
|         self.conn.prompt(0.1)  # clear an existing prompt if there is one. | ||||
|         if as_root: | ||||
|             command = "sudo -- sh -c '{}'".format(escape_single_quotes(command)) | ||||
|             if log: | ||||
|                 logger.debug(command) | ||||
|             self.conn.sendline(command) | ||||
|             if self.password: | ||||
|                 index = self.conn.expect_exact([self.password_prompt, TIMEOUT], timeout=0.5) | ||||
|                 if index == 0: | ||||
|                     self.conn.sendline(self.password) | ||||
|         else:  # not as_root | ||||
|             if log: | ||||
|                 logger.debug(command) | ||||
|             self.conn.sendline(command) | ||||
|         timed_out = self._wait_for_prompt(timeout) | ||||
|         # the regex removes line breaks potential introduced when writing | ||||
|         # command to shell. | ||||
|         output = process_backspaces(self.conn.before) | ||||
|         output = re.sub(r'\r([^\n])', r'\1', output) | ||||
|         if '\r\n' in output: # strip the echoed command | ||||
|             output = output.split('\r\n', 1)[1] | ||||
|         if timed_out: | ||||
|             self.cancel_running_command() | ||||
|             raise TimeoutError(command, output) | ||||
|         if strip_colors: | ||||
|             output = strip_bash_colors(output) | ||||
|         return output | ||||
|  | ||||
|     def _wait_for_prompt(self, timeout=None): | ||||
|         if timeout: | ||||
|             return not self.conn.prompt(timeout) | ||||
|         else:  # cannot timeout; wait forever | ||||
|             while not self.conn.prompt(1): | ||||
|                 pass | ||||
|             return False | ||||
|  | ||||
|     def _scp(self, source, dest, timeout=30): | ||||
|         # NOTE: the version of scp in Ubuntu 12.04 occasionally (and bizarrely) | ||||
|         # fails to connect to a device if port is explicitly specified using -P | ||||
|         # option, even if it is the default port, 22. To minimize this problem, | ||||
|         # only specify -P for scp if the port is *not* the default. | ||||
|         port_string = '-P {}'.format(self.port) if (self.port and self.port != 22) else '' | ||||
|         keyfile_string = '-i {}'.format(self.keyfile) if self.keyfile else '' | ||||
|         command = '{} -r {} {} {} {}'.format(scp, keyfile_string, port_string, source, dest) | ||||
|         pass_string = '' | ||||
|         logger.debug(command) | ||||
|         if self.password: | ||||
|             command = _give_password(self.password, command) | ||||
|         try: | ||||
|             check_output(command, timeout=timeout, shell=True) | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             raise subprocess.CalledProcessError(e.returncode, e.cmd.replace(pass_string, ''), e.output) | ||||
|         except TimeoutError as e: | ||||
|             raise TimeoutError(e.command.replace(pass_string, ''), e.output) | ||||
|  | ||||
|  | ||||
| def _give_password(password, command): | ||||
|     if not sshpass: | ||||
|         raise HostError('Must have sshpass installed on the host in order to use password-based auth.') | ||||
|     pass_string = "sshpass -p '{}' ".format(password) | ||||
|     return pass_string + command | ||||
|  | ||||
|  | ||||
| def _check_env(): | ||||
|     global ssh, scp, sshpass  # pylint: disable=global-statement | ||||
|     if not ssh: | ||||
|         ssh = which('ssh') | ||||
|         scp = which('scp') | ||||
|         sshpass = which('sshpass') | ||||
|     if not (ssh and scp): | ||||
|         raise HostError('OpenSSH must be installed on the host.') | ||||
|  | ||||
|  | ||||
| def process_backspaces(text): | ||||
|     chars = [] | ||||
|     for c in text: | ||||
|         if c == chr(8) and chars:  # backspace | ||||
|             chars.pop() | ||||
|         else: | ||||
|             chars.append(c) | ||||
|     return ''.join(chars) | ||||
							
								
								
									
										113
									
								
								devlib/utils/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								devlib/utils/types.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| """ | ||||
| Routines for doing various type conversions. These usually embody some higher-level | ||||
| semantics than are present in standard Python types (e.g. ``boolean`` will convert the | ||||
| string ``"false"`` to ``False``, where as non-empty strings are usually considered to be | ||||
| ``True``). | ||||
|  | ||||
| A lot of these are intened to stpecify type conversions declaratively in place like | ||||
| ``Parameter``'s ``kind`` argument. These are basically "hacks" around the fact that Python | ||||
| is not the best language to use for configuration. | ||||
|  | ||||
| """ | ||||
| import math | ||||
|  | ||||
| from devlib.utils.misc import isiterable, to_identifier, ranges_to_list, list_to_mask | ||||
|  | ||||
|  | ||||
| def identifier(text): | ||||
|     """Converts text to a valid Python identifier by replacing all | ||||
|     whitespace and punctuation.""" | ||||
|     return to_identifier(text) | ||||
|  | ||||
|  | ||||
| def boolean(value): | ||||
|     """ | ||||
|     Returns bool represented by the value. This is different from | ||||
|     calling the builtin bool() in that it will interpret string representations. | ||||
|     e.g. boolean('0') and boolean('false') will both yield False. | ||||
|  | ||||
|     """ | ||||
|     false_strings = ['', '0', 'n', 'no', 'off'] | ||||
|     if isinstance(value, basestring): | ||||
|         value = value.lower() | ||||
|         if value in false_strings or 'false'.startswith(value): | ||||
|             return False | ||||
|     return bool(value) | ||||
|  | ||||
|  | ||||
| def integer(value): | ||||
|     """Handles conversions for string respresentations of binary, octal and hex.""" | ||||
|     if isinstance(value, basestring): | ||||
|         return int(value, 0) | ||||
|     else: | ||||
|         return int(value) | ||||
|  | ||||
|  | ||||
| def numeric(value): | ||||
|     """ | ||||
|     Returns the value as number (int if possible, or float otherwise), or | ||||
|     raises ``ValueError`` if the specified ``value`` does not have a straight | ||||
|     forward numeric conversion. | ||||
|  | ||||
|     """ | ||||
|     if isinstance(value, int): | ||||
|         return value | ||||
|     try: | ||||
|         fvalue = float(value) | ||||
|     except ValueError: | ||||
|         raise ValueError('Not numeric: {}'.format(value)) | ||||
|     if not math.isnan(fvalue) and not math.isinf(fvalue): | ||||
|         ivalue = int(fvalue) | ||||
|         if ivalue == fvalue:  # yeah, yeah, I know. Whatever. This is best-effort. | ||||
|             return ivalue | ||||
|     return fvalue | ||||
|  | ||||
|  | ||||
| class caseless_string(str): | ||||
|     """ | ||||
|     Just like built-in Python string except case-insensitive on comparisons. However, the | ||||
|     case is preserved otherwise. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         if isinstance(other, basestring): | ||||
|             other = other.lower() | ||||
|         return self.lower() == other | ||||
|  | ||||
|     def __ne__(self, other): | ||||
|         return not self.__eq__(other) | ||||
|  | ||||
|     def __cmp__(self, other): | ||||
|         if isinstance(basestring, other): | ||||
|             other = other.lower() | ||||
|         return cmp(self.lower(), other) | ||||
|  | ||||
|     def format(self, *args, **kwargs): | ||||
|         return caseless_string(super(caseless_string, self).format(*args, **kwargs)) | ||||
|  | ||||
|  | ||||
| def bitmask(value): | ||||
|     if isinstance(value, basestring): | ||||
|         value = ranges_to_list(value) | ||||
|     if isiterable(value): | ||||
|         value = list_to_mask(value) | ||||
|     if not isinstance(value, int): | ||||
|         raise ValueError(value) | ||||
|     return value | ||||
							
								
								
									
										116
									
								
								devlib/utils/uboot.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								devlib/utils/uboot.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| # | ||||
| #    Copyright 2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| import re | ||||
| import time | ||||
| import logging | ||||
|  | ||||
| from devlib.utils.serial_port import TIMEOUT | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger('U-Boot') | ||||
|  | ||||
|  | ||||
| class UbootMenu(object): | ||||
|     """ | ||||
|     Allows navigating Das U-boot menu over serial (it relies on a pexpect connection). | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     option_regex = re.compile(r'^\[(\d+)\]\s+([^\r]+)\r\n', re.M) | ||||
|     prompt_regex = re.compile(r'^([^\r\n]+):\s*', re.M) | ||||
|     invalid_regex = re.compile(r'Invalid input \(max (\d+)\)', re.M) | ||||
|  | ||||
|     load_delay = 1  # seconds | ||||
|     default_timeout = 60  # seconds | ||||
|  | ||||
|     def __init__(self, conn, start_prompt='Hit any key to stop autoboot'): | ||||
|         """ | ||||
|         :param conn: A serial connection as returned by ``pexect.spawn()``. | ||||
|         :param prompt: U-Boot menu prompt | ||||
|         :param start_prompt: The starting prompt to wait for during ``open()``. | ||||
|  | ||||
|         """ | ||||
|         self.conn = conn | ||||
|         self.conn.crlf = '\n\r'  # TODO: this has *got* to be a bug in U-Boot... | ||||
|         self.start_prompt = start_prompt | ||||
|         self.options = {} | ||||
|         self.prompt = None | ||||
|  | ||||
|     def open(self, timeout=default_timeout): | ||||
|         """ | ||||
|         "Open" the UEFI menu by sending an interrupt on STDIN after seeing the | ||||
|         starting prompt (configurable upon creation of the ``UefiMenu`` object. | ||||
|  | ||||
|         """ | ||||
|         self.conn.expect(self.start_prompt, timeout) | ||||
|         self.conn.sendline('') | ||||
|         time.sleep(self.load_delay) | ||||
|         self.conn.readline()  # garbage | ||||
|         self.conn.sendline('') | ||||
|         self.prompt = self.conn.readline().strip() | ||||
|  | ||||
|     def getenv(self): | ||||
|         output = self.enter('printenv') | ||||
|         result = {} | ||||
|         for line in output.split('\n'): | ||||
|             if '=' in line: | ||||
|                 variable, value = line.split('=', 1) | ||||
|                 result[variable.strip()] = value.strip() | ||||
|         return result | ||||
|  | ||||
|     def setenv(self, variable, value, force=False): | ||||
|         force_str = ' -f' if force else '' | ||||
|         if value is not None: | ||||
|             command = 'setenv{} {} {}'.format(force_str, variable, value) | ||||
|         else: | ||||
|             command = 'setenv{} {}'.format(force_str, variable) | ||||
|         return self.enter(command) | ||||
|  | ||||
|     def boot(self): | ||||
|         self.write_characters('boot') | ||||
|  | ||||
|     def nudge(self): | ||||
|         """Send a little nudge to ensure there is something to read. This is useful when you're not | ||||
|         sure if all out put from the serial has been read already.""" | ||||
|         self.enter('') | ||||
|  | ||||
|     def enter(self, value, delay=load_delay): | ||||
|         """Like ``select()`` except no resolution is performed -- the value is sent directly | ||||
|         to the serial connection.""" | ||||
|         # Empty the buffer first, so that only response to the input about to | ||||
|         # be sent will be processed by subsequent commands. | ||||
|         value = str(value) | ||||
|         self.empty_buffer() | ||||
|         self.write_characters(value) | ||||
|         self.conn.expect(self.prompt, timeout=delay) | ||||
|         return self.conn.before | ||||
|  | ||||
|     def write_characters(self, line): | ||||
|         line = line.rstrip('\r\n') | ||||
|         for c in line: | ||||
|             self.conn.send(c) | ||||
|             time.sleep(0.05) | ||||
|         self.conn.sendline('') | ||||
|  | ||||
|     def empty_buffer(self): | ||||
|         try: | ||||
|             while True: | ||||
|                 time.sleep(0.1) | ||||
|                 self.conn.read_nonblocking(size=1024, timeout=0.1) | ||||
|         except TIMEOUT: | ||||
|             pass | ||||
|         self.conn.buffer = '' | ||||
|  | ||||
							
								
								
									
										239
									
								
								devlib/utils/uefi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								devlib/utils/uefi.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| import re | ||||
| import time | ||||
| import logging | ||||
| from copy import copy | ||||
|  | ||||
| from devlib.utils.serial_port import write_characters, TIMEOUT | ||||
| from devlib.utils.types import boolean | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger('UEFI') | ||||
|  | ||||
|  | ||||
| class UefiConfig(object): | ||||
|  | ||||
|     def __init__(self, config_dict): | ||||
|         if isinstance(config_dict, UefiConfig): | ||||
|             self.__dict__ = copy(config_dict.__dict__) | ||||
|         else: | ||||
|             try: | ||||
|                 self.image_name = config_dict['image_name'] | ||||
|                 self.image_args = config_dict['image_args'] | ||||
|                 self.fdt_support = boolean(config_dict['fdt_support']) | ||||
|             except KeyError as e: | ||||
|                 raise ValueError('Missing mandatory parameter for UEFI entry config: "{}"'.format(e)) | ||||
|             self.initrd = config_dict.get('initrd') | ||||
|             self.fdt_path = config_dict.get('fdt_path') | ||||
|             if self.fdt_path and not self.fdt_support: | ||||
|                 raise ValueError('FDT path has been specfied for UEFI entry, when FDT support is "False"') | ||||
|  | ||||
|  | ||||
| class UefiMenu(object): | ||||
|     """ | ||||
|     Allows navigating UEFI menu over serial (it relies on a pexpect connection). | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     option_regex = re.compile(r'^\[(\d+)\]\s+([^\r]+)\r\n', re.M) | ||||
|     prompt_regex = re.compile(r'^(\S[^\r\n]+):\s*', re.M) | ||||
|     invalid_regex = re.compile(r'Invalid input \(max (\d+)\)', re.M) | ||||
|  | ||||
|     load_delay = 1  # seconds | ||||
|     default_timeout = 60  # seconds | ||||
|  | ||||
|     def __init__(self, conn, prompt='The default boot selection will start in'): | ||||
|         """ | ||||
|         :param conn: A serial connection as returned by ``pexect.spawn()``. | ||||
|         :param prompt: The starting prompt to wait for during ``open()``. | ||||
|  | ||||
|         """ | ||||
|         self.conn = conn | ||||
|         self.start_prompt = prompt | ||||
|         self.options = {} | ||||
|         self.prompt = None | ||||
|         self.attempting_invalid_retry = False | ||||
|  | ||||
|     def wait(self, timeout=default_timeout): | ||||
|         """ | ||||
|         "Open" the UEFI menu by sending an interrupt on STDIN after seeing the | ||||
|         starting prompt (configurable upon creation of the ``UefiMenu`` object. | ||||
|  | ||||
|         """ | ||||
|         self.conn.expect(self.start_prompt, timeout) | ||||
|         self.connect() | ||||
|  | ||||
|     def connect(self, timeout=default_timeout): | ||||
|         self.nudge() | ||||
|         time.sleep(self.load_delay) | ||||
|         self.read_menu(timeout=timeout) | ||||
|  | ||||
|     def create_entry(self, name, config): | ||||
|         """Create a new UEFI entry using the parameters. The menu is assumed | ||||
|         to be at the top level. Upon return, the menu will be at the top level.""" | ||||
|         logger.debug('Creating UEFI entry {}'.format(name)) | ||||
|         self.nudge() | ||||
|         self.select('Boot Manager') | ||||
|         self.select('Add Boot Device Entry') | ||||
|         self.select('NOR Flash') | ||||
|         self.enter(config.image_name) | ||||
|         self.enter('y' if config.fdt_support else 'n') | ||||
|         if config.initrd: | ||||
|             self.enter('y') | ||||
|             self.enter(config.initrd) | ||||
|         else: | ||||
|             self.enter('n') | ||||
|         self.enter(config.image_args) | ||||
|         self.enter(name) | ||||
|  | ||||
|         if config.fdt_path: | ||||
|             self.select('Update FDT path') | ||||
|             self.enter(config.fdt_path) | ||||
|  | ||||
|         self.select('Return to main menu') | ||||
|  | ||||
|     def delete_entry(self, name): | ||||
|         """Delete the specified UEFI entry. The menu is assumed | ||||
|         to be at the top level. Upon return, the menu will be at the top level.""" | ||||
|         logger.debug('Removing UEFI entry {}'.format(name)) | ||||
|         self.nudge() | ||||
|         self.select('Boot Manager') | ||||
|         self.select('Remove Boot Device Entry') | ||||
|         self.select(name) | ||||
|         self.select('Return to main menu') | ||||
|  | ||||
|     def select(self, option, timeout=default_timeout): | ||||
|         """ | ||||
|         Select the specified option from the current menu. | ||||
|  | ||||
|         :param option: Could be an ``int`` index of the option, or a string/regex to | ||||
|                        match option text against. | ||||
|         :param timeout: If a non-``int`` option is specified, the option list may need | ||||
|                         need to be parsed (if it hasn't been already), this may block | ||||
|                         and the timeout is used to cap that , resulting in a ``TIMEOUT`` | ||||
|                         exception. | ||||
|         :param delay: A fixed delay to wait after sending the input to the serial connection. | ||||
|                       This should be set if input this action is known to result in a | ||||
|                       long-running operation. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(option, basestring): | ||||
|             option = self.get_option_index(option, timeout) | ||||
|         self.enter(option) | ||||
|  | ||||
|     def enter(self, value, delay=load_delay): | ||||
|         """Like ``select()`` except no resolution is performed -- the value is sent directly | ||||
|         to the serial connection.""" | ||||
|         # Empty the buffer first, so that only response to the input about to | ||||
|         # be sent will be processed by subsequent commands. | ||||
|         value = str(value) | ||||
|         self._reset() | ||||
|         write_characters(self.conn, value) | ||||
|         # TODO: in case the value is long an complicated, things may get | ||||
|         # screwed up (e.g. there may be line breaks injected), additionally, | ||||
|         # special chars might cause regex to fail. To avoid these issues i'm | ||||
|         # only matching against the first 5 chars of the value. This is | ||||
|         # entirely arbitrary and I'll probably have to find a better way of | ||||
|         # doing this at some point. | ||||
|         self.conn.expect(value[:5], timeout=delay) | ||||
|         time.sleep(self.load_delay) | ||||
|  | ||||
|     def read_menu(self, timeout=default_timeout): | ||||
|         """Parse serial output to get the menu options and the following prompt.""" | ||||
|         attempting_timeout_retry = False | ||||
|         self.attempting_invalid_retry = False | ||||
|         while True: | ||||
|             index = self.conn.expect([self.option_regex, self.prompt_regex, self.invalid_regex, TIMEOUT], | ||||
|                                      timeout=timeout) | ||||
|             match = self.conn.match | ||||
|             if index == 0:  # matched menu option | ||||
|                 self.options[match.group(1)] = match.group(2) | ||||
|             elif index == 1:  # matched prompt | ||||
|                 self.prompt = match.group(1) | ||||
|                 break | ||||
|             elif index == 2:  # matched invalid selection | ||||
|                 # We've sent an invalid input (which includes an empty line) at | ||||
|                 # the top-level menu. To get back the menu options, it seems we | ||||
|                 # need to enter what the error reports as the max + 1, so... | ||||
|                 if not self.attempting_invalid_retry: | ||||
|                     self.attempting_invalid_retry = True | ||||
|                     val = int(match.group(1)) | ||||
|                     self.empty_buffer() | ||||
|                     self.enter(val) | ||||
|                     self.select('Return to main menu') | ||||
|                     self.attempting_invalid_retry = False | ||||
|                 else:   # OK, that didn't work; panic! | ||||
|                     raise RuntimeError('Could not read menu entries stuck on "{}" prompt'.format(self.prompt)) | ||||
|             elif index == 3:  # timed out | ||||
|                 if not attempting_timeout_retry: | ||||
|                     attempting_timeout_retry = True | ||||
|                     self.nudge() | ||||
|                 else:  # Didn't help. Run away! | ||||
|                     raise RuntimeError('Did not see a valid UEFI menu.') | ||||
|             else: | ||||
|                 raise AssertionError('Unexpected response waiting for UEFI menu')  # should never get here | ||||
|  | ||||
|     def list_options(self, timeout=default_timeout): | ||||
|         """Returns the menu index of the specified option text (uses regex matching). If the option | ||||
|         is not in the current menu, ``LookupError`` will be raised.""" | ||||
|         if not self.prompt: | ||||
|             self.read_menu(timeout) | ||||
|         return self.options.items() | ||||
|  | ||||
|     def get_option_index(self, text, timeout=default_timeout): | ||||
|         """Returns the menu index of the specified option text (uses regex matching). If the option | ||||
|         is not in the current menu, ``LookupError`` will be raised.""" | ||||
|         if not self.prompt: | ||||
|             self.read_menu(timeout) | ||||
|         for k, v in self.options.iteritems(): | ||||
|             if re.search(text, v): | ||||
|                 return k | ||||
|         raise LookupError(text) | ||||
|  | ||||
|     def has_option(self, text, timeout=default_timeout): | ||||
|         """Returns ``True`` if at least one of the options in the current menu has | ||||
|         matched (using regex) the specified text.""" | ||||
|         try: | ||||
|             self.get_option_index(text, timeout) | ||||
|             return True | ||||
|         except LookupError: | ||||
|             return False | ||||
|  | ||||
|     def nudge(self): | ||||
|         """Send a little nudge to ensure there is something to read. This is useful when you're not | ||||
|         sure if all out put from the serial has been read already.""" | ||||
|         self.enter('') | ||||
|  | ||||
|     def empty_buffer(self): | ||||
|         """Read everything from the serial and clear the internal pexpect buffer. This ensures | ||||
|         that the next ``expect()`` call will time out (unless further input will be sent to the | ||||
|         serial beforehand. This is used to create a "known" state and avoid unexpected matches.""" | ||||
|         try: | ||||
|             while True: | ||||
|                 time.sleep(0.1) | ||||
|                 self.conn.read_nonblocking(size=1024, timeout=0.1) | ||||
|         except TIMEOUT: | ||||
|             pass | ||||
|         self.conn.buffer = '' | ||||
|  | ||||
|     def _reset(self): | ||||
|         self.options = {} | ||||
|         self.prompt = None | ||||
|         self.empty_buffer() | ||||
|  | ||||
|  | ||||
							
								
								
									
										192
									
								
								doc/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								doc/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| # Makefile for Sphinx documentation | ||||
| # | ||||
|  | ||||
| # You can set these variables from the command line. | ||||
| SPHINXOPTS    = | ||||
| SPHINXBUILD   = sphinx-build | ||||
| PAPER         = | ||||
| BUILDDIR      = _build | ||||
|  | ||||
| # User-friendly check for sphinx-build | ||||
| ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) | ||||
| $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) | ||||
| endif | ||||
|  | ||||
| # Internal variables. | ||||
| PAPEROPT_a4     = -D latex_paper_size=a4 | ||||
| PAPEROPT_letter = -D latex_paper_size=letter | ||||
| 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 html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext | ||||
|  | ||||
| help: | ||||
| 	@echo "Please use \`make <target>' where <target> is one of" | ||||
| 	@echo "  html       to make standalone HTML files" | ||||
| 	@echo "  dirhtml    to make HTML files named index.html in directories" | ||||
| 	@echo "  singlehtml to make a single large HTML file" | ||||
| 	@echo "  pickle     to make pickle files" | ||||
| 	@echo "  json       to make JSON files" | ||||
| 	@echo "  htmlhelp   to make HTML files and a HTML help project" | ||||
| 	@echo "  qthelp     to make HTML files and a qthelp project" | ||||
| 	@echo "  applehelp  to make an Apple Help Book" | ||||
| 	@echo "  devhelp    to make HTML files and a Devhelp project" | ||||
| 	@echo "  epub       to make an epub" | ||||
| 	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | ||||
| 	@echo "  latexpdf   to make LaTeX files and run them through pdflatex" | ||||
| 	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx" | ||||
| 	@echo "  text       to make text files" | ||||
| 	@echo "  man        to make manual pages" | ||||
| 	@echo "  texinfo    to make Texinfo files" | ||||
| 	@echo "  info       to make Texinfo files and run them through makeinfo" | ||||
| 	@echo "  gettext    to make PO message catalogs" | ||||
| 	@echo "  changes    to make an overview of all changed/added/deprecated items" | ||||
| 	@echo "  xml        to make Docutils-native XML files" | ||||
| 	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes" | ||||
| 	@echo "  linkcheck  to check all external links for integrity" | ||||
| 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)" | ||||
| 	@echo "  coverage   to run coverage check of the documentation (if enabled)" | ||||
|  | ||||
| clean: | ||||
| 	rm -rf $(BUILDDIR)/* | ||||
|  | ||||
| html: | ||||
| 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | ||||
|  | ||||
| dirhtml: | ||||
| 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | ||||
|  | ||||
| singlehtml: | ||||
| 	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | ||||
|  | ||||
| pickle: | ||||
| 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can process the pickle files." | ||||
|  | ||||
| json: | ||||
| 	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can process the JSON files." | ||||
|  | ||||
| htmlhelp: | ||||
| 	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can run HTML Help Workshop with the" \ | ||||
| 	      ".hhp project file in $(BUILDDIR)/htmlhelp." | ||||
|  | ||||
| qthelp: | ||||
| 	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \ | ||||
| 	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:" | ||||
| 	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/devlib.qhcp" | ||||
| 	@echo "To view the help file:" | ||||
| 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/devlib.qhc" | ||||
|  | ||||
| applehelp: | ||||
| 	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp | ||||
| 	@echo | ||||
| 	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp." | ||||
| 	@echo "N.B. You won't be able to view it unless you put it in" \ | ||||
| 	      "~/Library/Documentation/Help or install it in your application" \ | ||||
| 	      "bundle." | ||||
|  | ||||
| devhelp: | ||||
| 	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | ||||
| 	@echo | ||||
| 	@echo "Build finished." | ||||
| 	@echo "To view the help file:" | ||||
| 	@echo "# mkdir -p $$HOME/.local/share/devhelp/devlib" | ||||
| 	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/devlib" | ||||
| 	@echo "# devhelp" | ||||
|  | ||||
| epub: | ||||
| 	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | ||||
| 	@echo | ||||
| 	@echo "Build finished. The epub file is in $(BUILDDIR)/epub." | ||||
|  | ||||
| latex: | ||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||
| 	@echo | ||||
| 	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | ||||
| 	@echo "Run \`make' in that directory to run these through (pdf)latex" \ | ||||
| 	      "(use \`make latexpdf' here to do that automatically)." | ||||
|  | ||||
| latexpdf: | ||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||
| 	@echo "Running LaTeX files through pdflatex..." | ||||
| 	$(MAKE) -C $(BUILDDIR)/latex all-pdf | ||||
| 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | ||||
|  | ||||
| latexpdfja: | ||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||
| 	@echo "Running LaTeX files through platex and dvipdfmx..." | ||||
| 	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja | ||||
| 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | ||||
|  | ||||
| text: | ||||
| 	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | ||||
| 	@echo | ||||
| 	@echo "Build finished. The text files are in $(BUILDDIR)/text." | ||||
|  | ||||
| man: | ||||
| 	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | ||||
| 	@echo | ||||
| 	@echo "Build finished. The manual pages are in $(BUILDDIR)/man." | ||||
|  | ||||
| texinfo: | ||||
| 	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | ||||
| 	@echo | ||||
| 	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." | ||||
| 	@echo "Run \`make' in that directory to run these through makeinfo" \ | ||||
| 	      "(use \`make info' here to do that automatically)." | ||||
|  | ||||
| info: | ||||
| 	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | ||||
| 	@echo "Running Texinfo files through makeinfo..." | ||||
| 	make -C $(BUILDDIR)/texinfo info | ||||
| 	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." | ||||
|  | ||||
| gettext: | ||||
| 	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale | ||||
| 	@echo | ||||
| 	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." | ||||
|  | ||||
| changes: | ||||
| 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | ||||
| 	@echo | ||||
| 	@echo "The overview file is in $(BUILDDIR)/changes." | ||||
|  | ||||
| linkcheck: | ||||
| 	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | ||||
| 	@echo | ||||
| 	@echo "Link check complete; look for any errors in the above output " \ | ||||
| 	      "or in $(BUILDDIR)/linkcheck/output.txt." | ||||
|  | ||||
| doctest: | ||||
| 	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | ||||
| 	@echo "Testing of doctests in the sources finished, look at the " \ | ||||
| 	      "results in $(BUILDDIR)/doctest/output.txt." | ||||
|  | ||||
| coverage: | ||||
| 	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage | ||||
| 	@echo "Testing of coverage in the sources finished, look at the " \ | ||||
| 	      "results in $(BUILDDIR)/coverage/python.txt." | ||||
|  | ||||
| xml: | ||||
| 	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The XML files are in $(BUILDDIR)/xml." | ||||
|  | ||||
| pseudoxml: | ||||
| 	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." | ||||
							
								
								
									
										287
									
								
								doc/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								doc/conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # devlib documentation build configuration file, created by | ||||
| # sphinx-quickstart on Tue Aug 11 17:37:27 2015. | ||||
| # | ||||
| # 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 | ||||
| import os | ||||
| import shlex | ||||
|  | ||||
| # 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', | ||||
|     'sphinx.ext.viewcode', | ||||
| ] | ||||
|  | ||||
| # Add any paths that contain templates here, relative to this directory. | ||||
| templates_path = ['static/templates'] | ||||
|  | ||||
| # The suffix(es) of source filenames. | ||||
| # You can specify multiple suffix as a list of string: | ||||
| # source_suffix = ['.rst', '.md'] | ||||
| 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'devlib' | ||||
| copyright = u'2015, ARM Limited' | ||||
| author = u'ARM Limited' | ||||
|  | ||||
| # 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. | ||||
| # | ||||
| # This is also used if you do content translation via gettext catalogs. | ||||
| # Usually you set "language" from the command line for these cases. | ||||
| 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 = [] | ||||
|  | ||||
| # If true, keep warnings as "system message" paragraphs in the built documents. | ||||
| #keep_warnings = False | ||||
|  | ||||
| # If true, `todo` and `todoList` produce output, else they produce nothing. | ||||
| todo_include_todos = False | ||||
|  | ||||
|  | ||||
| # -- 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 = 'sphinx_rtd_theme' | ||||
|  | ||||
| # 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 = {} | ||||
|  | ||||
| # 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 | ||||
| # "<project> v<release> documentation". | ||||
| #html_title = None | ||||
|  | ||||
| # 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'] | ||||
|  | ||||
| # Add any extra paths that contain custom files (such as robots.txt or | ||||
| # .htaccess) here, relative to this directory. These files are copied | ||||
| # directly to the root of the documentation. | ||||
| #html_extra_path = [] | ||||
|  | ||||
| # 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 = True | ||||
|  | ||||
| # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | ||||
| #html_show_sphinx = True | ||||
|  | ||||
| # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | ||||
| #html_show_copyright = True | ||||
|  | ||||
| # If true, an OpenSearch description file will be output, and all pages will | ||||
| # contain a <link> 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 | ||||
|  | ||||
| # Language to be used for generating the HTML full-text search index. | ||||
| # Sphinx supports the following languages: | ||||
| #   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' | ||||
| #   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' | ||||
| #html_search_language = 'en' | ||||
|  | ||||
| # A dictionary with options for the search language support, empty by default. | ||||
| # Now only 'ja' uses this config value | ||||
| #html_search_options = {'type': 'default'} | ||||
|  | ||||
| # The name of a javascript file (relative to the configuration directory) that | ||||
| # implements a search results scorer. If empty, the default will be used. | ||||
| #html_search_scorer = 'scorer.js' | ||||
|  | ||||
| # Output file base name for HTML help builder. | ||||
| htmlhelp_basename = 'devlibdoc' | ||||
|  | ||||
| # -- 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': '', | ||||
|  | ||||
| # Latex figure (float) alignment | ||||
| #'figure_align': 'htbp', | ||||
| } | ||||
|  | ||||
| # Grouping the document tree into LaTeX files. List of tuples | ||||
| # (source start file, target name, title, | ||||
| #  author, documentclass [howto, manual, or own class]). | ||||
| latex_documents = [ | ||||
|   (master_doc, 'devlib.tex', u'devlib Documentation', | ||||
|    u'ARM Limited', '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 = [ | ||||
|     (master_doc, 'devlib', u'devlib Documentation', | ||||
|      [author], 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 = [ | ||||
|   (master_doc, 'devlib', u'devlib Documentation', | ||||
|    author, 'devlib', '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' | ||||
|  | ||||
| # If true, do not generate a @detailmenu in the "Top" node's menu. | ||||
| #texinfo_no_detailmenu = False | ||||
							
								
								
									
										31
									
								
								doc/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								doc/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| .. devlib documentation master file, created by | ||||
|    sphinx-quickstart on Tue Aug 11 17:37:27 2015. | ||||
|    You can adapt this file completely to your liking, but it should at least | ||||
|    contain the root `toctree` directive. | ||||
|  | ||||
| Welcome to devlib documentation | ||||
| =============================== | ||||
|  | ||||
| devlib provides an interface for interacting with remote targets, such as | ||||
| development boards, mobile devices, etc. It also provides means of collecting | ||||
| various measurements and traces from such targets. | ||||
|  | ||||
| Contents: | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 2 | ||||
|  | ||||
|    overview | ||||
|    target | ||||
|    modules | ||||
|    instrumentation | ||||
|  | ||||
|  | ||||
|  | ||||
| Indices and tables | ||||
| ================== | ||||
|  | ||||
| * :ref:`genindex` | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
|  | ||||
							
								
								
									
										238
									
								
								doc/instrumentation.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								doc/instrumentation.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | ||||
| Instrumentation | ||||
| =============== | ||||
|  | ||||
| The ``Instrument`` API provide a consistent way of collecting measurements from | ||||
| a target. Measurements are collected via an instance of a class derived from | ||||
| :class:`Instrument`. An ``Instrument`` allows collection of measurement from one | ||||
| or more channels. An ``Instrument`` may support ``INSTANTANEOUS`` or | ||||
| ``CONTINUOUS`` collection, or both. | ||||
|  | ||||
| Example | ||||
| ------- | ||||
|  | ||||
| The following example shows how to use an instrument to read temperature from an | ||||
| Android target. | ||||
|  | ||||
| .. code-block:: ipython | ||||
|  | ||||
|     # import and instantiate the Target and the instrument | ||||
|     # (note: this assumes exactly one android target connected | ||||
|     #  to the host machine). | ||||
|     In [1]: from devlib import AndroidTarget, HwmonInstrument | ||||
|  | ||||
|     In [2]: t = AndroidTarget() | ||||
|  | ||||
|     In [3]: i = HwmonInstrument(t) | ||||
|  | ||||
|     # Set up the instrument on the Target. In case of HWMON, this is | ||||
|     # a no-op, but is included here for completeness. | ||||
|     In [4]: i.setup() | ||||
|  | ||||
|     # Find out what the instrument is capable collecting from the  | ||||
|     # target. | ||||
|     In [5]: i.list_channels() | ||||
|     Out[5]: | ||||
|     [CHAN(battery/temp1, battery_temperature), | ||||
|      CHAN(exynos-therm/temp1, exynos-therm_temperature)] | ||||
|  | ||||
|     # Set up a new measurement session, and specify what is to be | ||||
|     # collected. | ||||
|     In [6]: i.reset(sites=['exynos-therm']) | ||||
|  | ||||
|     # HWMON instrument supports INSTANTANEOUS collection, so invoking | ||||
|     # take_measurement() will return a list of measurements take from  | ||||
|     # each of the channels configured during reset() | ||||
|     In [7]: i.take_measurement() | ||||
|     Out[7]: [exynos-therm_temperature: 36.0 degrees] | ||||
|  | ||||
| API | ||||
| --- | ||||
|  | ||||
| Instrument | ||||
| ~~~~~~~~~~ | ||||
|  | ||||
| .. class:: Instrument(target, **kwargs) | ||||
|  | ||||
|    An ``Instrument`` allows collection of measurement from one or more | ||||
|    channels. An ``Instrument`` may support ``INSTANTANEOUS`` or ``CONTINUOUS`` | ||||
|    collection, or both. | ||||
|  | ||||
| .. attribute:: Instrument.mode | ||||
|  | ||||
|    A bit mask that indicates collection modes that are supported by this | ||||
|    instrument. Possible values are: | ||||
|  | ||||
|    :INSTANTANEOUS: The instrument supports taking a single sample via | ||||
|                    ``take_measurement()``. | ||||
|    :CONTINUOUS: The instrument supports collecting measurements over a | ||||
|                 period of time via ``start()``, ``stop()``, and | ||||
|                 ``get_data()`` methods. | ||||
|  | ||||
|    .. note:: It's possible for one instrument to support more than a single  | ||||
|              mode. | ||||
|  | ||||
| .. attribute:: Instrument.active_channels | ||||
|  | ||||
|    Channels that have been activated via ``reset()``. Measurements will only be | ||||
|    collected for these channels. | ||||
|  | ||||
| .. method:: Instrument.list_channels() | ||||
|  | ||||
|    Returns a list of :class:`InstrumentChannel` instances that describe what | ||||
|    this instrument can measure on the current target. A channel is a combination | ||||
|    of a ``kind`` of measurement (power, temperature, etc) and a ``site`` that | ||||
|    indicates where on the target the measurement will be collected from. | ||||
|  | ||||
| .. method:: Instrument.get_channels(measure) | ||||
|  | ||||
|    Returns channels for a particular ``measure`` type. A ``measure`` can be | ||||
|    either a string (e.g. ``"power"``) or a :class:`MeasurmentType` instance. | ||||
|  | ||||
| .. method::  Instrument.setup(*args, **kwargs) | ||||
|  | ||||
|    This will set up the instrument on the target. Parameters this method takes | ||||
|    are particular to subclasses (see documentation for specific instruments | ||||
|    below).  What actions are performed by this method are also | ||||
|    instrument-specific.  Usually these will be things like  installing | ||||
|    executables, starting services, deploying assets, etc. Typically, this method | ||||
|    needs to be invoked at most once per reboot of the target (unless | ||||
|    ``teardown()`` has been called), but see documentation for the instrument | ||||
|    you're interested in. | ||||
|  | ||||
| .. method:: Instrument.reset([sites, [kinds]]) | ||||
|  | ||||
|    This is used to configure an instrument for collection. This must be invoked | ||||
|    before ``start()`` is called to begin collection. ``sites`` and ``kinds`` | ||||
|    parameters may be used to specify which channels measurements should be | ||||
|    collected from (if omitted, then measurements will be collected for all | ||||
|    available sites/kinds). This methods sets the ``active_channels`` attribute | ||||
|    of the ``Instrument``. | ||||
|  | ||||
| .. method:: Instrument.take_measurment() | ||||
|  | ||||
|    Take a single measurement from ``active_channels``. Returns a list of | ||||
|    :class:`Measurement` objects (one for each active channel). | ||||
|  | ||||
|    .. note:: This method is only implemented by :class:`Instrument`\ s that | ||||
|              support ``INSTANTANEOUS`` measurment. | ||||
|  | ||||
| .. method:: Instrument.start() | ||||
|  | ||||
|    Starts collecting measurements from ``active_channels``. | ||||
|  | ||||
|    .. note:: This method is only implemented by :class:`Instrument`\ s that | ||||
|              support ``CONTINUOUS`` measurment. | ||||
|  | ||||
| .. method:: Instrument.stop() | ||||
|  | ||||
|    Stops collecting measurements from ``active_channels``. Must be called after | ||||
|    :func:`start()`. | ||||
|  | ||||
|    .. note:: This method is only implemented by :class:`Instrument`\ s that | ||||
|              support ``CONTINUOUS`` measurment. | ||||
|  | ||||
| .. method:: Instrument.get_data(outfile) | ||||
|  | ||||
|    Write collected data into ``outfile``. Must be called after :func:`stop()`.  | ||||
|    Data will be written in CSV format with a column for each channel and a row | ||||
|    for each sample. Column heading will be channel, labels in the form  | ||||
|    ``<site>_<kind>`` (see :class:`InstrumentChannel`). The order of the coluns | ||||
|    will be the same as the order of channels in ``Instrument.active_channels``. | ||||
|  | ||||
|    This returns a :class:`MeasurementCsv` instance associated with the outfile | ||||
|    that can be used to stream :class:`Measurement`\ s lists (similar to what is | ||||
|    returned by ``take_measurement()``. | ||||
|  | ||||
|    .. note:: This method is only implemented by :class:`Instrument`\ s that | ||||
|              support ``CONTINUOUS`` measurment. | ||||
|  | ||||
|  | ||||
| Instrument Channel | ||||
| ~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| .. class:: InstrumentChannel(name, site, measurement_type, **attrs) | ||||
|  | ||||
|    An :class:`InstrumentChannel` describes a single type of measurement that may | ||||
|    be collected by an :class:`Instrument`. A channel is primarily defined by a | ||||
|    ``site`` and a ``measurement_type``. | ||||
|  | ||||
|    A ``site`` indicates where  on the target a measurement is collected from | ||||
|    (e.g. a volage rail or location of a sensor). | ||||
|  | ||||
|    A ``measurement_type`` is an instance of :class:`MeasurmentType` that | ||||
|    describes what sort of measurment this is (power, temperature, etc). Each | ||||
|    mesurement type has a standard unit it is reported in, regardless of an | ||||
|    instrument used to collect it. | ||||
|  | ||||
|    A channel (i.e. site/measurement_type combination) is unique per instrument, | ||||
|    however there may be more than one channel associated with one site (e.g. for | ||||
|    both volatage and power). | ||||
|  | ||||
|    It should not be assumed that any site/measurement_type combination is valid. | ||||
|    The list of available channels can queried with | ||||
|    :func:`Instrument.list_channels()`. | ||||
|  | ||||
| .. attribute:: InstrumentChannel.site | ||||
|  | ||||
|    The name of the "site" from which the measurments are collected (e.g. voltage | ||||
|    rail, sensor, etc). | ||||
|  | ||||
| .. attribute:: InstrumentChannel.kind | ||||
|  | ||||
|    A string indingcating the type of measrument that will be collted. This is | ||||
|    the ``name`` of the :class:`MeasurmentType` associated with this channel. | ||||
|  | ||||
| .. attribute:: InstrumentChannel.units | ||||
|  | ||||
|    Units in which measurment will be reported. this is determined by the | ||||
|    underlying :class:`MeasurmentType`. | ||||
|  | ||||
| .. attribute:: InstrumentChannel.label | ||||
|  | ||||
|    A label that can be attached to measurments associated with with channel. | ||||
|    This is constructed with :: | ||||
|  | ||||
|        '{}_{}'.format(self.site, self.kind) | ||||
|  | ||||
|  | ||||
| Measurement Types | ||||
| ~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| In order to make instruments easer to use, and to make it easier to swap them | ||||
| out when necessary (e.g. change method of collecting power), a number of | ||||
| standard measurement types are defined. This way, for example, power will always | ||||
| be reported as "power" in Watts, and never as "pwr" in milliWatts. Currently | ||||
| defined measurement types are | ||||
|  | ||||
|  | ||||
| +-------------+---------+---------------+ | ||||
| | name        | units   | category      | | ||||
| +=============+=========+===============+ | ||||
| | time        | seconds |               | | ||||
| +-------------+---------+---------------+ | ||||
| | temperature | degrees |               | | ||||
| +-------------+---------+---------------+ | ||||
| | power       | watts   | power/energy  | | ||||
| +-------------+---------+---------------+ | ||||
| | voltage     | volts   | power/energy  | | ||||
| +-------------+---------+---------------+ | ||||
| | current     | amps    | power/energy  | | ||||
| +-------------+---------+---------------+ | ||||
| | energy      | joules  | power/energy  | | ||||
| +-------------+---------+---------------+ | ||||
| | tx          | bytes   | data transfer | | ||||
| +-------------+---------+---------------+ | ||||
| | rx          | bytes   | data transfer | | ||||
| +-------------+---------+---------------+ | ||||
| | tx/rx       | bytes   | data transfer | | ||||
| +-------------+---------+---------------+ | ||||
|  | ||||
|  | ||||
| .. instruments: | ||||
|  | ||||
| Available Instruments | ||||
| --------------------- | ||||
|  | ||||
| This section lists instruments that are currently part of devlib. | ||||
|  | ||||
| TODO | ||||
							
								
								
									
										263
									
								
								doc/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								doc/make.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,263 @@ | ||||
| @ECHO OFF | ||||
|  | ||||
| REM Command file for Sphinx documentation | ||||
|  | ||||
| if "%SPHINXBUILD%" == "" ( | ||||
| 	set SPHINXBUILD=sphinx-build | ||||
| ) | ||||
| set BUILDDIR=_build | ||||
| set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . | ||||
| set I18NSPHINXOPTS=%SPHINXOPTS% . | ||||
| if NOT "%PAPER%" == "" ( | ||||
| 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | ||||
| 	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% | ||||
| ) | ||||
|  | ||||
| if "%1" == "" goto help | ||||
|  | ||||
| if "%1" == "help" ( | ||||
| 	:help | ||||
| 	echo.Please use `make ^<target^>` where ^<target^> is one of | ||||
| 	echo.  html       to make standalone HTML files | ||||
| 	echo.  dirhtml    to make HTML files named index.html in directories | ||||
| 	echo.  singlehtml to make a single large HTML file | ||||
| 	echo.  pickle     to make pickle files | ||||
| 	echo.  json       to make JSON files | ||||
| 	echo.  htmlhelp   to make HTML files and a HTML help project | ||||
| 	echo.  qthelp     to make HTML files and a qthelp project | ||||
| 	echo.  devhelp    to make HTML files and a Devhelp project | ||||
| 	echo.  epub       to make an epub | ||||
| 	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter | ||||
| 	echo.  text       to make text files | ||||
| 	echo.  man        to make manual pages | ||||
| 	echo.  texinfo    to make Texinfo files | ||||
| 	echo.  gettext    to make PO message catalogs | ||||
| 	echo.  changes    to make an overview over all changed/added/deprecated items | ||||
| 	echo.  xml        to make Docutils-native XML files | ||||
| 	echo.  pseudoxml  to make pseudoxml-XML files for display purposes | ||||
| 	echo.  linkcheck  to check all external links for integrity | ||||
| 	echo.  doctest    to run all doctests embedded in the documentation if enabled | ||||
| 	echo.  coverage   to run coverage check of the documentation if enabled | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "clean" ( | ||||
| 	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i | ||||
| 	del /q /s %BUILDDIR%\* | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
|  | ||||
| REM Check if sphinx-build is available and fallback to Python version if any | ||||
| %SPHINXBUILD% 2> nul | ||||
| if errorlevel 9009 goto sphinx_python | ||||
| goto sphinx_ok | ||||
|  | ||||
| :sphinx_python | ||||
|  | ||||
| set SPHINXBUILD=python -m sphinx.__init__ | ||||
| %SPHINXBUILD% 2> nul | ||||
| if errorlevel 9009 ( | ||||
| 	echo. | ||||
| 	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx | ||||
| 	echo.installed, then set the SPHINXBUILD environment variable to point | ||||
| 	echo.to the full path of the 'sphinx-build' executable. Alternatively you | ||||
| 	echo.may add the Sphinx directory to PATH. | ||||
| 	echo. | ||||
| 	echo.If you don't have Sphinx installed, grab it from | ||||
| 	echo.http://sphinx-doc.org/ | ||||
| 	exit /b 1 | ||||
| ) | ||||
|  | ||||
| :sphinx_ok | ||||
|  | ||||
|  | ||||
| if "%1" == "html" ( | ||||
| 	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/html. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "dirhtml" ( | ||||
| 	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "singlehtml" ( | ||||
| 	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "pickle" ( | ||||
| 	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can process the pickle files. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "json" ( | ||||
| 	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can process the JSON files. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "htmlhelp" ( | ||||
| 	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can run HTML Help Workshop with the ^ | ||||
| .hhp project file in %BUILDDIR%/htmlhelp. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "qthelp" ( | ||||
| 	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can run "qcollectiongenerator" with the ^ | ||||
| .qhcp project file in %BUILDDIR%/qthelp, like this: | ||||
| 	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\devlib.qhcp | ||||
| 	echo.To view the help file: | ||||
| 	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\devlib.ghc | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "devhelp" ( | ||||
| 	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "epub" ( | ||||
| 	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The epub file is in %BUILDDIR%/epub. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "latex" ( | ||||
| 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "latexpdf" ( | ||||
| 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | ||||
| 	cd %BUILDDIR%/latex | ||||
| 	make all-pdf | ||||
| 	cd %~dp0 | ||||
| 	echo. | ||||
| 	echo.Build finished; the PDF files are in %BUILDDIR%/latex. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "latexpdfja" ( | ||||
| 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | ||||
| 	cd %BUILDDIR%/latex | ||||
| 	make all-pdf-ja | ||||
| 	cd %~dp0 | ||||
| 	echo. | ||||
| 	echo.Build finished; the PDF files are in %BUILDDIR%/latex. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "text" ( | ||||
| 	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The text files are in %BUILDDIR%/text. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "man" ( | ||||
| 	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The manual pages are in %BUILDDIR%/man. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "texinfo" ( | ||||
| 	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "gettext" ( | ||||
| 	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The message catalogs are in %BUILDDIR%/locale. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "changes" ( | ||||
| 	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.The overview file is in %BUILDDIR%/changes. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "linkcheck" ( | ||||
| 	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Link check complete; look for any errors in the above output ^ | ||||
| or in %BUILDDIR%/linkcheck/output.txt. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "doctest" ( | ||||
| 	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Testing of doctests in the sources finished, look at the ^ | ||||
| results in %BUILDDIR%/doctest/output.txt. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "coverage" ( | ||||
| 	%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Testing of coverage in the sources finished, look at the ^ | ||||
| results in %BUILDDIR%/coverage/python.txt. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "xml" ( | ||||
| 	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The XML files are in %BUILDDIR%/xml. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "pseudoxml" ( | ||||
| 	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| :end | ||||
							
								
								
									
										172
									
								
								doc/modules.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								doc/modules.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | ||||
| Modules | ||||
| ======= | ||||
|  | ||||
| Modules add additional functionality to the core :class:`Target` interface. | ||||
| Usually, it is support for specific subsystems on the target. Modules are | ||||
| instantiated as attributes of the :class:`Target` instance. | ||||
|  | ||||
| hotplug | ||||
| ------- | ||||
|  | ||||
| Kernel ``hotplug`` subsystem allows offlining ("removing") cores from the | ||||
| system, and onlining them back int. The ``devlib`` module exposes a simple | ||||
| interface to this subsystem | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import LocalLinuxTarget | ||||
|    target = LocalLinuxTarget() | ||||
|  | ||||
|    # offline cpus 2 and 3, "removing" them from the system | ||||
|    target.hotplug.offline(2, 3) | ||||
|  | ||||
|    # bring CPU 2 back in | ||||
|    target.hotplug.online(2) | ||||
|  | ||||
|    # Make sure all cpus are online | ||||
|    target.hotplug.online_all() | ||||
|  | ||||
| cpufreq | ||||
| ------- | ||||
|  | ||||
| ``cpufreq`` is the kernel subsystem for managing DVFS (Dynamic Voltage and | ||||
| Frequency Scaling). It allows controlling frequency ranges and switching | ||||
| policies (governors). The ``devlib`` module exposes the following interface | ||||
|  | ||||
| .. note:: On ARM big.LITTLE systems, all cores on a cluster (usually all cores | ||||
|           of the same type) are in the same frequency domain, so setting | ||||
|           ``cpufreq`` state on one core on a cluter will affect all cores on | ||||
|           that cluster. Because of this, some devices only expose cpufreq sysfs | ||||
|           interface (which is what is used by the ``devlib`` module) on the | ||||
|           first cpu in a cluster. So to keep your scripts proable, always use | ||||
|           the fist (online) CPU in a cluster to set ``cpufreq`` state. | ||||
|  | ||||
| .. method:: target.cpufreq.list_governors(cpu) | ||||
|  | ||||
|    List cpufreq governors available for the specified cpu. Returns a list of | ||||
|    strings. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|                ``1`` or ``"cpu1"``). | ||||
|  | ||||
| .. method:: target.cpufreq.list_governor_tunables(cpu) | ||||
|  | ||||
|    List the tunables for the specified cpu's current governor. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|        ``1`` or ``"cpu1"``). | ||||
|  | ||||
|  | ||||
| .. method:: target.cpufreq.get_governor(cpu) | ||||
|  | ||||
|    Returns the name of the currently set governor for the specified cpu. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|                ``1`` or ``"cpu1"``). | ||||
|  | ||||
| .. method:: target.cpufreq.set_governor(cpu, governor, **kwargs) | ||||
|  | ||||
|    Sets the governor for the specified cpu. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|         ``1`` or ``"cpu1"``). | ||||
|    :param governor: The name of the governor. This must be one of the governors  | ||||
|                 supported by the CPU (as retrunted by ``list_governors()``. | ||||
|  | ||||
|    Keyword arguments may be used to specify governor tunable values. | ||||
|  | ||||
|  | ||||
| .. method:: target.cpufreq.get_governor_tunables(cpu) | ||||
|  | ||||
|    Return a dict with the values of the specfied CPU's current governor. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|        ``1`` or ``"cpu1"``). | ||||
|  | ||||
| .. method:: target.cpufreq.set_governor_tunables(cpu, **kwargs) | ||||
|  | ||||
|    Set the tunables for the current governor on the specified CPU. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|        ``1`` or ``"cpu1"``). | ||||
|  | ||||
|    Keyword arguments should be used to specify tunable values. | ||||
|  | ||||
| .. method:: target.cpufreq.list_frequencie(cpu) | ||||
|  | ||||
|    List DVFS frequencies supported by the specified CPU. Returns a list of ints. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|        ``1`` or ``"cpu1"``). | ||||
|  | ||||
| .. method:: target.cpufreq.get_min_frequency(cpu) | ||||
|             target.cpufreq.get_max_frequency(cpu) | ||||
|             target.cpufreq.set_min_frequency(cpu, frequency[, exact=True]) | ||||
|             target.cpufreq.set_max_frequency(cpu, frequency[, exact=True]) | ||||
|  | ||||
|    Get and set min and max frequencies on the specfied CPU. "set" functions are | ||||
|    avialable with all governors other than ``userspace``. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|        ``1`` or ``"cpu1"``). | ||||
|    :param frequency: Frequency to set. | ||||
|  | ||||
| .. method:: target.cpufreq.get_frequency(cpu) | ||||
|             target.cpufreq.set_frequency(cpu, frequency[, exact=True]) | ||||
|  | ||||
|    Get and set current frequency on the specified CPU. ``set_frequency`` is only | ||||
|    available if the current governor is ``userspace``. | ||||
|  | ||||
|    :param cpu: The cpu; could be a numeric or the corresponding string (e.g. | ||||
|        ``1`` or ``"cpu1"``). | ||||
|    :param frequency: Frequency to set. | ||||
|  | ||||
| cpuidle | ||||
| ------- | ||||
|  | ||||
| ``cpufreq`` is the kernel subsystem for managing CPU low power (idle) states. | ||||
|  | ||||
| .. method:: taget.cpuidle.get_driver() | ||||
|  | ||||
|    Return the name current cpuidle driver. | ||||
|  | ||||
| .. method:: taget.cpuidle.get_governor() | ||||
|  | ||||
|    Return the name current cpuidle governor (policy). | ||||
|  | ||||
| .. method:: target.cpuidle.get_states([cpu=0]) | ||||
|  | ||||
|    Return idle states (optionally, for the specified CPU). Returns a list of | ||||
|    :class:`CpuidleState` instances. | ||||
|  | ||||
| .. method:: target.cpuidle.get_state(state[, cpu=0]) | ||||
|  | ||||
|    Return :class:`CpuidleState` instance (optionally, for the specified CPU) | ||||
|    representing the specified idle state. ``state`` can be either an integer | ||||
|    index of the state or a string with the states ``name`` or ``desc``. | ||||
|  | ||||
| .. method:: target.cpuidle.enable(state[, cpu=0]) | ||||
|             target.cpuidle.disable(state[, cpu=0]) | ||||
|             target.cpuidle.enable_all([cpu=0]) | ||||
|             target.cpuidle.disable_all([cpu=0]) | ||||
|  | ||||
|     Enable or disable the specified or all states (optionally on the specified | ||||
|     CPU. | ||||
|  | ||||
| You can also call ``enable()`` or ``disable()`` on :class:`CpuidleState` objects  | ||||
| returned by get_state(s). | ||||
|  | ||||
| cgroups | ||||
| ------- | ||||
|  | ||||
| TODO | ||||
|  | ||||
| hwmon | ||||
| ----- | ||||
|  | ||||
| TODO | ||||
|  | ||||
| API | ||||
| --- | ||||
|  | ||||
| TODO | ||||
							
								
								
									
										282
									
								
								doc/overview.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								doc/overview.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,282 @@ | ||||
| Overview | ||||
| ======== | ||||
|  | ||||
| A :class:`Target` instance serves as the main interface to the target device.  | ||||
| There currently three target interfaces: | ||||
|  | ||||
| - :class:`LinuxTarget` for interacting with Linux devices over SSH. | ||||
| - :class:`AndroidTraget` for interacting with Android devices over adb. | ||||
| - :class:`LocalLinuxTarget`: for interacting with the local Linux host. | ||||
|  | ||||
| They all work in more-or-less the same way, with the major difference being in | ||||
| how connection settings are specified; though there may also be a few APIs | ||||
| specific to a particular target type (e.g. :class:`AndroidTarget` exposes | ||||
| methods for working with logcat). | ||||
|  | ||||
|  | ||||
| Acquiring a Target | ||||
| ------------------ | ||||
|  | ||||
| To create an interface to your device, you just need to instantiate one of the | ||||
| :class:`Target` derivatives listed above, and pass it the right | ||||
| ``connection_settings``. Code snippet below gives a typical example of | ||||
| instantiating each of the three target types.  | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import LocalLinuxTarget, LinuxTarget, AndroidTarget | ||||
|  | ||||
|    # Local machine requires no special connection settings. | ||||
|    t1 = LocalLinuxTarget() | ||||
|  | ||||
|    # For a Linux device, you will need to provide the normal SSH credentials. | ||||
|    # Both password-based, and key-based authentication is supported (password | ||||
|    # authentication requires sshpass to be installed on your host machine).' | ||||
|    t2 = LinuxTarget(connetion_settings={'host': '192.168.0.5', | ||||
|                                         'username': 'root', | ||||
|                                         'password': 'sekrit', | ||||
|                                         # or | ||||
|                                         'keyfile': '/home/me/.ssh/id_rsa'}) | ||||
|  | ||||
|    # For an Android target, you will need to pass the device name as reported | ||||
|    # by "adb devices". If there is only one device visible to adb, you can omit | ||||
|    # this setting and instantiate similar to a local target. | ||||
|    t3 = AndroidTarget(connection_settings={'device': '0123456789abcde'}) | ||||
|  | ||||
| Instantiating a target may take a second or two as the remote device will be | ||||
| queried to initialize :class:`Target`'s internal state. If you would like to | ||||
| create a :class:`Target` instance but not immediately connect to the remote | ||||
| device, you can pass ``connect=False`` parameter. If you do that, you would have | ||||
| to then explicitly call ``t.connect()`` before you can interact with the device. | ||||
|  | ||||
| There are a few additional parameters you can pass in instantiation besides | ||||
| ``connection_settings``, but they are usually unnecessary. Please see | ||||
| :class:`Target` API documentation for more details. | ||||
|  | ||||
| Target Interface | ||||
| ---------------- | ||||
|  | ||||
| This is a quick overview of the basic interface to the device. See | ||||
| :class:`Targeet` API documentation for the full list of supported methods and | ||||
| more detailed documentation. | ||||
|  | ||||
| One-time Setup | ||||
| ~~~~~~~~~~~~~~ | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import LocalLinuxTarget | ||||
|    t = LocalLinuxTarget() | ||||
|  | ||||
|    t.setup() | ||||
|  | ||||
| This sets up the target for ``devlib`` interaction. This includes creating | ||||
| working directories, deploying busybox, etc. It's usually enough to do this once | ||||
| for a new device, as the changes this makes will persist across reboots. | ||||
| However, there is no issue with calling this multiple times, so, to be on the | ||||
| safe site, it's a good idea to call this once at the beginning of your scripts. | ||||
|  | ||||
| Command Execution | ||||
| ~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| There are several ways to execute a command on the target. In each case, a | ||||
| :class:`TargetError` will be raised if something goes wrong. In very case, it is | ||||
| also possible to specify ``as_root=True`` if the specified command should be | ||||
| executed as root. | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import LocalLinuxTarget | ||||
|    t = LocalLinuxTarget() | ||||
|  | ||||
|    # Execute a command  | ||||
|    output = t.execute('echo $PWD') | ||||
|  | ||||
|    # Execute command via a subprocess and return the corresponding Popen object. | ||||
|    # This will block current connection to the device until the command | ||||
|    # completes. | ||||
|    p = t.background('echo $PWD') | ||||
|    output, error = p.communicate() | ||||
|  | ||||
|    # Run the command in the background on the device and return immediately. | ||||
|    # This will not block the connection, allowing to immediately execute another | ||||
|    # command.  | ||||
|    t.kick_off('echo $PWD') | ||||
|  | ||||
|    # This is used to invoke an executable binary on the device. This allows some | ||||
|    # finer-grained control over the invocation, such as specifying the directory | ||||
|    # in which the executable will run; however you're limited to a single binary | ||||
|    # and cannot construct complex commands (e.g. this does not allow chaining or | ||||
|    # piping several commands together). | ||||
|    output = t.invoke('echo', args=['$PWD'], in_directory='/') | ||||
|  | ||||
| File Transfer | ||||
| ~~~~~~~~~~~~~ | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import LocalLinuxTarget | ||||
|    t = LocalLinuxTarget() | ||||
|  | ||||
|    # "push" a file from the local machine onto the target device. | ||||
|    t.push('/path/to/local/file.txt', '/path/to/target/file.txt') | ||||
|  | ||||
|    # "pull" a file from the target device into a location on the local machine | ||||
|    t.pull('/path/to/target/file.txt', '/path/to/local/file.txt') | ||||
|  | ||||
|    # Install the specified binary on the target. This will deploy the file and | ||||
|    # ensure it's executable. This will *not* guarantee that the binary will be  | ||||
|    # in PATH. Instead the path to the binary will be returned; this should be | ||||
|    # used to call the binary henceforth. | ||||
|    target_bin = t.install('/path/to/local/bin.exe') | ||||
|    # Example invocation: | ||||
|    output = t.execute('{} --some-option'.format(target_bin)) | ||||
|  | ||||
| The usual access permission constraints on the user account (both on the target | ||||
| and the host) apply.  | ||||
|  | ||||
| Process Control | ||||
| ~~~~~~~~~~~~~~~ | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    import signal | ||||
|    from devlib import LocalLinuxTarget | ||||
|    t = LocalLinuxTarget() | ||||
|  | ||||
|    # return PIDs of all running instances of a process | ||||
|    pids = t.get_pids_of('sshd') | ||||
|  | ||||
|    # kill a running process. This works the same ways as the kill command, so | ||||
|    # SIGTERM will be used by default. | ||||
|    t.kill(666, signal=signal.SIGKILL) | ||||
|  | ||||
|    # kill all running instances of a process. | ||||
|    t.killall('badexe', signal=signal.SIGKILL) | ||||
|  | ||||
|    # List processes running on the target. This retruns a list of parsed | ||||
|    # PsEntry records. | ||||
|    entries = t.ps() | ||||
|    # e.g.  print virtual memory sizes of all running sshd processes: | ||||
|    print ', '.join(str(e.vsize) for e in entries if e.name == 'sshd') | ||||
|  | ||||
|  | ||||
| More... | ||||
| ~~~~~~~ | ||||
|  | ||||
| As mentioned previously, the above is not intended to be exhaustive | ||||
| documentation of the :class:`Target` interface. Please refer to the API | ||||
| documentation for the full list of attributes and methods and their parameters. | ||||
|  | ||||
| Super User Privileges | ||||
| --------------------- | ||||
|  | ||||
| It is not necessary for the account logged in on the target to have super user | ||||
| privileges, however the functionality will obviously be diminished, if that is | ||||
| not the case. ``devilib`` will determine if the logged in user has root | ||||
| privileges and the correct way to invoke it. You should avoid including "sudo" | ||||
| directly in your commands, instead, specify ``as_root=True`` where needed. This | ||||
| will make your scripts portable across multiple devices and OS's. | ||||
|  | ||||
|  | ||||
| On-Target Locations | ||||
| ------------------- | ||||
|  | ||||
| File system layouts vary wildly between devices and operating systems. | ||||
| Hard-coding absolute paths in your scripts will mean there is a good chance they | ||||
| will break if run on a different device.  To help with this, ``devlib`` defines | ||||
| a couple of "standard" locations and a means of working with them. | ||||
|  | ||||
| working_directory | ||||
|         This is a directory on the target readable and writable by the account | ||||
|         used to log in. This should generally be used for all output generated | ||||
|         by your script on the device and as the destination for all | ||||
|         host-to-target file transfers. It may or may not permit execution so | ||||
|         executables should not be run directly from here. | ||||
|          | ||||
| executables_directory | ||||
|         This directory allows execution. This will be used by ``install()``. | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import LocalLinuxTarget | ||||
|    t = LocalLinuxTarget() | ||||
|  | ||||
|    # t.path  is equivalent to Python standard library's os.path, and should be | ||||
|    # used in the same way. This insures that your scripts are portable across | ||||
|    # both target and host OS variations. e.g. | ||||
|    on_target_path = t.path.join(t.working_directory, 'assets.tar.gz') | ||||
|    t.push('/local/path/to/assets.tar.gz', on_target_path) | ||||
|  | ||||
|    # Since working_directory is a common base path for on-target locations, | ||||
|    # there a short-hand for the above: | ||||
|    t.push('/local/path/to/assets.tar.gz', t.get_workpath('assets.tar.gz')) | ||||
|  | ||||
|  | ||||
| Modules | ||||
| ------- | ||||
|  | ||||
| Additional functionality is exposed via modules. Modules are initialized as | ||||
| attributes of a target instance. By default, ``hotplug``, ``cpufreq``, | ||||
| ``cpuidle``, ``cgroups`` and ``hwmon`` will attempt to load on target; additional | ||||
| modules may be specified when creating a :class:`Target` instance. | ||||
|  | ||||
| A module will probe the target for support before attempting to load. So if the | ||||
| underlying platform does not support particular functionality (e.g. the kernel | ||||
| on target device was built without hotplug support). To check whether a module | ||||
| has been successfully installed on a target, you can use ``has()`` method, e.g. | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import LocalLinuxTarget | ||||
|    t = LocalLinuxTarget() | ||||
|  | ||||
|    cpu0_freqs = [] | ||||
|    if t.has('cpufreq'): | ||||
|        cpu0_freqs = t.cpufreq.list_frequencies(0) | ||||
|  | ||||
|  | ||||
| Please see the modules documentation for more detail. | ||||
|  | ||||
|  | ||||
| Measurement and Trace | ||||
| --------------------- | ||||
|  | ||||
| You can collected traces (currently, just ftrace) using | ||||
| :class:`TraceCollector`\ s. For example | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|    from devlib import AndroidTarget, FtraceCollector | ||||
|    t = LocalLinuxTarget() | ||||
|    | ||||
|    # Initialize a collector specifying the events you want to collect and | ||||
|    # the buffer size to be used. | ||||
|    trace = FtraceCollector(t, events=['power*'], buffer_size=40000) | ||||
|  | ||||
|    # clear ftrace buffer | ||||
|    trace.reset() | ||||
|  | ||||
|    # start trace collection | ||||
|    trace.start() | ||||
|  | ||||
|    # Perform the operations you want to trace here... | ||||
|    import time; time.sleep(5) | ||||
|  | ||||
|    # stop trace collection | ||||
|    trace.stop() | ||||
|  | ||||
|    # extract the trace file from the target into a local file | ||||
|    trace.get_trace('/tmp/trace.bin') | ||||
|  | ||||
|    # View trace file using Kernelshark (must be installed on the host). | ||||
|    trace.view('/tmp/trace.bin') | ||||
|  | ||||
|    # Convert binary trace into text format. This would normally be done | ||||
|    # automatically during get_trace(), unless autoreport is set to False during | ||||
|    # instantiation of the trace collector. | ||||
|    trace.report('/tmp/trace.bin', '/tmp/trace.txt') | ||||
|  | ||||
| In a similar way, :class:`Instrument` instances may be used to collect | ||||
| measurements (such as power) from targets that support it. Please see | ||||
| instruments documentation for more details. | ||||
							
								
								
									
										422
									
								
								doc/target.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										422
									
								
								doc/target.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,422 @@ | ||||
| Target | ||||
| ====== | ||||
|  | ||||
|  | ||||
| .. class:: Target(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT) | ||||
|     | ||||
|     :class:`Target` is the primary interface to the remote device. All interactions | ||||
|     with the device are performed via a :class:`Target` instance, either | ||||
|     directly, or via its modules or a wrapper interface (such as an | ||||
|     :class:`Instrument`). | ||||
|  | ||||
|     :param connection_settings: A ``dict`` that specifies how to connect to the remote  | ||||
|        device. Its contents depend on the specific :class:`Target` type used (e.g. | ||||
|        :class:`AndroidTarget` expects the adb ``device`` name). | ||||
|  | ||||
|     :param platform: A :class:`Target` defines interactions at Operating System level. A  | ||||
|         :class:`Platform` describes the underlying hardware (such as CPUs | ||||
|         available). If a :class:`Platform` instance is not specified on | ||||
|         :class:`Target` creation, one will be created automatically and it will | ||||
|         dynamically probe the device to discover as much about the underlying | ||||
|         hardware as it can. | ||||
|  | ||||
|     :param working_directory: This is primary location for on-target file system | ||||
|         interactions performed by ``devlib``. This location *must* be readable and | ||||
|         writable directly (i.e. without sudo) by the connection's user account.  | ||||
|         It may or may not allow execution. This location will be created,  | ||||
|         if necessary, during ``setup()``. | ||||
|  | ||||
|         If not explicitly specified, this will be set to a default value | ||||
|         depending on the type of :class:`Target` | ||||
|  | ||||
|     :param executables_directory: This is the location to which ``devlib`` will | ||||
|         install executable binaries (either during ``setup()`` or via an | ||||
|         explicit ``install()`` call). This location *must* support execution | ||||
|         (obviously). It should also be possible to write to this location, | ||||
|         possibly with elevated privileges (i.e. on a rooted Linux target, it | ||||
|         should be possible to write here with sudo, but not necessarily directly | ||||
|         by the connection's account). This location will be created,  | ||||
|         if necessary, during ``setup()``. | ||||
|  | ||||
|         This location does *not* to be same as the system's executables | ||||
|         location. In fact, to prevent devlib from overwriting system's defaults, | ||||
|         it better if this is a separate location, if possible. | ||||
|  | ||||
|         If not explicitly specified, this will be set to a default value | ||||
|         depending on the type of :class:`Target` | ||||
|  | ||||
|     :param connect: Specifies whether a connections should be established to the | ||||
|         target. If this is set to ``False``, then ``connect()`` must be | ||||
|         explicitly called later on before the :class:`Target` instance can be | ||||
|         used. | ||||
|  | ||||
|     :param modules: a list of additional modules to be installed. Some modules will | ||||
|         try to install by default (if supported by the underlying target). | ||||
|         Current default modules are ``hotplug``, ``cpufreq``, ``cpuidle``,  | ||||
|         ``cgroups``, and ``hwmon``. | ||||
|  | ||||
|         See modules documentation for more detail. | ||||
|  | ||||
|     :param load_default_modules: If set to ``False``,  default modules listed | ||||
|          above will *not* attempt to load. This may be used to either speed up | ||||
|          target instantiation (probing for initializing modules takes a bit of time) | ||||
|          or if there is an issue with one of the modules on a particular device | ||||
|          (the rest of the modules will then have to be explicitly specified in | ||||
|          the ``modules``). | ||||
|  | ||||
|     :param shell_prompt: This is a regular expression that matches the shell | ||||
|          prompted on the target. This may be used by some modules that establish | ||||
|          auxiliary connections to a target over UART. | ||||
|  | ||||
| .. attribute:: Target.core_names | ||||
|  | ||||
|    This is a list containing names of CPU cores on the target, in the order in | ||||
|    which they are index by the kernel. This is obtained via the underlying | ||||
|    :class:`Platform`. | ||||
|  | ||||
| .. attribute:: Target.core_clusters | ||||
|  | ||||
|    Some devices feature heterogeneous core configurations (such as ARM | ||||
|    big.LITTLE).  This is a list that maps CPUs onto underlying clusters. | ||||
|    (Usually, but not always, clusters correspond to groups of CPUs with the same | ||||
|    name). This is obtained via the underlying :class:`Platform`. | ||||
|  | ||||
| .. attribute:: Target.big_core | ||||
|  | ||||
|    This is the name of the cores that the "big"s in an ARM big.LITTLE | ||||
|    configuration. This is obtained via the underlying :class:`Platform`. | ||||
|  | ||||
| .. attribute:: Target.little_core | ||||
|  | ||||
|    This is the name of the cores that the "little"s in an ARM big.LITTLE | ||||
|    configuration. This is obtained via the underlying :class:`Platform`. | ||||
|  | ||||
| .. attribute:: Target.is_connected | ||||
|  | ||||
|    A boolean value that indicates whether an active connection exists to the | ||||
|    target device.  | ||||
|  | ||||
| .. attribute:: Target.connected_as_root | ||||
|  | ||||
|    A boolean value that indicate whether the account that was used to connect to | ||||
|    the target device is "root" (uid=0). | ||||
|  | ||||
| .. attribute:: Target.is_rooted | ||||
|  | ||||
|    A boolean value that indicates whether the connected user has super user | ||||
|    privileges on the devices (either is root, or is a sudoer). | ||||
|  | ||||
| .. attribute:: Target.kernel_version | ||||
|  | ||||
|    The version of the kernel on the target device. This returns a | ||||
|    :class:`KernelVersion` instance that has separate ``version`` and ``release`` | ||||
|    fields. | ||||
|  | ||||
| .. attribute:: Target.os_version | ||||
|  | ||||
|    This is a dict that contains a mapping of OS version elements to their | ||||
|    values. This mapping is OS-specific. | ||||
|  | ||||
| .. attribute:: Target.cpuinfo | ||||
|  | ||||
|    This is a :class:`Cpuinfo` instance which contains parsed contents of | ||||
|    ``/proc/cpuinfo``. | ||||
|  | ||||
| .. attribute:: Target.number_of_cpus | ||||
|  | ||||
|    The total number of CPU cores on the target device. | ||||
|  | ||||
| .. attribute:: Target.config | ||||
|  | ||||
|    A :class:`KernelConfig` instance that contains parsed kernel config from the | ||||
|    target device. This may be ``None`` if kernel config could not be extracted. | ||||
|  | ||||
| .. attribute:: Target.user | ||||
|  | ||||
|    The name of the user logged in on the target device. | ||||
|  | ||||
| .. attribute:: Target.conn | ||||
|  | ||||
|    The underlying connection object. This will be ``None`` if an active | ||||
|    connection does not exist (e.g. if ``connect=False`` as passed on | ||||
|    initialization and ``connect()`` has not been called). | ||||
|  | ||||
|    .. note:: a :class:`Target` will automatically create a connection per | ||||
|              thread. This will always be set to the connection for the current | ||||
|              thread. | ||||
|  | ||||
| .. method:: Target.connect([timeout]) | ||||
|     | ||||
|    Establish a connection to the target. It is usually not necessary to call | ||||
|    this explicitly, as a connection gets automatically established on | ||||
|    instantiation. | ||||
|  | ||||
| .. method:: Target.disconnect() | ||||
|  | ||||
|    Disconnect from target, closing all active connections to it. | ||||
|  | ||||
| .. method:: Target.get_connection([timeout]) | ||||
|  | ||||
|    Get an additional connection to the target. A connection can be used to | ||||
|    execute one blocking command at time. This will return a connection that can | ||||
|    be used to interact with a target in parallel while a blocking operation is | ||||
|    being executed. | ||||
|  | ||||
|    This should *not* be used to establish an initial connection; use | ||||
|    ``connect()`` instead. | ||||
|  | ||||
|    .. note:: :class:`Target` will automatically create a connection per | ||||
|              thread, so you don't normally need to use this explicitly in | ||||
|              threaded code. This is generally useful if you want to perform a | ||||
|              blocking operation (e.g. using ``background()``) while at the same | ||||
|              time doing something else in the same host-side thread. | ||||
|  | ||||
| .. method:: Target.setup([executables]) | ||||
|  | ||||
|    This will perform an initial one-time set up of a device for devlib | ||||
|    interaction. This involves deployment of tools relied on the :class:`Target`, | ||||
|    creation of working locations on the device, etc. | ||||
|  | ||||
|    Usually, it is enough to call this method once per new device, as its effects | ||||
|    will persist across reboots. However, it is safe to call this method multiple | ||||
|    times. It may therefore be a good practice to always call it once at the | ||||
|    beginning of a script to ensure that subsequent interactions will succeed. | ||||
|  | ||||
|    Optionally, this may also be used to deploy additional tools to the device | ||||
|    by specifying a list of binaries to install in the ``executables`` parameter. | ||||
|  | ||||
| .. method:: Target.reboot([hard [, connect, [timeout]]]) | ||||
|  | ||||
|    Reboot the target device. | ||||
|  | ||||
|    :param hard: A boolean value. If ``True`` a hard reset will be used instead | ||||
|         of the usual soft reset. Hard reset must be supported (usually via a | ||||
|         module) for this to work. Defaults to ``False``. | ||||
|    :param connect: A boolean value. If ``True``, a connection will be | ||||
|         automatically established to the target after reboot. Defaults to | ||||
|         ``True``. | ||||
|    :param timeout: If set, this will be used by various (platform-specific) | ||||
|         operations during reboot process to detect if the reboot has failed and | ||||
|         the device has hung. | ||||
|  | ||||
| .. method:: Target.push(source, dest [, timeout]) | ||||
|  | ||||
|    Transfer a file from the host machine to the target device. | ||||
|  | ||||
|    :param source: path of to the file on the host | ||||
|    :param dest: path of to the file on the target | ||||
|    :param timeout: timeout (in seconds) for the transfer; if the transfer does | ||||
|        not  complete within this period, an exception will be raised. | ||||
|  | ||||
| .. method:: Target.pull(source, dest [, timeout]) | ||||
|  | ||||
|    Transfer a file from the target device to the host machine. | ||||
|  | ||||
|    :param source: path of to the file on the target | ||||
|    :param dest: path of to the file on the host | ||||
|    :param timeout: timeout (in seconds) for the transfer; if the transfer does | ||||
|        not  complete within this period, an exception will be raised. | ||||
|  | ||||
| .. method:: Target.execute(command [, timeout [, check_exit_code [, as_root]]]) | ||||
|  | ||||
|    Execute the specified command on the target device and return its output. | ||||
|  | ||||
|    :param command: The command to be executed. | ||||
|    :param timeout: Timeout (in seconds) for the execution of the command. If | ||||
|        specified, an exception will be raised if execution does not complete | ||||
|        with the specified period. | ||||
|    :param check_exit_code: If ``True`` (the default) the exit code (on target)  | ||||
|        from execution of the command will be checked, and an exception will be | ||||
|        raised if it is not ``0``. | ||||
|    :param as_root: The command will be executed as root. This will fail on | ||||
|        unrooted targets. | ||||
|  | ||||
| .. method:: Target.background(command [, stdout [, stderr [, as_root]]]) | ||||
|  | ||||
|    Execute the command on the target, invoking it via subprocess on the host. | ||||
|    This will return :class:`subprocess.Popen` instance for the command. | ||||
|  | ||||
|    :param command: The command to be executed. | ||||
|    :param stdout: By default, standard output will be piped from the subprocess; | ||||
|       this may be used to redirect it to an alternative file handle. | ||||
|    :param stderr: By default, standard error will be piped from the subprocess; | ||||
|       this may be used to redirect it to an alternative file handle. | ||||
|    :param as_root: The command will be executed as root. This will fail on | ||||
|        unrooted targets. | ||||
|  | ||||
|    .. note:: This **will block the connection** until the command completes. | ||||
|  | ||||
| .. method:: Target.invoke(binary [, args [, in_directory [, on_cpus [, as_root [, timeout]]]]]) | ||||
|  | ||||
|    Execute the specified binary on target (must already be installed) under the | ||||
|    specified conditions and return the output. | ||||
|  | ||||
|    :param binary: binary to execute. Must be present and executable on the device. | ||||
|    :param args: arguments to be passed to the binary. The can be either a list or | ||||
|           a string. | ||||
|    :param in_directory:  execute the binary in the  specified directory. This must | ||||
|                    be an absolute path. | ||||
|    :param on_cpus:  taskset the binary to these CPUs. This may be a single ``int`` (in which | ||||
|           case, it will be interpreted as the mask), a list of ``ints``, in which | ||||
|           case this will be interpreted as the list of cpus, or string, which | ||||
|           will be interpreted as a comma-separated list of cpu ranges, e.g. | ||||
|           ``"0,4-7"``. | ||||
|    :param as_root: Specify whether the command should be run as root | ||||
|    :param timeout: If this is specified and invocation does not terminate within this number  | ||||
|            of seconds, an exception will be raised. | ||||
|  | ||||
| .. method:: Target.kick_off(command [, as_root]) | ||||
|  | ||||
|    Kick off the specified command on the target and return immediately. Unlike | ||||
|    ``background()`` this will not block the connection; on the other hand, there | ||||
|    is not way to know when the command finishes (apart from calling ``ps()``) | ||||
|    or to get its output (unless its redirected into a file that can be pulled | ||||
|    later as part of the command). | ||||
|  | ||||
|    :param command: The command to be executed. | ||||
|    :param as_root: The command will be executed as root. This will fail on | ||||
|        unrooted targets. | ||||
|  | ||||
| .. method:: Target.read_value(path [,kind]) | ||||
|  | ||||
|    Read the value from the specified path. This is primarily intended for | ||||
|    sysfs/procfs/debugfs etc. | ||||
|  | ||||
|    :param path: file to read | ||||
|    :param kind: Optionally, read value will be converted into the specified | ||||
|         kind (which should be a callable that takes exactly one parameter). | ||||
|  | ||||
| .. method:: Target.read_int(self, path) | ||||
|  | ||||
|    Equivalent to ``Target.read_value(path, kind=devlab.utils.types.integer)`` | ||||
|  | ||||
| .. method:: Target.read_bool(self, path) | ||||
|  | ||||
|    Equivalent to ``Target.read_value(path, kind=devlab.utils.types.boolean)`` | ||||
|  | ||||
| .. method:: Target.write_value(path, value [, verify]) | ||||
|  | ||||
|    Write the value to the specified path on the target. This is primarily  | ||||
|    intended for sysfs/procfs/debugfs etc. | ||||
|  | ||||
|    :param path: file to write into | ||||
|    :param value: value to be written | ||||
|    :param verify: If ``True`` (the default) the value will be read back after | ||||
|        it is written to make sure it has been written successfully. This due to  | ||||
|        some sysfs entries silently failing to set the written value without | ||||
|        returning an error code. | ||||
|  | ||||
| .. method:: Target.reset() | ||||
|  | ||||
|    Soft reset the target. Typically, this means executing ``reboot`` on the | ||||
|    target. | ||||
|  | ||||
| .. method:: Target.check_responsive() | ||||
|  | ||||
|    Returns ``True`` if the target appears to be responsive and ``False`` | ||||
|    otherwise. | ||||
|  | ||||
| .. method:: Target.kill(pid[, signal[, as_root]]) | ||||
|  | ||||
|    Kill a process on the target. | ||||
|  | ||||
|    :param pid: PID of the process to be killed. | ||||
|    :param signal: Signal to be used to kill the process. Defaults to | ||||
|        ``signal.SIGTERM``. | ||||
|    :param as_root: If set to ``True``, kill will be issued as root. This will | ||||
|        fail on unrooted targets. | ||||
|  | ||||
| .. method:: Target.killall(name[, signal[, as_root]]) | ||||
|  | ||||
|    Kill all processes with the specified name on the target. Other parameters | ||||
|    are the same as for ``kill()``. | ||||
|  | ||||
| .. method:: Target.get_pids_of(name) | ||||
|  | ||||
|    Return a list of PIDs of all running instances of the specified process. | ||||
|  | ||||
| .. method:: Target.ps() | ||||
|  | ||||
|    Return a list of :class:`PsEntry` instances for all running processes on the | ||||
|    system. | ||||
|  | ||||
| .. method:: Target.file_exists(self, filepath) | ||||
|  | ||||
|    Returns ``True`` if the specified path exists on the target and ``False`` | ||||
|    otherwise. | ||||
|  | ||||
| .. method:: Target.list_file_systems() | ||||
|  | ||||
|    Lists file systems mounted on the target. Returns a list of | ||||
|    :class:`FstabEntry`\ s. | ||||
|  | ||||
| .. method:: Target.list_directory(path[, as_root]) | ||||
|  | ||||
|    List (optionally, as root) the contents of the specified directory. Returns a | ||||
|    list of strings. | ||||
|  | ||||
|  | ||||
| .. method:: Target.get_workpath(self, path) | ||||
|  | ||||
|    Convert the specified path to an absolute path relative to | ||||
|    ``working_directory`` on the target. This is a shortcut for | ||||
|    ``t.path.join(t.working_directory, path)`` | ||||
|  | ||||
| .. method:: Target.tempfile([prefix [, suffix]]) | ||||
|  | ||||
|    Get a path to a temporary file (optionally, with the specified prefix and/or | ||||
|    suffix) on the target. | ||||
|  | ||||
| .. method:: Target.remove(path[, as_root]) | ||||
|  | ||||
|    Delete the specified path on the target. Will work on files and directories. | ||||
|  | ||||
| .. method:: Target.core_cpus(core) | ||||
|  | ||||
|    Return a list of numeric cpu IDs corresponding to the specified core name. | ||||
|  | ||||
| .. method:: Target.list_online_cpus([core]) | ||||
|  | ||||
|    Return a list of numeric cpu IDs for all online CPUs (optionally, only for | ||||
|    CPUs corresponding to the specified core). | ||||
|  | ||||
| .. method:: Target.list_offline_cpus([core]) | ||||
|  | ||||
|    Return a list of numeric cpu IDs for all offline CPUs (optionally, only for | ||||
|    CPUs corresponding to the specified core). | ||||
|  | ||||
| .. method:: Target.getenv(variable) | ||||
|  | ||||
|    Return the value of the specified environment variable on the device | ||||
|  | ||||
| .. method:: Target.capture_screen(filepath) | ||||
|  | ||||
|    Take a screenshot on the device and save it to the specified file on the | ||||
|    host. This may not be supported by the target. | ||||
|  | ||||
| .. method:: Target.install(filepath[, timeout[, with_name]]) | ||||
|  | ||||
|    Install an executable on the device. | ||||
|  | ||||
|    :param filepath: path to the executable on the host | ||||
|    :param timeout: Optional timeout (in seconds) for the installation | ||||
|    :param with_name: This may be used to rename the executable on the target | ||||
|  | ||||
| .. method:: Target.uninstall(name) | ||||
|  | ||||
|    Uninstall the specified executable from the target | ||||
|  | ||||
| .. method:: Target.get_installed(name) | ||||
|  | ||||
|    Return the full installation path on the target for the specified executable, | ||||
|    or ``None`` if the executable is not installed. | ||||
|  | ||||
| .. method:: Target.which(name) | ||||
|  | ||||
|    Alias for ``get_installed()`` | ||||
|  | ||||
| .. method:: Target.is_installed(name) | ||||
|  | ||||
|    Returns ``True`` if an executable with the specified name is installed on the | ||||
|    target and ``False`` other wise. | ||||
|  | ||||
							
								
								
									
										89
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| import warnings | ||||
| from itertools import chain | ||||
|  | ||||
| try: | ||||
|     from setuptools import setup | ||||
| except ImportError: | ||||
|     from distutils.core import setup | ||||
|  | ||||
|  | ||||
| devlib_dir = os.path.join(os.path.dirname(__file__), 'devlib') | ||||
|  | ||||
| sys.path.insert(0, os.path.join(devlib_dir, 'core')) | ||||
|  | ||||
| # happends if falling back to distutils | ||||
| warnings.filterwarnings('ignore', "Unknown distribution option: 'install_requires'") | ||||
| warnings.filterwarnings('ignore', "Unknown distribution option: 'extras_require'") | ||||
|  | ||||
| try: | ||||
|     os.remove('MANIFEST') | ||||
| except OSError: | ||||
|     pass | ||||
|  | ||||
| packages = [] | ||||
| data_files = {} | ||||
| source_dir = os.path.dirname(__file__) | ||||
| for root, dirs, files in os.walk(devlib_dir): | ||||
|     rel_dir = os.path.relpath(root, source_dir) | ||||
|     data = [] | ||||
|     if '__init__.py' in files: | ||||
|         for f in files: | ||||
|             if os.path.splitext(f)[1] not in ['.py', '.pyc', '.pyo']: | ||||
|                 data.append(f) | ||||
|         package_name = rel_dir.replace(os.sep, '.') | ||||
|         package_dir = root | ||||
|         packages.append(package_name) | ||||
|         data_files[package_name] = data | ||||
|     else: | ||||
|         # use previous package name | ||||
|         filepaths = [os.path.join(root, f) for f in files] | ||||
|         data_files[package_name].extend([os.path.relpath(f, package_dir) for f in filepaths]) | ||||
|  | ||||
| params = dict( | ||||
|     name='devlib', | ||||
|     description='A framework for automating workload execution and measurment collection on ARM devices.', | ||||
|     version='0.0.1', | ||||
|     packages=packages, | ||||
|     package_data=data_files, | ||||
|     url='N/A', | ||||
|     license='Apache v2', | ||||
|     maintainer='ARM Ltd.', | ||||
|     install_requires=[ | ||||
|         'python-dateutil',  # converting between UTC and local time. | ||||
|         'pexpect>=3.3',  # Send/recieve to/from device | ||||
|         'pyserial',  # Serial port interface | ||||
|     ], | ||||
|     extras_require={ | ||||
|         'daq': ['daqpower'], | ||||
|         'doc': ['sphinx'], | ||||
|     }, | ||||
|     # https://pypi.python.org/pypi?%3Aaction=list_classifiers | ||||
|     classifiers=[ | ||||
|         'Development Status :: 4 - Beta', | ||||
|         'License :: OSI Approved :: Apache Software License', | ||||
|         'Operating System :: POSIX :: Linux', | ||||
|         'Programming Language :: Python :: 2.7', | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| all_extras = list(chain(params['extras_require'].itervalues())) | ||||
| params['extras_require']['full'] = all_extras | ||||
|  | ||||
| setup(**params) | ||||
							
								
								
									
										14
									
								
								src/netstats/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/netstats/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|       package="com.arm.devlib.netstats" | ||||
|       android:versionCode="1" | ||||
|       android:versionName="1.0"> | ||||
|     <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> | ||||
|         <service android:name="com.arm.devlib.netstats.TrafficMetricsService" android:exported="true" android:enabled="true"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="com.arm.devlib.netstats.TrafficMetricsService" /> | ||||
|             </intent-filter> | ||||
|         </service> | ||||
|     </application> | ||||
|     <uses-feature android:name="android.hardware.touchscreen" android:required="false" /> | ||||
| </manifest> | ||||
							
								
								
									
										8
									
								
								src/netstats/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										8
									
								
								src/netstats/build.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| set -e | ||||
| THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) | ||||
| pushd $THIS_DIR | ||||
| ant release | ||||
| jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android -keypass android $THIS_DIR/bin/netstats-*.apk androiddebugkey | ||||
| cp $THIS_DIR/bin/netstats-*.apk $THIS_DIR/../../devlib/instrument/netstats/netstats.apk | ||||
| ant clean | ||||
| popd | ||||
							
								
								
									
										92
									
								
								src/netstats/build.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/netstats/build.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project name="netstats" default="help"> | ||||
|  | ||||
|     <!-- The local.properties file is created and updated by the 'android' tool. | ||||
|          It contains the path to the SDK. It should *NOT* be checked into | ||||
|          Version Control Systems. --> | ||||
|     <property file="local.properties" /> | ||||
|  | ||||
|     <!-- The ant.properties file can be created by you. It is only edited by the | ||||
|          'android' tool to add properties to it. | ||||
|          This is the place to change some Ant specific build properties. | ||||
|          Here are some properties you may want to change/update: | ||||
|  | ||||
|          source.dir | ||||
|              The name of the source directory. Default is 'src'. | ||||
|          out.dir | ||||
|              The name of the output directory. Default is 'bin'. | ||||
|  | ||||
|          For other overridable properties, look at the beginning of the rules | ||||
|          files in the SDK, at tools/ant/build.xml | ||||
|  | ||||
|          Properties related to the SDK location or the project target should | ||||
|          be updated using the 'android' tool with the 'update' action. | ||||
|  | ||||
|          This file is an integral part of the build system for your | ||||
|          application and should be checked into Version Control Systems. | ||||
|  | ||||
|          --> | ||||
|     <property file="ant.properties" /> | ||||
|  | ||||
|     <!-- if sdk.dir was not set from one of the property file, then | ||||
|          get it from the ANDROID_HOME env var. | ||||
|          This must be done before we load project.properties since | ||||
|          the proguard config can use sdk.dir --> | ||||
|     <property environment="env" /> | ||||
|     <condition property="sdk.dir" value="${env.ANDROID_HOME}"> | ||||
|         <isset property="env.ANDROID_HOME" /> | ||||
|     </condition> | ||||
|  | ||||
|     <!-- The project.properties file is created and updated by the 'android' | ||||
|          tool, as well as ADT. | ||||
|  | ||||
|          This contains project specific properties such as project target, and library | ||||
|          dependencies. Lower level build properties are stored in ant.properties | ||||
|          (or in .classpath for Eclipse projects). | ||||
|  | ||||
|          This file is an integral part of the build system for your | ||||
|          application and should be checked into Version Control Systems. --> | ||||
|     <loadproperties srcFile="project.properties" /> | ||||
|  | ||||
|     <!-- quick check on sdk.dir --> | ||||
|     <fail | ||||
|             message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." | ||||
|             unless="sdk.dir" | ||||
|     /> | ||||
|  | ||||
|     <!-- | ||||
|         Import per project custom build rules if present at the root of the project. | ||||
|         This is the place to put custom intermediary targets such as: | ||||
|             -pre-build | ||||
|             -pre-compile | ||||
|             -post-compile (This is typically used for code obfuscation. | ||||
|                            Compiled code location: ${out.classes.absolute.dir} | ||||
|                            If this is not done in place, override ${out.dex.input.absolute.dir}) | ||||
|             -post-package | ||||
|             -post-build | ||||
|             -pre-clean | ||||
|     --> | ||||
|     <import file="custom_rules.xml" optional="true" /> | ||||
|  | ||||
|     <!-- Import the actual build file. | ||||
|  | ||||
|          To customize existing targets, there are two options: | ||||
|          - Customize only one target: | ||||
|              - copy/paste the target into this file, *before* the | ||||
|                <import> task. | ||||
|              - customize it to your needs. | ||||
|          - Customize the whole content of build.xml | ||||
|              - copy/paste the content of the rules files (minus the top node) | ||||
|                into this file, replacing the <import> task. | ||||
|              - customize to your needs. | ||||
|  | ||||
|          *********************** | ||||
|          ****** IMPORTANT ****** | ||||
|          *********************** | ||||
|          In all cases you must update the value of version-tag below to read 'custom' instead of an integer, | ||||
|          in order to avoid having your file be overridden by tools such as "android update project" | ||||
|     --> | ||||
|     <!-- version-tag: 1 --> | ||||
|     <import file="${sdk.dir}/tools/ant/build.xml" /> | ||||
|  | ||||
| </project> | ||||
							
								
								
									
										20
									
								
								src/netstats/proguard-project.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/netstats/proguard-project.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| # To enable ProGuard in your project, edit project.properties | ||||
| # to define the proguard.config property as described in that file. | ||||
| # | ||||
| # Add project specific ProGuard rules here. | ||||
| # By default, the flags in this file are appended to flags specified | ||||
| # in ${sdk.dir}/tools/proguard/proguard-android.txt | ||||
| # You can edit the include path and order by changing the ProGuard | ||||
| # include property in project.properties. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
|  | ||||
| # Add any project specific keep options here: | ||||
|  | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
							
								
								
									
										14
									
								
								src/netstats/project.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/netstats/project.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| # This file is automatically generated by Android Tools. | ||||
| # Do not modify this file -- YOUR CHANGES WILL BE ERASED! | ||||
| # | ||||
| # This file must be checked in Version Control Systems. | ||||
| # | ||||
| # To customize properties used by the Ant build system edit | ||||
| # "ant.properties", and override values to adapt the script to your | ||||
| # project structure. | ||||
| # | ||||
| # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): | ||||
| #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt | ||||
|  | ||||
| # Project target. | ||||
| target=Google Inc.:Google APIs (x86 System Image):19 | ||||
							
								
								
									
										
											BIN
										
									
								
								src/netstats/res/drawable-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/netstats/res/drawable-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/netstats/res/drawable-ldpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/netstats/res/drawable-ldpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/netstats/res/drawable-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/netstats/res/drawable-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/netstats/res/drawable-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/netstats/res/drawable-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										13
									
								
								src/netstats/res/layout/main.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/netstats/res/layout/main.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="fill_parent" | ||||
|     > | ||||
| <TextView | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:text="Hello World, NetStats" | ||||
|     /> | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										4
									
								
								src/netstats/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/netstats/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="app_name">NetStats</string> | ||||
| </resources> | ||||
| @@ -0,0 +1,124 @@ | ||||
| package com.arm.devlib.netstats; | ||||
|  | ||||
| import java.lang.InterruptedException; | ||||
| import java.lang.System; | ||||
| import java.lang.Thread; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.HashMap; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.app.IntentService; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.net.TrafficStats; | ||||
| import android.os.Bundle; | ||||
| import android.util.Log; | ||||
|  | ||||
| class TrafficPoller implements Runnable { | ||||
|  | ||||
|     private String tag; | ||||
|     private int period; | ||||
|     private PackageManager pm; | ||||
|     private static String TAG = "TrafficMetrics"; | ||||
|     private List<String> packageNames; | ||||
|     private Map<String, Map<String, Long>> previousValues; | ||||
|  | ||||
|     public TrafficPoller(String tag, PackageManager pm, int period, List<String> packages) { | ||||
|         this.tag = tag; | ||||
|         this.pm = pm; | ||||
|         this.period = period; | ||||
|         this.packageNames = packages; | ||||
|         this.previousValues = new HashMap<String, Map<String, Long>>(); | ||||
|     } | ||||
|  | ||||
|     public void run() { | ||||
|         try { | ||||
|             while (true) { | ||||
|                 Thread.sleep(this.period); | ||||
|                 getPakagesInfo(); | ||||
|                 if (Thread.interrupted()) { | ||||
|                     throw new InterruptedException(); | ||||
|                 } | ||||
|             } | ||||
|         } catch (InterruptedException e) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void getPakagesInfo() { | ||||
|         List<ApplicationInfo> apps; | ||||
|         if (this.packageNames == null) { | ||||
|             apps = pm.getInstalledApplications(0); | ||||
|             for (ApplicationInfo app : apps) { | ||||
|             } | ||||
|         } else { | ||||
|             apps = new ArrayList<ApplicationInfo>(); | ||||
|             for (String packageName : packageNames) { | ||||
|                 try { | ||||
|                     ApplicationInfo info = pm.getApplicationInfo(packageName,  0); | ||||
|                     apps.add(info); | ||||
|                 } catch (PackageManager.NameNotFoundException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for (ApplicationInfo appInfo : apps) { | ||||
|             int uid = appInfo.uid; | ||||
|             String name = appInfo.packageName; | ||||
|             long time =  System.currentTimeMillis(); | ||||
|             long received = TrafficStats.getUidRxBytes(uid); | ||||
|             long sent = TrafficStats.getUidTxBytes(uid); | ||||
|  | ||||
|             if (!this.previousValues.containsKey(name)) { | ||||
|                 this.previousValues.put(name, new HashMap<String, Long>()); | ||||
|                 this.previousValues.get(name).put("sent", sent); | ||||
|                 this.previousValues.get(name).put("received", received); | ||||
|                 Log.i(this.tag, String.format("INITIAL \"%s\" TX: %d RX: %d",  | ||||
|                                               name, sent, received)); | ||||
|             } else { | ||||
|                 long previosSent = this.previousValues.get(name).put("sent", sent); | ||||
|                 long previosReceived = this.previousValues.get(name).put("received", received); | ||||
|                 Log.i(this.tag, String.format("%d \"%s\" TX: %d RX: %d",  | ||||
|                                               time, name,  | ||||
|                                               sent - previosSent,  | ||||
|                                               received - previosReceived)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| public class TrafficMetricsService extends IntentService { | ||||
|  | ||||
|     private static String TAG = "TrafficMetrics"; | ||||
|     private Thread thread; | ||||
|     private static int defaultPollingPeriod = 5000; | ||||
|  | ||||
|     public TrafficMetricsService() { | ||||
|             super("TrafficMetrics"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onHandleIntent(Intent intent) { | ||||
|         List<String> packages = null; | ||||
|         String runTag = intent.getStringExtra("tag"); | ||||
|         if (runTag == null) { | ||||
|             runTag = TAG; | ||||
|         } | ||||
|         String packagesString = intent.getStringExtra("packages"); | ||||
|         int pollingPeriod = intent.getIntExtra("period", this.defaultPollingPeriod); | ||||
|         if (packagesString != null) { | ||||
|             packages = new ArrayList<String>(Arrays.asList(packagesString.split(","))); | ||||
|         }  | ||||
|  | ||||
|         if (this.thread != null) { | ||||
|             Log.e(runTag, "Attemping to start when monitoring is already in progress."); | ||||
|             return; | ||||
|         } | ||||
|         this.thread = new Thread(new TrafficPoller(runTag, getPackageManager(), pollingPeriod, packages)); | ||||
|         this.thread.start(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/readenergy/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/readenergy/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| # To build: | ||||
| # | ||||
| # CROSS_COMPILE=aarch64-linux-gnu- make | ||||
| # | ||||
| CROSS_COMPILE?=aarch64-linux-gnu- | ||||
| CC=$(CROSS_COMPILE)gcc | ||||
| CFLAGS='-Wl,-static -Wl,-lc' | ||||
|  | ||||
| readenergy: readenergy.c | ||||
| 	$(CC) $(CFLAGS) readenergy.c -o readenergy | ||||
| 	mv readenergy ../../devlib/bin/arm64/readenergy | ||||
							
								
								
									
										345
									
								
								src/readenergy/readenergy.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								src/readenergy/readenergy.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,345 @@ | ||||
| /*    Copyright 2014-2015 ARM Limited | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
| */ | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * readenergy.c  | ||||
|  * | ||||
|  * Reads APB energy registers in Juno and outputs the measurements (converted to appropriate units). | ||||
|  * | ||||
| */ | ||||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <signal.h> | ||||
| #include <sys/mman.h> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/types.h> | ||||
| #include <time.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| // The following values obtained from Juno TRM 2014/03/04 section 4.5 | ||||
|  | ||||
| // Location of APB registers in memory | ||||
| #define APB_BASE_MEMORY 0x1C010000 | ||||
| // APB energy counters start at offset 0xD0 from the base APB address. | ||||
| #define BASE_INDEX 0xD0 / 4 | ||||
| // the one-past last APB counter | ||||
| #define APB_SIZE 0x120 | ||||
|  | ||||
| // Masks specifying the bits that contain the actual counter values | ||||
| #define CMASK 0xFFF | ||||
| #define VMASK 0xFFF | ||||
| #define PMASK 0xFFFFFF | ||||
|  | ||||
| // Sclaing factor (divisor) or getting measured values from counters | ||||
| #define SYS_ADC_CH0_PM1_SYS_SCALE 761 | ||||
| #define SYS_ADC_CH1_PM2_A57_SCALE 381 | ||||
| #define SYS_ADC_CH2_PM3_A53_SCALE 761 | ||||
| #define SYS_ADC_CH3_PM4_GPU_SCALE 381 | ||||
| #define SYS_ADC_CH4_VSYS_SCALE 1622 | ||||
| #define SYS_ADC_CH5_VA57_SCALE 1622 | ||||
| #define SYS_ADC_CH6_VA53_SCALE 1622 | ||||
| #define SYS_ADC_CH7_VGPU_SCALE 1622 | ||||
| #define SYS_POW_CH04_SYS_SCALE (SYS_ADC_CH0_PM1_SYS_SCALE * SYS_ADC_CH4_VSYS_SCALE) | ||||
| #define SYS_POW_CH15_A57_SCALE (SYS_ADC_CH1_PM2_A57_SCALE * SYS_ADC_CH5_VA57_SCALE) | ||||
| #define SYS_POW_CH26_A53_SCALE (SYS_ADC_CH2_PM3_A53_SCALE * SYS_ADC_CH6_VA53_SCALE) | ||||
| #define SYS_POW_CH37_GPU_SCALE (SYS_ADC_CH3_PM4_GPU_SCALE * SYS_ADC_CH7_VGPU_SCALE) | ||||
| #define SYS_ENM_CH0_SYS_SCALE 12348030000 | ||||
| #define SYS_ENM_CH1_A57_SCALE 6174020000 | ||||
| #define SYS_ENM_CH0_A53_SCALE 12348030000 | ||||
| #define SYS_ENM_CH0_GPU_SCALE 6174020000 | ||||
|  | ||||
| // Original values prior to re-callibrations. | ||||
| /*#define SYS_ADC_CH0_PM1_SYS_SCALE 819.2*/ | ||||
| /*#define SYS_ADC_CH1_PM2_A57_SCALE 409.6*/ | ||||
| /*#define SYS_ADC_CH2_PM3_A53_SCALE 819.2*/ | ||||
| /*#define SYS_ADC_CH3_PM4_GPU_SCALE 409.6*/ | ||||
| /*#define SYS_ADC_CH4_VSYS_SCALE 1638.4*/ | ||||
| /*#define SYS_ADC_CH5_VA57_SCALE 1638.4*/ | ||||
| /*#define SYS_ADC_CH6_VA53_SCALE 1638.4*/ | ||||
| /*#define SYS_ADC_CH7_VGPU_SCALE 1638.4*/ | ||||
| /*#define SYS_POW_CH04_SYS_SCALE (SYS_ADC_CH0_PM1_SYS_SCALE * SYS_ADC_CH4_VSYS_SCALE)*/ | ||||
| /*#define SYS_POW_CH15_A57_SCALE (SYS_ADC_CH1_PM2_A57_SCALE * SYS_ADC_CH5_VA57_SCALE)*/ | ||||
| /*#define SYS_POW_CH26_A53_SCALE (SYS_ADC_CH2_PM3_A53_SCALE * SYS_ADC_CH6_VA53_SCALE)*/ | ||||
| /*#define SYS_POW_CH37_GPU_SCALE (SYS_ADC_CH3_PM4_GPU_SCALE * SYS_ADC_CH7_VGPU_SCALE)*/ | ||||
| /*#define SYS_ENM_CH0_SYS_SCALE 13421772800.0*/ | ||||
| /*#define SYS_ENM_CH1_A57_SCALE 6710886400.0*/ | ||||
| /*#define SYS_ENM_CH0_A53_SCALE 13421772800.0*/ | ||||
| /*#define SYS_ENM_CH0_GPU_SCALE 6710886400.0*/ | ||||
|  | ||||
| // Ignore individual errors but if see too many, abort. | ||||
| #define ERROR_THRESHOLD 10 | ||||
|  | ||||
| // Default counter poll period (in milliseconds). | ||||
| #define DEFAULT_PERIOD 100 | ||||
|  | ||||
| // A single reading from the energy meter. The values are the proper readings converted | ||||
| // to appropriate units (e.g. Watts for power); they are *not* raw counter values. | ||||
| struct reading | ||||
| { | ||||
| 	double sys_adc_ch0_pm1_sys; | ||||
| 	double sys_adc_ch1_pm2_a57; | ||||
| 	double sys_adc_ch2_pm3_a53; | ||||
| 	double sys_adc_ch3_pm4_gpu; | ||||
| 	double sys_adc_ch4_vsys; | ||||
| 	double sys_adc_ch5_va57; | ||||
| 	double sys_adc_ch6_va53; | ||||
| 	double sys_adc_ch7_vgpu; | ||||
| 	double sys_pow_ch04_sys; | ||||
| 	double sys_pow_ch15_a57; | ||||
| 	double sys_pow_ch26_a53; | ||||
| 	double sys_pow_ch37_gpu; | ||||
| 	double sys_enm_ch0_sys; | ||||
| 	double sys_enm_ch1_a57; | ||||
| 	double sys_enm_ch0_a53; | ||||
| 	double sys_enm_ch0_gpu; | ||||
| }; | ||||
|  | ||||
| inline uint64_t join_64bit_register(uint32_t *buffer, int index) | ||||
| { | ||||
| 	uint64_t result = 0; | ||||
| 	result |= buffer[index]; | ||||
| 	result |= (uint64_t)(buffer[index+1]) << 32; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| int nsleep(const struct timespec *req, struct timespec *rem) | ||||
| { | ||||
| 	struct timespec temp_rem; | ||||
| 	if (nanosleep(req, rem) == -1) | ||||
| 	{ | ||||
| 		if (errno == EINTR) | ||||
| 		{ | ||||
| 			nsleep(rem, &temp_rem); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			return errno; | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| void print_help() | ||||
| { | ||||
| 	fprintf(stderr, "Usage: readenergy [-t PERIOD] -o OUTFILE\n\n" | ||||
| 			"Read Juno energy counters every PERIOD milliseconds, writing them\n" | ||||
| 			"to OUTFILE in CSV format until SIGTERM is received.\n\n" | ||||
| 			"Parameters:\n" | ||||
| 			"	PERIOD is the counter poll period in milliseconds.\n" | ||||
| 			"	       (Defaults to 100 milliseconds.)\n" | ||||
| 			"	OUTFILE is the output file path\n"); | ||||
| } | ||||
|  | ||||
| // debugging only... | ||||
| inline void dprint(char *msg) | ||||
| { | ||||
| 	fprintf(stderr, "%s\n", msg); | ||||
| 	sync(); | ||||
| } | ||||
|  | ||||
| // -------------------------------------- config ---------------------------------------------------- | ||||
|  | ||||
| struct config | ||||
| { | ||||
| 	struct timespec period; | ||||
| 	char *output_file; | ||||
| }; | ||||
|  | ||||
| void config_init_period_from_millis(struct config *this, long millis) | ||||
| { | ||||
| 	this->period.tv_sec = (time_t)(millis / 1000); | ||||
| 	this->period.tv_nsec = (millis % 1000) * 1000000; | ||||
| } | ||||
|  | ||||
| void config_init(struct config *this, int argc, char *argv[]) | ||||
| { | ||||
| 	this->output_file = NULL; | ||||
| 	config_init_period_from_millis(this, DEFAULT_PERIOD); | ||||
|  | ||||
| 	int opt; | ||||
| 	while ((opt = getopt(argc, argv, "ht:o:")) != -1) | ||||
| 	{ | ||||
| 		switch(opt) | ||||
| 		{ | ||||
| 			case 't': | ||||
| 				config_init_period_from_millis(this, atol(optarg)); | ||||
| 				break; | ||||
| 			case 'o': | ||||
| 				this->output_file = optarg; | ||||
| 				break; | ||||
| 			case 'h': | ||||
| 				print_help(); | ||||
| 				exit(EXIT_SUCCESS); | ||||
| 				break; | ||||
| 			default: | ||||
| 				fprintf(stderr, "ERROR: Unexpected option %s\n\n", opt); | ||||
| 				print_help(); | ||||
| 				exit(EXIT_FAILURE); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (this->output_file == NULL) | ||||
| 	{ | ||||
| 		fprintf(stderr, "ERROR: Mandatory -o option not specified.\n\n"); | ||||
| 		print_help(); | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // -------------------------------------- /config --------------------------------------------------- | ||||
|  | ||||
| // -------------------------------------- emeter ---------------------------------------------------- | ||||
|  | ||||
| struct emeter | ||||
| { | ||||
| 	int fd; | ||||
| 	FILE *out; | ||||
| 	void *mmap_base; | ||||
| }; | ||||
|  | ||||
| void emeter_init(struct emeter *this, char *outfile) | ||||
| { | ||||
| 	this->out = fopen(outfile, "w"); | ||||
| 	if (this->out == NULL) | ||||
| 	{ | ||||
| 		fprintf(stderr, "ERROR: Could not open output file %s; got %s\n", outfile, strerror(errno)); | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
|  | ||||
|         this->fd = open("/dev/mem", O_RDONLY); | ||||
|         if(this->fd < 0) | ||||
|         { | ||||
|                 fprintf(stderr, "ERROR: Can't open /dev/mem; got %s\n", strerror(errno)); | ||||
| 		fclose(this->out); | ||||
| 		exit(EXIT_FAILURE); | ||||
|         } | ||||
|  | ||||
| 	this->mmap_base = mmap(NULL, APB_SIZE, PROT_READ, MAP_SHARED, this->fd, APB_BASE_MEMORY); | ||||
| 	if (this->mmap_base == MAP_FAILED) | ||||
| 	{ | ||||
| 		fprintf(stderr, "ERROR: mmap failed; got %s\n", strerror(errno)); | ||||
| 		close(this->fd); | ||||
| 		fclose(this->out); | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
|  | ||||
| 	fprintf(this->out, "sys_curr,a57_curr,a53_curr,gpu_curr," | ||||
|  			   "sys_volt,a57_volt,a53_volt,gpu_volt," | ||||
| 			   "sys_pow,a57_pow,a53_pow,gpu_pow," | ||||
| 			   "sys_cenr,a57_cenr,a53_cenr,gpu_cenr\n"); | ||||
| } | ||||
|  | ||||
| void emeter_read_measurements(struct emeter *this, struct reading *reading) | ||||
| { | ||||
| 	uint32_t *buffer = (uint32_t *)this->mmap_base; | ||||
| 	reading->sys_adc_ch0_pm1_sys = (double)(CMASK & buffer[BASE_INDEX+0]) / SYS_ADC_CH0_PM1_SYS_SCALE; | ||||
| 	reading->sys_adc_ch1_pm2_a57 = (double)(CMASK & buffer[BASE_INDEX+1]) / SYS_ADC_CH1_PM2_A57_SCALE; | ||||
| 	reading->sys_adc_ch2_pm3_a53 = (double)(CMASK & buffer[BASE_INDEX+2]) / SYS_ADC_CH2_PM3_A53_SCALE; | ||||
| 	reading->sys_adc_ch3_pm4_gpu = (double)(CMASK & buffer[BASE_INDEX+3]) / SYS_ADC_CH3_PM4_GPU_SCALE; | ||||
| 	reading->sys_adc_ch4_vsys = (double)(VMASK & buffer[BASE_INDEX+4]) / SYS_ADC_CH4_VSYS_SCALE; | ||||
| 	reading->sys_adc_ch5_va57 = (double)(VMASK & buffer[BASE_INDEX+5]) / SYS_ADC_CH5_VA57_SCALE; | ||||
| 	reading->sys_adc_ch6_va53 = (double)(VMASK & buffer[BASE_INDEX+6]) / SYS_ADC_CH6_VA53_SCALE; | ||||
| 	reading->sys_adc_ch7_vgpu = (double)(VMASK & buffer[BASE_INDEX+7]) / SYS_ADC_CH7_VGPU_SCALE; | ||||
| 	reading->sys_pow_ch04_sys = (double)(PMASK & buffer[BASE_INDEX+8]) / SYS_POW_CH04_SYS_SCALE; | ||||
| 	reading->sys_pow_ch15_a57 = (double)(PMASK & buffer[BASE_INDEX+9]) / SYS_POW_CH15_A57_SCALE; | ||||
| 	reading->sys_pow_ch26_a53 = (double)(PMASK & buffer[BASE_INDEX+10]) / SYS_POW_CH26_A53_SCALE; | ||||
| 	reading->sys_pow_ch37_gpu = (double)(PMASK & buffer[BASE_INDEX+11]) / SYS_POW_CH37_GPU_SCALE; | ||||
| 	reading->sys_enm_ch0_sys = (double)join_64bit_register(buffer, BASE_INDEX+12) / SYS_ENM_CH0_SYS_SCALE; | ||||
| 	reading->sys_enm_ch1_a57 = (double)join_64bit_register(buffer, BASE_INDEX+14) / SYS_ENM_CH1_A57_SCALE; | ||||
| 	reading->sys_enm_ch0_a53 = (double)join_64bit_register(buffer, BASE_INDEX+16) / SYS_ENM_CH0_A53_SCALE; | ||||
| 	reading->sys_enm_ch0_gpu = (double)join_64bit_register(buffer, BASE_INDEX+18) / SYS_ENM_CH0_GPU_SCALE; | ||||
| } | ||||
|  | ||||
| void emeter_take_reading(struct emeter *this) | ||||
| { | ||||
| 	static struct reading reading; | ||||
| 	int error_count = 0; | ||||
| 	emeter_read_measurements(this, &reading); | ||||
| 	int ret = fprintf(this->out, "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n", | ||||
| 			reading.sys_adc_ch0_pm1_sys, | ||||
| 			reading.sys_adc_ch1_pm2_a57, | ||||
| 			reading.sys_adc_ch2_pm3_a53, | ||||
| 			reading.sys_adc_ch3_pm4_gpu, | ||||
| 			reading.sys_adc_ch4_vsys, | ||||
| 			reading.sys_adc_ch5_va57, | ||||
| 			reading.sys_adc_ch6_va53, | ||||
| 			reading.sys_adc_ch7_vgpu, | ||||
| 			reading.sys_pow_ch04_sys, | ||||
| 			reading.sys_pow_ch15_a57, | ||||
| 			reading.sys_pow_ch26_a53, | ||||
| 			reading.sys_pow_ch37_gpu, | ||||
| 			reading.sys_enm_ch0_sys, | ||||
| 			reading.sys_enm_ch1_a57, | ||||
| 			reading.sys_enm_ch0_a53, | ||||
| 			reading.sys_enm_ch0_gpu); | ||||
| 	if (ret < 0) | ||||
| 	{ | ||||
| 		fprintf(stderr, "ERROR: while writing a meter reading: %s\n", strerror(errno)); | ||||
| 		if (++error_count > ERROR_THRESHOLD) | ||||
| 			exit(EXIT_FAILURE); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void emeter_finalize(struct emeter *this) | ||||
| { | ||||
| 	if (munmap(this->mmap_base, APB_SIZE) == -1)  | ||||
| 	{ | ||||
| 		// Report the error but don't bother doing anything else, as we're not gonna do  | ||||
| 		// anything with emeter after this point anyway. | ||||
| 		fprintf(stderr, "ERROR: munmap failed; got %s\n", strerror(errno)); | ||||
| 	} | ||||
| 	close(this->fd); | ||||
| 	fclose(this->out); | ||||
| } | ||||
|  | ||||
| // -------------------------------------- /emeter ---------------------------------------------------- | ||||
|  | ||||
| int done = 0; | ||||
|  | ||||
| void term_handler(int signum) | ||||
| { | ||||
| 	done = 1; | ||||
| } | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
| 	struct sigaction action; | ||||
| 	memset(&action, 0, sizeof(struct sigaction)); | ||||
| 	action.sa_handler = term_handler; | ||||
| 	sigaction(SIGTERM, &action, NULL); | ||||
|  | ||||
| 	struct config config; | ||||
| 	struct emeter emeter; | ||||
| 	config_init(&config, argc, argv); | ||||
| 	emeter_init(&emeter, config.output_file); | ||||
|  | ||||
| 	struct timespec remaining; | ||||
| 	while (!done)  | ||||
| 	{ | ||||
| 		emeter_take_reading(&emeter); | ||||
| 		nsleep(&config.period, &remaining); | ||||
| 	} | ||||
|  | ||||
| 	emeter_finalize(&emeter); | ||||
| 	return EXIT_SUCCESS; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user