├── .circleci └── config.yml ├── .gitignore ├── .pylintrc ├── LICENSE ├── README.md ├── _config.yml ├── docs ├── API.rst ├── Makefile ├── Outputs_API.rst ├── conf.py ├── generate.sh ├── index.rst ├── make.bat ├── quickstart.rst └── requirements.txt ├── pyRAPL ├── __init__.py ├── device.py ├── device_api.py ├── exception.py ├── measurement.py ├── outputs │ ├── __init__.py │ ├── buffered_output.py │ ├── csvoutput.py │ ├── dataframeoutput.py │ ├── mongooutput.py │ ├── output.py │ └── printoutput.py ├── pyRAPL.py ├── result.py └── sensor.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── acceptation ├── test_context_measure.py ├── test_decorator_measureit.py └── test_normal_measure_bench.py ├── integration └── outputs │ ├── test_csv_output.py │ ├── test_mongo_output.py │ └── test_pandas_output.py ├── unit ├── outputs │ └── test_print_output.py ├── test_device_api.py ├── test_results.py └── test_sensor.py └── utils.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # PyRAPL CircleCI configuration file 2 | version: 2.1 3 | jobs: 4 | build: 5 | docker: 6 | # Language image for build and unit tests 7 | - image: circleci/python:3.7 8 | 9 | working_directory: ~/repo 10 | 11 | steps: 12 | - checkout 13 | 14 | # Download and cache dependencies 15 | - restore_cache: 16 | keys: 17 | - v1-dependencies-{{ checksum "setup.cfg" }} 18 | # fallback to using the latest cache if no exact match is found 19 | - v1-dependencies- 20 | 21 | - run: 22 | name: install dependencies 23 | command: | 24 | python3 -m venv venv 25 | . venv/bin/activate 26 | pip3 install -e ".[mongodb, pandas]" 27 | 28 | - save_cache: 29 | paths: 30 | - ./venv 31 | key: v1-dependencies-{{ checksum "setup.cfg" }} 32 | 33 | # Run unit and integration tests 34 | - run: 35 | name: run tests 36 | command: | 37 | . venv/bin/activate 38 | python3 setup.py test 39 | 40 | publish-release: 41 | docker: 42 | - image: circleci/python:3.7 43 | 44 | steps: 45 | - checkout 46 | 47 | - run: 48 | name: check git tag with package version 49 | command: | 50 | python3 -c "import os, pyRAPL; exit(os.environ.get('CIRCLE_TAG', '?')[1:] != pyRAPL .__version__)" 51 | 52 | - run: 53 | name: prepare environment 54 | command: | 55 | python3 -m venv venv 56 | . venv/bin/activate 57 | pip3 install -U pip twine 58 | 59 | - run: 60 | name: init .pypirc 61 | command: | 62 | echo -e "[pypi]" >> ~/.pypirc 63 | echo -e "username = powerapi" >> ~/.pypirc 64 | echo -e "password = $PYPI_PASSWORD" >> ~/.pypirc 65 | 66 | - run: 67 | name: generate package 68 | command: | 69 | python3 setup.py sdist bdist_wheel 70 | 71 | - run: 72 | name: upload to pypi 73 | command: | 74 | . venv/bin/activate 75 | twine upload dist/* 76 | 77 | workflows: 78 | version: 2 79 | build-n-publish: 80 | jobs: 81 | - build: 82 | filters: 83 | tags: 84 | only: /.*/ 85 | 86 | - publish-release: 87 | requires: 88 | - build 89 | filters: 90 | branches: 91 | ignore: /.*/ 92 | tags: 93 | only: /^v.*/ 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python ### 2 | 3 | ### my local tests ### 4 | test.py 5 | 6 | #vscode settings 7 | 8 | .vscode 9 | 10 | # virtual enviromenenmt 11 | venv 12 | 13 | 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # C extensions 20 | *.so 21 | chiko.py 22 | # Distribution / packaging 23 | .Python 24 | build/ 25 | develop-eggs/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # IPython 75 | profile_default/ 76 | ipython_config.py 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # Environments 82 | .env 83 | .venv 84 | env/ 85 | venv/ 86 | ENV/ 87 | env.bak/ 88 | venv.bak/ 89 | 90 | # mypy 91 | .mypy_cache/ 92 | .dmypy.json 93 | dmypy.json 94 | 95 | # Pyre type checker 96 | .pyre/ 97 | 98 | ### Emacs ### 99 | *~ 100 | \#*\# 101 | /.emacs.desktop 102 | /.emacs.desktop.lock 103 | *.elc 104 | auto-save-list 105 | tramp 106 | .\#* 107 | 108 | # Org-mode 109 | .org-id-locations 110 | *_archive 111 | 112 | # flymake-mode 113 | *_flymake.* 114 | 115 | # eshell files 116 | /eshell/history 117 | /eshell/lastdir 118 | 119 | # elpa packages 120 | /elpa/ 121 | 122 | # reftex files 123 | *.rel 124 | 125 | # AUCTeX auto folder 126 | /auto/ 127 | 128 | # cask packages 129 | .cask/ 130 | dist/ 131 | 132 | # Flycheck 133 | flycheck_*.el 134 | 135 | # server auth directory 136 | /server/ 137 | 138 | # projectiles files 139 | .projectile 140 | 141 | # directory configuration 142 | .dir-locals.el 143 | 144 | # network security 145 | /network-security.data 146 | 147 | ### Vim ### 148 | # Swap 149 | [._]*.s[a-v][a-z] 150 | [._]*.sw[a-p] 151 | [._]s[a-rt-v][a-z] 152 | [._]ss[a-gi-z] 153 | [._]sw[a-p] 154 | 155 | # Session 156 | Session.vim 157 | 158 | # Temporary 159 | .netrwhist 160 | # Auto-generated tag files 161 | tags 162 | # Persistent undo 163 | [._]*.un~ 164 | 165 | ### IntelliJ IDEA ### 166 | .idea/ 167 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 21 | # number of processors available to use. 22 | jobs=1 23 | 24 | # Control the amount of potential inferred values when inferring a single 25 | # object. This can help the performance when dealing with large functions or 26 | # complex, nested conditions. 27 | limit-inference-results=100 28 | 29 | # List of plugins (as comma separated values of python modules names) to load, 30 | # usually to register additional checkers. 31 | load-plugins= 32 | 33 | # Pickle collected data for later comparisons. 34 | persistent=yes 35 | 36 | # Specify a configuration file. 37 | #rcfile= 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=R0903, R0902, C0200 64 | print-statement, 65 | parameter-unpacking, 66 | unpacking-in-except, 67 | old-raise-syntax, 68 | backtick, 69 | long-suffix, 70 | old-ne-operator, 71 | old-octal-literal, 72 | import-star-module-level, 73 | non-ascii-bytes-literal, 74 | raw-checker-failed, 75 | bad-inline-option, 76 | locally-disabled, 77 | file-ignored, 78 | suppressed-message, 79 | useless-suppression, 80 | deprecated-pragma, 81 | use-symbolic-message-instead, 82 | apply-builtin, 83 | basestring-builtin, 84 | buffer-builtin, 85 | cmp-builtin, 86 | coerce-builtin, 87 | execfile-builtin, 88 | file-builtin, 89 | long-builtin, 90 | raw_input-builtin, 91 | reduce-builtin, 92 | standarderror-builtin, 93 | unicode-builtin, 94 | xrange-builtin, 95 | coerce-method, 96 | delslice-method, 97 | getslice-method, 98 | setslice-method, 99 | no-absolute-import, 100 | old-division, 101 | dict-iter-method, 102 | dict-view-method, 103 | next-method-called, 104 | metaclass-assignment, 105 | indexing-exception, 106 | raising-string, 107 | reload-builtin, 108 | oct-method, 109 | hex-method, 110 | nonzero-method, 111 | cmp-method, 112 | input-builtin, 113 | round-builtin, 114 | intern-builtin, 115 | unichr-builtin, 116 | map-builtin-not-iterating, 117 | zip-builtin-not-iterating, 118 | range-builtin-not-iterating, 119 | filter-builtin-not-iterating, 120 | using-cmp-argument, 121 | eq-without-hash, 122 | div-method, 123 | idiv-method, 124 | rdiv-method, 125 | exception-message-attribute, 126 | invalid-str-codec, 127 | sys-max-int, 128 | bad-python3-import, 129 | deprecated-string-function, 130 | deprecated-str-translate-call, 131 | deprecated-itertools-function, 132 | deprecated-types-field, 133 | next-method-defined, 134 | dict-items-not-iterating, 135 | dict-keys-not-iterating, 136 | dict-values-not-iterating, 137 | deprecated-operator-function, 138 | deprecated-urllib-function, 139 | xreadlines-attribute, 140 | deprecated-sys-function, 141 | exception-escape, 142 | comprehension-escape 143 | 144 | # Enable the message, report, category or checker with the given id(s). You can 145 | # either give multiple identifier separated by comma (,) or put this option 146 | # multiple time (only on the command line, not in the configuration file where 147 | # it should appear only once). See also the "--disable" option for examples. 148 | enable=c-extension-no-member 149 | 150 | 151 | [REPORTS] 152 | 153 | # Python expression which should return a note less than 10 (10 is the highest 154 | # note). You have access to the variables errors warning, statement which 155 | # respectively contain the number of errors / warnings messages and the total 156 | # number of statements analyzed. This is used by the global evaluation report 157 | # (RP0004). 158 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 159 | 160 | # Template used to display messages. This is a python new-style format string 161 | # used to format the message information. See doc for all details. 162 | #msg-template= 163 | 164 | # Set the output format. Available formats are text, parseable, colorized, json 165 | # and msvs (visual studio). You can also give a reporter class, e.g. 166 | # mypackage.mymodule.MyReporterClass. 167 | output-format=text 168 | 169 | # Tells whether to display a full report or only the messages. 170 | reports=no 171 | 172 | # Activate the evaluation score. 173 | score=yes 174 | 175 | 176 | [REFACTORING] 177 | 178 | # Maximum number of nested blocks for function / method body 179 | max-nested-blocks=5 180 | 181 | # Complete name of functions that never returns. When checking for 182 | # inconsistent-return-statements if a never returning function is called then 183 | # it will be considered as an explicit return statement and no message will be 184 | # printed. 185 | never-returning-functions=sys.exit 186 | 187 | 188 | [TYPECHECK] 189 | 190 | # List of decorators that produce context managers, such as 191 | # contextlib.contextmanager. Add to this list to register other decorators that 192 | # produce valid context managers. 193 | contextmanager-decorators=contextlib.contextmanager 194 | 195 | # List of members which are set dynamically and missed by pylint inference 196 | # system, and so shouldn't trigger E1101 when accessed. Python regular 197 | # expressions are accepted. 198 | generated-members= 199 | 200 | # Tells whether missing members accessed in mixin class should be ignored. A 201 | # mixin class is detected if its name ends with "mixin" (case insensitive). 202 | ignore-mixin-members=yes 203 | 204 | # Tells whether to warn about missing members when the owner of the attribute 205 | # is inferred to be None. 206 | ignore-none=yes 207 | 208 | # This flag controls whether pylint should warn about no-member and similar 209 | # checks whenever an opaque object is returned when inferring. The inference 210 | # can return multiple potential results while evaluating a Python object, but 211 | # some branches might not be evaluated, which results in partial inference. In 212 | # that case, it might be useful to still emit no-member and other checks for 213 | # the rest of the inferred objects. 214 | ignore-on-opaque-inference=yes 215 | 216 | # List of class names for which member attributes should not be checked (useful 217 | # for classes with dynamically set attributes). This supports the use of 218 | # qualified names. 219 | ignored-classes=optparse.Values,thread._local,_thread._local 220 | 221 | # List of module names for which member attributes should not be checked 222 | # (useful for modules/projects where namespaces are manipulated during runtime 223 | # and thus existing member attributes cannot be deduced by static analysis. It 224 | # supports qualified module names, as well as Unix pattern matching. 225 | ignored-modules= 226 | 227 | # Show a hint with possible names when a member name was not found. The aspect 228 | # of finding the hint is based on edit distance. 229 | missing-member-hint=yes 230 | 231 | # The minimum edit distance a name should have in order to be considered a 232 | # similar match for a missing member name. 233 | missing-member-hint-distance=1 234 | 235 | # The total number of similar names that should be taken in consideration when 236 | # showing a hint for a missing member. 237 | missing-member-max-choices=1 238 | 239 | 240 | [SPELLING] 241 | 242 | # Limits count of emitted suggestions for spelling mistakes. 243 | max-spelling-suggestions=4 244 | 245 | # Spelling dictionary name. Available dictionaries: none. To make it working 246 | # install python-enchant package.. 247 | spelling-dict= 248 | 249 | # List of comma separated words that should not be checked. 250 | spelling-ignore-words= 251 | 252 | # A path to a file that contains private dictionary; one word per line. 253 | spelling-private-dict-file= 254 | 255 | # Tells whether to store unknown words to indicated private dictionary in 256 | # --spelling-private-dict-file option instead of raising a message. 257 | spelling-store-unknown-words=no 258 | 259 | 260 | [VARIABLES] 261 | 262 | # List of additional names supposed to be defined in builtins. Remember that 263 | # you should avoid defining new builtins when possible. 264 | additional-builtins= 265 | 266 | # Tells whether unused global variables should be treated as a violation. 267 | allow-global-unused-variables=yes 268 | 269 | # List of strings which can identify a callback function by name. A callback 270 | # name must start or end with one of those strings. 271 | callbacks=cb_, 272 | _cb 273 | 274 | # A regular expression matching the name of dummy variables (i.e. expected to 275 | # not be used). 276 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 277 | 278 | # Argument names that match this expression will be ignored. Default to name 279 | # with leading underscore. 280 | ignored-argument-names=_.*|^ignored_|^unused_ 281 | 282 | # Tells whether we should check for unused import in __init__ files. 283 | init-import=no 284 | 285 | # List of qualified module names which can have objects that can redefine 286 | # builtins. 287 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 288 | 289 | 290 | [SIMILARITIES] 291 | 292 | # Ignore comments when computing similarities. 293 | ignore-comments=yes 294 | 295 | # Ignore docstrings when computing similarities. 296 | ignore-docstrings=yes 297 | 298 | # Ignore imports when computing similarities. 299 | ignore-imports=no 300 | 301 | # Minimum lines number of a similarity. 302 | min-similarity-lines=4 303 | 304 | 305 | [STRING] 306 | 307 | # This flag controls whether the implicit-str-concat-in-sequence should 308 | # generate a warning on implicit string concatenation in sequences defined over 309 | # several lines. 310 | check-str-concat-over-line-jumps=no 311 | 312 | 313 | [FORMAT] 314 | 315 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 316 | expected-line-ending-format= 317 | 318 | # Regexp for a line that is allowed to be longer than the limit. 319 | ignore-long-lines=^\s*(# )??$ 320 | 321 | # Number of spaces of indent required inside a hanging or continued line. 322 | indent-after-paren=4 323 | 324 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 325 | # tab). 326 | indent-string=' ' 327 | 328 | # Maximum number of characters on a single line. 329 | max-line-length=100 330 | 331 | # Maximum number of lines in a module. 332 | max-module-lines=1000 333 | 334 | # List of optional constructs for which whitespace checking is disabled. `dict- 335 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 336 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 337 | # `empty-line` allows space-only lines. 338 | no-space-check=trailing-comma, 339 | dict-separator 340 | 341 | # Allow the body of a class to be on the same line as the declaration if body 342 | # contains single statement. 343 | single-line-class-stmt=no 344 | 345 | # Allow the body of an if to be on the same line as the test if there is no 346 | # else. 347 | single-line-if-stmt=no 348 | 349 | 350 | [MISCELLANEOUS] 351 | 352 | # List of note tags to take in consideration, separated by a comma. 353 | notes=FIXME, 354 | XXX, 355 | TODO 356 | 357 | 358 | [BASIC] 359 | 360 | # Naming style matching correct argument names. 361 | argument-naming-style=snake_case 362 | 363 | # Regular expression matching correct argument names. Overrides argument- 364 | # naming-style. 365 | #argument-rgx= 366 | 367 | # Naming style matching correct attribute names. 368 | attr-naming-style=snake_case 369 | 370 | # Regular expression matching correct attribute names. Overrides attr-naming- 371 | # style. 372 | #attr-rgx= 373 | 374 | # Bad variable names which should always be refused, separated by a comma. 375 | bad-names=foo, 376 | bar, 377 | baz, 378 | toto, 379 | tutu, 380 | tata 381 | 382 | # Naming style matching correct class attribute names. 383 | class-attribute-naming-style=any 384 | 385 | # Regular expression matching correct class attribute names. Overrides class- 386 | # attribute-naming-style. 387 | #class-attribute-rgx= 388 | 389 | # Naming style matching correct class names. 390 | class-naming-style=PascalCase 391 | 392 | # Regular expression matching correct class names. Overrides class-naming- 393 | # style. 394 | #class-rgx= 395 | 396 | # Naming style matching correct constant names. 397 | const-naming-style=UPPER_CASE 398 | 399 | # Regular expression matching correct constant names. Overrides const-naming- 400 | # style. 401 | #const-rgx= 402 | 403 | # Minimum line length for functions/classes that require docstrings, shorter 404 | # ones are exempt. 405 | docstring-min-length=-1 406 | 407 | # Naming style matching correct function names. 408 | function-naming-style=snake_case 409 | 410 | # Regular expression matching correct function names. Overrides function- 411 | # naming-style. 412 | #function-rgx= 413 | 414 | # Good variable names which should always be accepted, separated by a comma. 415 | good-names=i, 416 | j, 417 | k, 418 | ex, 419 | Run, 420 | _ 421 | 422 | # Include a hint for the correct naming format with invalid-name. 423 | include-naming-hint=no 424 | 425 | # Naming style matching correct inline iteration names. 426 | inlinevar-naming-style=any 427 | 428 | # Regular expression matching correct inline iteration names. Overrides 429 | # inlinevar-naming-style. 430 | #inlinevar-rgx= 431 | 432 | # Naming style matching correct method names. 433 | method-naming-style=snake_case 434 | 435 | # Regular expression matching correct method names. Overrides method-naming- 436 | # style. 437 | #method-rgx= 438 | 439 | # Naming style matching correct module names. 440 | module-naming-style=snake_case 441 | 442 | # Regular expression matching correct module names. Overrides module-naming- 443 | # style. 444 | #module-rgx= 445 | 446 | # Colon-delimited sets of names that determine each other's naming style when 447 | # the name regexes allow several styles. 448 | name-group= 449 | 450 | # Regular expression which should only match function or class names that do 451 | # not require a docstring. 452 | no-docstring-rgx=^_ 453 | 454 | # List of decorators that produce properties, such as abc.abstractproperty. Add 455 | # to this list to register other decorators that produce valid properties. 456 | # These decorators are taken in consideration only for invalid-name. 457 | property-classes=abc.abstractproperty 458 | 459 | # Naming style matching correct variable names. 460 | variable-naming-style=snake_case 461 | 462 | # Regular expression matching correct variable names. Overrides variable- 463 | # naming-style. 464 | #variable-rgx= 465 | 466 | 467 | [LOGGING] 468 | 469 | # Format style used to check logging format string. `old` means using % 470 | # formatting, while `new` is for `{}` formatting. 471 | logging-format-style=old 472 | 473 | # Logging modules to check that the string format arguments are in logging 474 | # function parameter format. 475 | logging-modules=logging 476 | 477 | 478 | [CLASSES] 479 | 480 | # List of method names used to declare (i.e. assign) instance attributes. 481 | defining-attr-methods=__init__, 482 | __new__, 483 | setUp 484 | 485 | # List of member names, which should be excluded from the protected access 486 | # warning. 487 | exclude-protected=_asdict, 488 | _fields, 489 | _replace, 490 | _source, 491 | _make 492 | 493 | # List of valid names for the first argument in a class method. 494 | valid-classmethod-first-arg=cls 495 | 496 | # List of valid names for the first argument in a metaclass class method. 497 | valid-metaclass-classmethod-first-arg=cls 498 | 499 | 500 | [IMPORTS] 501 | 502 | # Allow wildcard imports from modules that define __all__. 503 | allow-wildcard-with-all=no 504 | 505 | # Analyse import fallback blocks. This can be used to support both Python 2 and 506 | # 3 compatible code, which means that the block might have code that exists 507 | # only in one or another interpreter, leading to false positives when analysed. 508 | analyse-fallback-blocks=no 509 | 510 | # Deprecated modules which should not be used, separated by a comma. 511 | deprecated-modules=optparse,tkinter.tix 512 | 513 | # Create a graph of external dependencies in the given file (report RP0402 must 514 | # not be disabled). 515 | ext-import-graph= 516 | 517 | # Create a graph of every (i.e. internal and external) dependencies in the 518 | # given file (report RP0402 must not be disabled). 519 | import-graph= 520 | 521 | # Create a graph of internal dependencies in the given file (report RP0402 must 522 | # not be disabled). 523 | int-import-graph= 524 | 525 | # Force import order to recognize a module as part of the standard 526 | # compatibility libraries. 527 | known-standard-library= 528 | 529 | # Force import order to recognize a module as part of a third party library. 530 | known-third-party=enchant 531 | 532 | 533 | [DESIGN] 534 | 535 | # Maximum number of arguments for function / method. 536 | max-args=5 537 | 538 | # Maximum number of attributes for a class (see R0902). 539 | max-attributes=7 540 | 541 | # Maximum number of boolean expressions in an if statement. 542 | max-bool-expr=5 543 | 544 | # Maximum number of branch for function / method body. 545 | max-branches=12 546 | 547 | # Maximum number of locals for function / method body. 548 | max-locals=15 549 | 550 | # Maximum number of parents for a class (see R0901). 551 | max-parents=7 552 | 553 | # Maximum number of public methods for a class (see R0904). 554 | max-public-methods=20 555 | 556 | # Maximum number of return / yield for function / method body. 557 | max-returns=6 558 | 559 | # Maximum number of statements in function / method body. 560 | max-statements=50 561 | 562 | # Minimum number of public methods for a class (see R0903). 563 | min-public-methods=2 564 | 565 | 566 | [EXCEPTIONS] 567 | 568 | # Exceptions that will emit a warning when being caught. Defaults to 569 | # "BaseException, Exception". 570 | overgeneral-exceptions=BaseException, 571 | Exception 572 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018, INRIA 4 | Copyright (c) 2018, University of Lille 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyRAPL 2 | 3 | [![License: MIT](https://img.shields.io/pypi/l/pyRAPL)](https://spdx.org/licenses/MIT.html) 4 | [![Build Status](https://img.shields.io/circleci/project/github/powerapi-ng/pyRAPL.svg)](https://circleci.com/gh/powerapi-ng/pyrapl) 5 | 6 | 7 | # About 8 | **pyRAPL** is a software toolkit to measure the energy footprint of a host machine along the execution of a piece of Python code. 9 | 10 | **pyRAPL** uses the Intel "_Running Average Power Limit_" (RAPL) technology that estimates power consumption of a CPU. 11 | This technology is available on Intel CPU since the [Sandy Bridge generation](https://fr.wikipedia.org/wiki/Intel#Historique_des_microprocesseurs_produits). 12 | 13 | More specifically, pyRAPL can measure the energy consumption of the following CPU domains: 14 | - CPU socket package 15 | - DRAM (for server architectures) 16 | - GPU (for client architectures) 17 | 18 | # Installation 19 | 20 | You can install **pyRAPL** with pip: `pip install pyRAPL` 21 | 22 | # Basic usage 23 | 24 | Here are some basic usages of **pyRAPL**. Please note that the reported energy consumption is not only the energy consumption of the code you are running. This includes the _global energy consumption_ of all the process running on the machine during this period, thus including the operating system and other applications. 25 | That is why we recommend to eliminate any extra programs that may alter the energy consumption of the machine hosting experiments and to keep _only_ the code under measurement (_i.e._, no extra applications, such as graphical interface, background running task...). This will give the closest measure to the real energy consumption of the measured code. 26 | 27 | ## Decorate a function to measure its energy consumption 28 | 29 | To measure the energy consumed by the machine during the execution of the function `foo()` run the following code: 30 | ```python 31 | import pyRAPL 32 | 33 | pyRAPL.setup() 34 | 35 | @pyRAPL.measureit 36 | def foo(): 37 | # Instructions to be evaluated. 38 | 39 | foo() 40 | ``` 41 | 42 | This will print in the console the recorded energy consumption of all the CPU domains during the execution of function `foo`. 43 | 44 | ## Configure the decorator specifying the device to monitor 45 | 46 | You can easily configure which device and which socket to monitor using the parameters of the `pyRAPL.setup` function. 47 | For example, the following example only monitors the CPU power consumption on the CPU socket `1`. 48 | By default, **pyRAPL** monitors all the available devices of the CPU sockets. 49 | ```python 50 | 51 | import pyRAPL 52 | 53 | pyRAPL.setup(devices=[pyRAPL.Device.PKG], socket_ids=[1]) 54 | 55 | @pyRAPL.measureit 56 | def foo(): 57 | # Instructions to be evaluated. 58 | 59 | foo() 60 | ``` 61 | 62 | You can append the device `pyRAPL.Device.DRAM` to the `devices` parameter list to monitor RAM device too. 63 | 64 | ## Running the test multiple times 65 | 66 | For short functions, you can configure the number of runs and it will calculate the mean energy consumption of all runs. 67 | As an example if you want to average over 100 runs and repeat the experiment 20 times: 68 | ```python 69 | import pyRAPL 70 | 71 | pyRAPL.setup() 72 | 73 | 74 | @pyRAPL.measureit(number=100) 75 | def foo(): 76 | # Instructions to be evaluated. 77 | 78 | for _ in range(20): 79 | foo() 80 | ``` 81 | 82 | ## Configure the output of the decorator 83 | 84 | If you want to handle data with different output than the standard one, you can configure the decorator with an `Output` instance from the `pyRAPL.outputs` module. 85 | 86 | As an example, if you want to write the recorded energy consumption in a .csv file: 87 | ```python 88 | import pyRAPL 89 | 90 | pyRAPL.setup() 91 | 92 | csv_output = pyRAPL.outputs.CSVOutput('result.csv') 93 | 94 | @pyRAPL.measureit(output=csv_output) 95 | def foo(): 96 | # Instructions to be evaluated. 97 | 98 | for _ in range(100): 99 | foo() 100 | 101 | csv_output.save() 102 | ``` 103 | 104 | This will produce a csv file of 100 lines. Each line containing the energy 105 | consumption recorded during one execution of the function `fun`. 106 | Other predefined `Output` classes exist to export data to *MongoDB* and *Panda* 107 | dataframe. 108 | You can also create your own Output class (see the 109 | [documentation](https://pyrapl.readthedocs.io/en/latest/Outputs_API.html)) 110 | 111 | ## Measure the energy consumption of a piece of code 112 | 113 | To measure the energy consumed by the machine during the execution of a given 114 | piece of code, run the following code : 115 | ```python 116 | import pyRAPL 117 | 118 | pyRAPL.setup() 119 | meter = pyRAPL.Measurement('bar') 120 | meter.begin() 121 | # ... 122 | # Instructions to be evaluated. 123 | # ... 124 | meter.end() 125 | ``` 126 | 127 | You can also access the result of the measurements by using the property `meter.result`, which returns a [`Result`](https://pyrapl.readthedocs.io/en/latest/API.html#pyRAPL.Result) object. 128 | 129 | You can also use an output to handle this results, for example with the .csv output: `meter.export(csv_output)` 130 | 131 | ## Measure the energy consumption of a block 132 | 133 | **pyRAPL** allows developers to measure a block of instructions using the keyword ```with``` as the example below: 134 | ```python 135 | import pyRAPL 136 | pyRAPL.setup() 137 | 138 | with pyRAPL.Measurement('bar'): 139 | # ... 140 | # Instructions to be evaluated. 141 | # ... 142 | ``` 143 | 144 | This will report the energy consumption of the block. To process the measurements instead of printing them, you can use any [`Output`](https://pyrapl.readthedocs.io/en/latest/Outputs_API.html) class that you pass to the `Measurement` object: 145 | ```python 146 | import pyRAPL 147 | pyRAPL.setup() 148 | 149 | report = pyRAPL.outputs.DataFrameOutput() 150 | 151 | with pyRAPL.Measurement('bar',output=report): 152 | # ... 153 | # Instructions to be evaluated. 154 | # ... 155 | 156 | report.data.head() 157 | ``` 158 | 159 | # Miscellaneous 160 | 161 | ## About 162 | 163 | **pyRAPL** is an open-source project developed by the [Spirals research group](https://team.inria.fr/spirals) (University of Lille and Inria) that is part of the [PowerAPI](http://powerapi.org) initiative. 164 | 165 | The documentation is available [here](https://pyrapl.readthedocs.io/en/latest/). 166 | 167 | ## Mailing list 168 | 169 | You can follow the latest news and asks questions by subscribing to our mailing list. 170 | 171 | ## Contributing 172 | 173 | If you would like to contribute code, you can do so via GitHub by forking the repository and sending a pull request. 174 | 175 | When submitting code, please make every effort to follow existing coding conventions and style in order to keep the code as readable as possible. 176 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /docs/API.rst: -------------------------------------------------------------------------------- 1 | API 2 | *** 3 | 4 | Enumeration 5 | =========== 6 | .. autoclass:: pyRAPL.Device 7 | :members: 8 | 9 | Functions 10 | ========= 11 | .. autofunction:: pyRAPL.setup 12 | 13 | Decorator 14 | ========= 15 | .. autodecorator:: pyRAPL.measureit 16 | 17 | Class 18 | ===== 19 | .. autoclass:: pyRAPL.Measurement 20 | :members: 21 | 22 | .. autoclass:: pyRAPL.Result 23 | :members: 24 | 25 | 26 | Exception 27 | ========= 28 | .. autoexception:: pyRAPL.PyRAPLException 29 | :members: 30 | .. autoexception:: pyRAPL.PyRAPLCantRecordEnergyConsumption 31 | :members: 32 | .. autoexception:: pyRAPL.PyRAPLBadSocketIdException 33 | :members: 34 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/Outputs_API.rst: -------------------------------------------------------------------------------- 1 | Outputs API 2 | *********** 3 | 4 | .. automodule:: pyRAPL.outputs 5 | 6 | Abstract Class 7 | ============== 8 | .. autoclass:: pyRAPL.outputs.Output 9 | :members: 10 | 11 | .. autoclass:: pyRAPL.outputs.BufferedOutput 12 | :members: 13 | :private-members: 14 | 15 | Class 16 | ===== 17 | 18 | .. autoclass:: pyRAPL.outputs.PrintOutput 19 | :members: 20 | 21 | .. autoclass:: pyRAPL.outputs.CSVOutput 22 | :members: 23 | 24 | .. autoclass:: pyRAPL.outputs.MongoOutput 25 | :members: 26 | 27 | .. autoclass:: pyRAPL.outputs.DataFrameOutput 28 | :members: 29 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.append('../') 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'pyRAPL' 21 | copyright = '2019, INRIA, University of Lille' 22 | author = 'INRIA, University of Lille' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '0.2.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ['sphinx.ext.autodoc', 34 | 'sphinx_autodoc_typehints', 35 | 'sphinx_rtd_theme'] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = "sphinx_rtd_theme" 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | -------------------------------------------------------------------------------- /docs/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -R _build 3 | virtualenv ../venv 4 | source ../venv/bin/activate 5 | pip install sphinx 6 | pip install sphinx-autodoc-typehints 7 | pip install sphinx-rtd-theme 8 | pip install pymongo 9 | pip install pandas 10 | sphinx-build ./ _build/ 11 | make html 12 | deactivate 13 | rm -R ../venv 14 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyRAPL documentation master file, created by 2 | sphinx-quickstart on Tue Oct 8 14:16:48 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. role:: raw-role(raw) 7 | :format: html latex 8 | 9 | Welcome to pyRAPL's documentation! 10 | ********************************** 11 | 12 | .. toctree:: 13 | :maxdepth: 3 14 | 15 | quickstart 16 | API 17 | Outputs_API 18 | 19 | 20 | About 21 | ===== 22 | 23 | **pyRAPL** is a software toolkit to measure the energy footprint of a host machine along the execution of a piece of Python code. 24 | 25 | **pyRAPL** uses the Intel "*Running Average Power Limit*" (RAPL) technology that estimates power consumption of a CPU. This technology is available on Intel CPU since the `Sandy Bridge`__ generation. 26 | 27 | __ https://fr.wikipedia.org/wiki/Intel#Historique_des_microprocesseurs_produits 28 | 29 | More specifically, pyRAPL can measure the energy consumption of the following CPU domains: 30 | 31 | - CPU socket package 32 | - DRAM (for server architectures) 33 | - GPU (for client architectures) 34 | 35 | Miscellaneous 36 | ============= 37 | 38 | PyRAPL is an open-source project developed by the `Spirals research group`__ (University of Lille and Inria) that take part of the Powerapi_ initiative. 39 | 40 | .. _Powerapi: http://powerapi.org 41 | 42 | __ https://team.inria.fr/spirals 43 | 44 | Mailing list and contact 45 | ^^^^^^^^^^^^^^^^^^^^^^^^ 46 | 47 | You can contact the developer team with this address : :raw-role:`powerapi-staff@inria.fr` 48 | 49 | You can follow the latest news and asks questions by subscribing to our :raw-role:`mailing list` 50 | 51 | Contributing 52 | ^^^^^^^^^^^^ 53 | 54 | If you would like to contribute code you can do so via GitHub by forking the repository and sending a pull request. 55 | 56 | When submitting code, please make every effort to follow existing coding conventions and style in order to keep the code as readable as possible. 57 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ********** 3 | 4 | Installation 5 | ============ 6 | 7 | You can install **pyRAPL** with pip : ``pip install pyRAPL`` 8 | 9 | Basic usage 10 | =========== 11 | 12 | Here are some basic usages of **pyRAPL**. Please note that the reported energy consumption is not only the energy consumption of the code you are running. This includes the *global energy consumption* of all the process running on the machine during this period, thus including the operating system and other applications. 13 | That is why we recommend to eliminate any extra programs that may alter the energy consumption of the machine hosting experiments and to keep only the code under measurement (*i.e.*, no extra applications, such as graphical interface, background running task...). This will give the closest measure to the real energy consumption of the measured code. 14 | 15 | Here are some basic usages of pyRAPL. Please understand that the measured energy consumption is not only the energy consumption of the code you are running. It's the **global energy consumption** of all the process running on the machine during this period. This includes also the operating system and other applications. 16 | That's why we recommend eliminating any extra programs that may alter the energy consumption of the machine where we run the experiments and keep **only** the code we want to measure its energy consumption (no extra applications such as graphical interface, background running task ...). This will give the closest measure to the real energy consumption of the measured code. 17 | 18 | Decorate a function to measure its energy consumption 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | To measure the energy consumed by the machine during the execution of the 22 | function ``foo()`` run the following code:: 23 | 24 | import pyRAPL 25 | 26 | pyRAPL.setup() 27 | 28 | @pyRAPL.measureit 29 | def foo(): 30 | # Instructions to be evaluated. 31 | 32 | foo() 33 | 34 | This will print the recorded energy consumption of all the monitorable devices of the machine during the execution of function ``fun``. 35 | 36 | Configure the decorator specifying the device to monitor 37 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | 39 | You can easly configure which device and which socket to monitor using the parameters of the ``pyRAPL.setup`` function. 40 | For example, the following example only monitors the CPU power consumption on the CPU socket ``1``. 41 | By default, **pyRAPL** monitors all the available devices of the CPU sockets:: 42 | 43 | import pyRAPL 44 | 45 | pyRAPL.setup(devices=[pyRAPL.Device.PKG], socket_ids=[1]) 46 | 47 | @pyRAPL.measureit 48 | def foo(): 49 | # Instructions to be evaluated. 50 | 51 | foo() 52 | 53 | You can append the device ``pyRAPL.Device.DRAM`` to the ``devices`` parameter list to monitor RAM device too. 54 | 55 | Running the test multiple times 56 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 57 | 58 | For short functions, you can configure the number of runs and it will calculate the mean energy consumption of all runs. 59 | As an example if you want to average over 100 runs and repeat the experiment 20 times:: 60 | 61 | import pyRAPL 62 | 63 | pyRAPL.setup() 64 | 65 | @pyRAPL.measureit(number=100) 66 | def foo(): 67 | # Instructions to be evaluated. 68 | 69 | for _ in range(20): 70 | foo() 71 | 72 | 73 | Configure the output of the decorator 74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 75 | 76 | If you want to handle data with different output than the standard one, you can configure the decorator with an ``Output`` instance from the ``pyRAPL.outputs`` module. 77 | 78 | As an example if you want to write the recorded energy consumption in a csv file :: 79 | 80 | import pyRAPL 81 | 82 | pyRAPL.setup() 83 | 84 | csv_output = pyRAPL.outputs.CSVOutput('result.csv') 85 | 86 | @pyRAPL.measureit(output=csv_output) 87 | def foo(): 88 | # Some stuff ... 89 | 90 | for _ in range(100): 91 | foo() 92 | 93 | csv_output.save() 94 | 95 | This will produce a csv file of 100 lines. Each line containing the energy 96 | consumption recorded during one execution of the function `fun`. 97 | Other predefined Output classes exist to export data to *MongoDB* and *Panda* 98 | dataframe. You can also create your own Output class (see the 99 | documentation_) 100 | 101 | .. _documentation: https://pyrapl.readthedocs.io/en/latest/Outputs_API.html 102 | 103 | Measure the energy consumption of a piece of code 104 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 105 | 106 | To measure the energy consumed by the machine during the execution of a given 107 | piece of code, run the following code:: 108 | 109 | import pyRAPL 110 | 111 | pyRAPL.setup() 112 | measure = pyRAPL.Measurement('bar') 113 | measure.begin() 114 | 115 | # ... 116 | # Instructions to be evaluated. 117 | # ... 118 | 119 | measure.end() 120 | 121 | You can also access the result of the measurements using the property : ``measure.result`` which returns a Result_ instance. 122 | 123 | .. _Result: https://pyrapl.readthedocs.io/en/latest/API.html#pyRAPL.Result 124 | 125 | You can also use an output to handle this results, for example with the csv output : ``measure.export(csv_output)`` 126 | 127 | 128 | Measure the energy consumption of a block 129 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 130 | 131 | **pyRAPL** allows also to measure a block of instructions using the Keyword ``with`` as the example below:: 132 | 133 | 134 | import pyRAPL 135 | pyRAPL.setup() 136 | 137 | with pyRAPL.Measurement('bar'): 138 | # ... 139 | # Instructions to be evaluated. 140 | # ... 141 | 142 | 143 | This will print in the console the energy consumption of the block. 144 | To handle the measures instead of just printing them you can use any Output_ class that you pass to the Measurement object 145 | 146 | .. _Output: https://pyrapl.readthedocs.io/en/latest/Outputs_API.html 147 | 148 | :: 149 | 150 | import pyRAPL 151 | pyRAPL.setup() 152 | 153 | dataoutput= pyRAPL.outputs.DataFrameOutput() 154 | with pyRAPL.Measurement('bar',output=dataoutput): 155 | 156 | # ... 157 | # Instructions to be evaluated. 158 | # ... 159 | 160 | dataoutput.data.head() 161 | 162 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-autodoc-typehints 2 | sphinx-rtd-theme 3 | pymongo 4 | pandas 5 | -------------------------------------------------------------------------------- /pyRAPL/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2018, INRIA 3 | # Copyright (c) 2018, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from pyRAPL.device import Device 21 | from pyRAPL.exception import PyRAPLException, PyRAPLCantInitDeviceAPI, PyRAPLBadSocketIdException 22 | from pyRAPL.exception import PyRAPLCantRecordEnergyConsumption 23 | from pyRAPL.device_api import DeviceAPI, PkgAPI, DramAPI, DeviceAPIFactory 24 | from pyRAPL.sensor import Sensor 25 | from pyRAPL.result import Result 26 | from pyRAPL.pyRAPL import setup 27 | from pyRAPL.measurement import Measurement, measureit 28 | 29 | __version__ = "0.2.3.1" 30 | 31 | _sensor = None 32 | -------------------------------------------------------------------------------- /pyRAPL/device.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from enum import IntEnum 21 | 22 | 23 | class Device(IntEnum): 24 | """ 25 | Device that can be monitored by pyRAPL 26 | 27 | Device.PKG : to monitor the CPU energy consumption 28 | 29 | Device.DRAM : to monitor the RAM energy consumption 30 | """ 31 | PKG = 0 32 | DRAM = 1 33 | -------------------------------------------------------------------------------- /pyRAPL/device_api.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import os 21 | import re 22 | from typing import Optional, Tuple, List 23 | 24 | from pyRAPL import Device, PyRAPLCantInitDeviceAPI, PyRAPLBadSocketIdException 25 | 26 | 27 | def cpu_ids() -> List[int]: 28 | """ 29 | return the cpu id of this machine 30 | """ 31 | api_file = open('/sys/devices/system/cpu/present', 'r') 32 | 33 | cpu_id_tmp = re.findall('\d+|-', api_file.readline().strip()) 34 | cpu_id_list = [] 35 | for i in range(len(cpu_id_tmp)): 36 | if cpu_id_tmp[i] == '-': 37 | for cpu_id in range(int(cpu_id_tmp[i - 1]) + 1, int(cpu_id_tmp[i + 1])): 38 | cpu_id_list.append(int(cpu_id)) 39 | else: 40 | cpu_id_list.append(int(cpu_id_tmp[i])) 41 | return cpu_id_list 42 | 43 | 44 | def get_socket_ids() -> List[int]: 45 | """ 46 | return cpu socket id present on the machine 47 | """ 48 | socket_id_list = [] 49 | for cpu_id in cpu_ids(): 50 | api_file = open('/sys/devices/system/cpu/cpu' + str(cpu_id) + '/topology/physical_package_id') 51 | socket_id_list.append(int(api_file.readline().strip())) 52 | return list(set(socket_id_list)) 53 | 54 | 55 | class DeviceAPI: 56 | """ 57 | API to read energy consumption from sysfs 58 | """ 59 | 60 | def __init__(self, socket_ids: Optional[List[int]] = None): 61 | """ 62 | :param int socket_ids: if None, the API will get the energy consumption of the whole machine otherwise, it will 63 | get the energy consumption of the device on the given socket package 64 | :raise PyRAPLCantInitDeviceAPI: the machine where is initialised the DeviceAPI have no rapl interface for the 65 | target device 66 | :raise PyRAPLBadSocketIdException: the machine where is initialised the DeviceAPI has no the requested socket 67 | """ 68 | all_socket_id = get_socket_ids() 69 | if socket_ids is None: 70 | self._socket_ids = all_socket_id 71 | else: 72 | for socket_id in socket_ids: 73 | if socket_id not in all_socket_id: 74 | raise PyRAPLBadSocketIdException(socket_id) 75 | self._socket_ids = socket_ids 76 | 77 | self._socket_ids.sort() 78 | 79 | self._sys_files = self._open_rapl_files() 80 | 81 | def _open_rapl_files(self): 82 | raise NotImplementedError() 83 | 84 | def _get_socket_directory_names(self) -> List[Tuple[str, int]]: 85 | """ 86 | :return (str, int): directory name, rapl_id 87 | """ 88 | 89 | def add_to_result(directory_info, result): 90 | """ 91 | check if the directory info could be added to the result list and add it 92 | """ 93 | dirname, _ = directory_info 94 | f_name = open(dirname + '/name', 'r') 95 | pkg_str = f_name.readline() 96 | if 'package' not in pkg_str: 97 | return 98 | package_id = int(pkg_str[:-1].split('-')[1]) 99 | 100 | if self._socket_ids is not None and package_id not in self._socket_ids: 101 | return 102 | result.append((package_id, ) + directory_info) 103 | 104 | rapl_id = 0 105 | result_list = [] 106 | while os.path.exists('/sys/class/powercap/intel-rapl/intel-rapl:' + str(rapl_id)): 107 | dirname = '/sys/class/powercap/intel-rapl/intel-rapl:' + str(rapl_id) 108 | add_to_result((dirname, rapl_id), result_list) 109 | rapl_id += 1 110 | 111 | if len(result_list) != len(self._socket_ids): 112 | raise PyRAPLCantInitDeviceAPI() 113 | 114 | # sort the result list 115 | result_list.sort(key=lambda t: t[0]) 116 | # return info without socket ids 117 | return list(map(lambda t: (t[1], t[2]), result_list)) 118 | 119 | def energy(self) -> Tuple[float, ...]: 120 | """ 121 | Get the energy consumption of the device since the last CPU reset 122 | :return float tuple: a tuple containing the energy consumption (in J) the device on each socket 123 | the Nth value of the tuple correspond to the energy consumption of the device on the Nth 124 | socket 125 | """ 126 | result = [-1] * (self._socket_ids[-1] + 1) 127 | for i in range(len(self._sys_files)): 128 | device_file = self._sys_files[i] 129 | device_file.seek(0, 0) 130 | result[self._socket_ids[i]] = float(device_file.readline()) 131 | return tuple(result) 132 | 133 | 134 | class PkgAPI(DeviceAPI): 135 | 136 | def __init__(self, socket_ids: Optional[int] = None): 137 | DeviceAPI.__init__(self, socket_ids) 138 | 139 | def _open_rapl_files(self): 140 | directory_name_list = self._get_socket_directory_names() 141 | 142 | rapl_files = [] 143 | for (directory_name, _) in directory_name_list: 144 | rapl_files.append(open(directory_name + '/energy_uj', 'r')) 145 | return rapl_files 146 | 147 | 148 | class DramAPI(DeviceAPI): 149 | 150 | def __init__(self, socket_ids: Optional[int] = None): 151 | DeviceAPI.__init__(self, socket_ids) 152 | 153 | def _open_rapl_files(self): 154 | directory_name_list = self._get_socket_directory_names() 155 | 156 | def get_dram_file(socket_directory_name, rapl_socket_id, ): 157 | rapl_device_id = 0 158 | while os.path.exists(socket_directory_name + '/intel-rapl:' + str(rapl_socket_id) + ':' + 159 | str(rapl_device_id)): 160 | dirname = socket_directory_name + '/intel-rapl:' + str(rapl_socket_id) + ':' + str(rapl_device_id) 161 | f_device = open(dirname + '/name', 'r') 162 | if f_device.readline() == 'dram\n': 163 | return open(dirname + '/energy_uj', 'r') 164 | rapl_device_id += 1 165 | raise PyRAPLCantInitDeviceAPI() 166 | 167 | rapl_files = [] 168 | for (socket_directory_name, rapl_socket_id) in directory_name_list: 169 | rapl_files.append(get_dram_file(socket_directory_name, rapl_socket_id)) 170 | 171 | return rapl_files 172 | 173 | 174 | class DeviceAPIFactory: 175 | """ 176 | Factory Returning DeviceAPI 177 | """ 178 | @staticmethod 179 | def create_device_api(device: Device, socket_ids: Optional[int]) -> DeviceAPI: 180 | """ 181 | :param device: the device corresponding to the DeviceAPI to be created 182 | :param socket_ids: param that will be passed to the constructor of the DeviceAPI instance 183 | :return: a DeviceAPI instance 184 | """ 185 | if device == Device.PKG: 186 | return PkgAPI(socket_ids) 187 | if device == Device.DRAM: 188 | return DramAPI(socket_ids) 189 | -------------------------------------------------------------------------------- /pyRAPL/exception.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from pyRAPL import Device 21 | 22 | 23 | class PyRAPLException(Exception): 24 | """Parent class of all PyRAPL exception 25 | """ 26 | 27 | 28 | class PyRAPLNoEnergyConsumptionRecordedException(PyRAPLException): 29 | """Exception raised when a function recorded_energy() is executed without stopping consumption recording before 30 | """ 31 | 32 | 33 | class PyRAPLNoEnergyConsumptionRecordStartedException(PyRAPLException): 34 | """Exception raised when a function stop() is executed without starting consumption recording before""" 35 | 36 | 37 | class PyRAPLCantRecordEnergyConsumption(PyRAPLException): 38 | """Exception raised when starting recording energy consumption for a device but no energy consumption metric is 39 | available for this device 40 | 41 | :var device: device that couldn't be monitored (if None, Any device on the machine could be monitored) 42 | :vartype device: Device 43 | """ 44 | def __init__(self, device: Device): 45 | PyRAPLException.__init__(self) 46 | self.device = device 47 | 48 | 49 | class PyRAPLCantInitDeviceAPI(PyRAPLException): 50 | """ 51 | Exception raised when trying to initialise a DeviceAPI instance on a machine that can't support it. For example, 52 | initialise a DramAPI on a machine without dram rapl interface will throw a PyRAPLCantInitDeviceAPI 53 | """ 54 | 55 | 56 | class PyRAPLBadSocketIdException(PyRAPLException): 57 | """ 58 | Exception raised when trying to initialise PyRAPL on a socket that doesn't exist on the machine 59 | 60 | :var socket_id: socket that doesn't exist 61 | :vartype socket_id: int 62 | """ 63 | 64 | def __init__(self, socket_id: int): 65 | PyRAPLException.__init__(self) 66 | self.socket_id = socket_id 67 | -------------------------------------------------------------------------------- /pyRAPL/measurement.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import functools 21 | 22 | from time import time_ns 23 | from pyRAPL import Result 24 | from pyRAPL.outputs import PrintOutput, Output 25 | import pyRAPL 26 | 27 | 28 | def empty_energy_result(energy_result): 29 | """ 30 | Return False if the energy_result list contains only negative numbers, True otherwise 31 | """ 32 | return functools.reduce(lambda acc, x: acc or (x >= 0), energy_result, False) 33 | 34 | 35 | class Measurement: 36 | """ 37 | measure the energy consumption of devices on a bounded period 38 | 39 | Beginning and end of this period are given by calling ``begin()`` and ``end()`` methods 40 | 41 | :param label: measurement label 42 | 43 | :param output: default output to export the recorded energy consumption. If None, the PrintOutput will be used 44 | """ 45 | 46 | def __init__(self, label: str, output: Output = None): 47 | self.label = label 48 | self._energy_begin = None 49 | self._ts_begin = None 50 | self._results = None 51 | self._output = output if output is not None else PrintOutput() 52 | 53 | self._sensor = pyRAPL._sensor 54 | 55 | def begin(self): 56 | """ 57 | Start energy consumption recording 58 | """ 59 | self._energy_begin = self._sensor.energy() 60 | self._ts_begin = time_ns() 61 | 62 | def __enter__(self): 63 | """use Measurement as a context """ 64 | self.begin() 65 | 66 | def __exit__(self, exc_type, exc_value, traceback): 67 | """use Measurement as a context """ 68 | self.end() 69 | if(exc_type is None): 70 | self.export() 71 | 72 | def end(self): 73 | """ 74 | End energy consumption recording 75 | """ 76 | ts_end = time_ns() 77 | energy_end = self._sensor.energy() 78 | 79 | delta = energy_end - self._energy_begin 80 | duration = ts_end - self._ts_begin 81 | pkg = delta[0::2] # get odd numbers 82 | pkg = pkg if empty_energy_result(pkg) else None # set result to None if its contains only -1 83 | dram = delta[1::2] # get even numbers 84 | dram = dram if empty_energy_result(dram) else None # set result to None if its contains only -1 85 | 86 | self._results = Result(self.label, self._ts_begin / 1000000000, duration / 1000, pkg, dram) 87 | 88 | def export(self, output: Output = None): 89 | """ 90 | Export the energy consumption measures to a given output 91 | 92 | :param output: output that will handle the measure, if None, the default output will be used 93 | """ 94 | if output is None: 95 | self._output.add(self._results) 96 | else: 97 | output.add(self._results) 98 | 99 | @property 100 | def result(self) -> Result: 101 | """ 102 | Access to the measurement data 103 | """ 104 | return self._results 105 | 106 | 107 | def measureit(_func=None, *, output: Output = None, number: int = 1): 108 | """ 109 | Measure the energy consumption of monitored devices during the execution of the decorated function (if multiple runs it will measure the mean energy) 110 | 111 | :param output: output instance that will receive the power consummation data 112 | :param number: number of iteration in the loop in case you need multiple runs or the code is too fast to be measured 113 | """ 114 | 115 | def decorator_measure_energy(func): 116 | @functools.wraps(func) 117 | def wrapper_measure(*args, **kwargs): 118 | sensor = Measurement(func.__name__, output) 119 | sensor.begin() 120 | for i in range(number): 121 | val = func(*args, **kwargs) 122 | sensor.end() 123 | sensor._results = sensor._results / number 124 | sensor.export() 125 | return val 126 | return wrapper_measure 127 | 128 | if _func is None: 129 | # to ensure the working system when you call it with parameters or without parameters 130 | return decorator_measure_energy 131 | else: 132 | return decorator_measure_energy(_func) 133 | -------------------------------------------------------------------------------- /pyRAPL/outputs/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | """ 21 | This module contains class that will be used by the ``measure`` decorator or the ``Measurement.export`` method to export 22 | recorded measurement 23 | 24 | example with the ``measure decorator``:: 25 | 26 | output_instance = pyRAPL.outputs.XXXOutput(...) 27 | 28 | @pyRAPL.measure(output=output_instance) 29 | def foo(): 30 | ... 31 | 32 | example with the ``Measurement.export`` function:: 33 | 34 | measure = pyRAPL.Measurement('label') 35 | ... 36 | output_instance = pyRAPL.outputs.XXXOutput(...) 37 | measure.export(output_instance) 38 | 39 | You can define your one output by inherit from the ``Output`` class and implements the ``add`` method. 40 | This method will receive the measured energy consumption data as a ``Result`` instance and must handle it. 41 | 42 | For example, the ``PrintOutput.add`` method will print the ``Result`` instance. 43 | """ 44 | import logging 45 | 46 | from .output import Output 47 | from .buffered_output import BufferedOutput 48 | from .printoutput import PrintOutput 49 | from .csvoutput import CSVOutput 50 | 51 | try: 52 | from .mongooutput import MongoOutput 53 | 54 | except ImportError: 55 | logging.warning("imports error \n You need to install pymongo>=3.9.0 in order to use MongoOutput ") 56 | 57 | try: 58 | from .dataframeoutput import DataFrameOutput 59 | 60 | except ImportError: 61 | logging.warning("imports error \n You need to install pandas>=0.25.1 in order to use DataFrameOutput ") 62 | -------------------------------------------------------------------------------- /pyRAPL/outputs/buffered_output.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from typing import List 21 | 22 | from pyRAPL import Result 23 | from pyRAPL.outputs import Output 24 | 25 | 26 | class BufferedOutput(Output): 27 | """ 28 | Use a buffer to batch the output process 29 | 30 | The method ``add`` add data to the buffer and the method ``save`` outputs each data in the buffer. After that, the 31 | buffer is flushed 32 | 33 | Implement the abstract method ``_output_buffer`` to define how to output buffered data 34 | """ 35 | 36 | def __init__(self): 37 | Output.__init__(self) 38 | self._buffer = [] 39 | 40 | def add(self, result): 41 | """ 42 | Add the given data to the buffer 43 | 44 | :param result: data that must be added to the buffer 45 | """ 46 | x = dict(vars(result)) 47 | x['timestamp'] = x['timestamp'] 48 | for i in range(len(result.pkg)): 49 | x['socket'] = i 50 | x['pkg'] = result.pkg[i] 51 | x['dram'] = result.dram[i] if result.dram else None 52 | self._buffer.append(x.copy()) 53 | 54 | @property 55 | def buffer(self) -> List[Result]: 56 | """ 57 | Return the buffer content 58 | 59 | :return: a list of all the ``Result`` instances contained in the buffer 60 | """ 61 | return self._buffer 62 | 63 | def _output_buffer(self): 64 | """ 65 | Abstract method 66 | 67 | Output all the data contained in the buffer 68 | 69 | :param data: data to output 70 | """ 71 | raise NotImplementedError() 72 | 73 | def save(self): 74 | """ 75 | Output each data in the buffer and empty the buffer 76 | """ 77 | self._output_buffer() 78 | self._buffer = [] 79 | -------------------------------------------------------------------------------- /pyRAPL/outputs/csvoutput.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import os 21 | 22 | from pyRAPL import Result 23 | from pyRAPL.outputs import BufferedOutput 24 | 25 | 26 | class CSVOutput(BufferedOutput): 27 | """ 28 | Write the recorded measure in csv format on a file 29 | 30 | if the file already exists, the result will be append to the end of the file, otherwise it will create a new file. 31 | 32 | This instance act as a buffer. The method ``add`` add data to the buffer and the method ``save`` append each data 33 | in the buffer at the end of the csv file. After that, the buffer is flushed 34 | 35 | :param filename: file's name were the result will be written 36 | 37 | :param separator: character used to separate columns in the csv file 38 | 39 | :param append: Turn it to False to delete file if it already exist. 40 | """ 41 | def __init__(self, filename: str, separator: str = ',', append: bool = True): 42 | BufferedOutput.__init__(self) 43 | self._separator = separator 44 | self._buffer = [] 45 | self._filename = filename 46 | 47 | # Create file with header if it not exist or if append is False 48 | if not os.path.exists(self._filename) or not append: 49 | header = separator.join(list(Result.__annotations__.keys()) + ['socket']) + '\n' 50 | 51 | with open(self._filename, 'w+') as csv_file: 52 | csv_file.writelines(header) 53 | 54 | def _output_buffer(self): 55 | """ 56 | Append the data at the end of the csv file 57 | :param data: data to write 58 | """ 59 | with open(self._filename, 'a+') as csv_file: 60 | for data in self._buffer: 61 | line = self._separator.join([str(column) for column in data.values()]) + '\n' 62 | csv_file.writelines(line) 63 | -------------------------------------------------------------------------------- /pyRAPL/outputs/dataframeoutput.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import time 21 | 22 | import pandas 23 | 24 | from pyRAPL import Result 25 | from pyRAPL.outputs import BufferedOutput 26 | 27 | 28 | class DataFrameOutput(BufferedOutput): 29 | """ 30 | Append recorded data to a pandas Dataframe 31 | """ 32 | def __init__(self): 33 | BufferedOutput.__init__(self) 34 | 35 | @property 36 | def data(self) -> pandas.DataFrame: 37 | """ 38 | Return the dataframe that contains the recorded data 39 | 40 | :return: the dataframe 41 | """ 42 | data_frame = pandas.DataFrame(self._buffer) 43 | data_frame['timestamp'] = data_frame['timestamp'].map(lambda x: time.ctime(x)) 44 | return data_frame 45 | -------------------------------------------------------------------------------- /pyRAPL/outputs/mongooutput.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import pymongo 21 | 22 | from pyRAPL.outputs import BufferedOutput 23 | 24 | 25 | class MongoOutput(BufferedOutput): 26 | """ 27 | Store the recorded measure in a MongoDB database 28 | 29 | This instance act as a buffer. The method ``add`` add data to the buffer and the method ``save`` store each data 30 | in the buffer in the MongoDB database. After that, the buffer is flushed 31 | 32 | :param uri: uri used to connect to the mongoDB instance 33 | :param database: database name to store the data 34 | :param collection: collection name to store the data 35 | """ 36 | def __init__(self, uri: str, database: str, collection: str): 37 | """ 38 | Export the results to a collection in a mongo database 39 | """ 40 | BufferedOutput.__init__(self) 41 | self._client = pymongo.MongoClient(uri) 42 | self._db = self._client[database] 43 | self._collection = self._db[collection] 44 | 45 | def _output_buffer(self): 46 | """ 47 | Store all the data contained in the buffer 48 | 49 | :param data: data to output 50 | """ 51 | self._collection.insert_many(self._buffer) 52 | -------------------------------------------------------------------------------- /pyRAPL/outputs/output.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from pyRAPL import Result 21 | 22 | 23 | class Output: 24 | """ 25 | Abstract class that represent an output handler for the `Measurement` class 26 | """ 27 | 28 | def add(self, result: Result): 29 | """ 30 | Handle the object `Result` 31 | 32 | :param result: data to handle 33 | """ 34 | raise NotImplementedError() 35 | -------------------------------------------------------------------------------- /pyRAPL/outputs/printoutput.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import time 21 | 22 | from pyRAPL import Result 23 | from pyRAPL.outputs import Output 24 | 25 | 26 | def print_energy(energy): 27 | s = "" 28 | for i in range(len(energy)): 29 | if isinstance(energy[i], float): 30 | s = s + f"\n\tsocket {i} : {energy[i]: 10.4f} uJ" 31 | else: 32 | s = s + f"\n\tsocket {i} : {energy[i]} uJ" 33 | return s 34 | 35 | 36 | class PrintOutput(Output): 37 | """ 38 | Output that print data on standard output 39 | 40 | :param raw: if True, print the raw result class to standard output. 41 | Otherwise, print a fancier representation of result 42 | """ 43 | 44 | def __init__(self, raw: bool = False): 45 | Output.__init__(self) 46 | 47 | self._raw = raw 48 | 49 | def _format_output(self, result): 50 | if self._raw: 51 | return str(result) 52 | else: 53 | s = f"""Label : {result.label}\nBegin : {time.ctime(result.timestamp)}\nDuration : {result.duration:10.4f} us""" 54 | if result.pkg is not None: 55 | s += f"""\n-------------------------------\nPKG :{print_energy(result.pkg)}""" 56 | if result.dram is not None: 57 | s += f"""\n-------------------------------\nDRAM :{print_energy(result.dram)}""" 58 | s += '\n-------------------------------' 59 | return s 60 | 61 | def add(self, result: Result): 62 | """ 63 | print result on standard output 64 | 65 | :param result: data to print 66 | """ 67 | print(self._format_output(result)) 68 | -------------------------------------------------------------------------------- /pyRAPL/pyRAPL.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from typing import List, Optional 21 | from pyRAPL import Sensor, Device 22 | import pyRAPL 23 | 24 | 25 | def setup(devices: Optional[List[Device]] = None, socket_ids: Optional[List[int]] = None): 26 | """ 27 | Configure which device and CPU socket should be monitored by pyRAPL 28 | 29 | This function must be called before using any other pyRAPL functions 30 | 31 | :param devices: list of monitored devices if None, all the available devices on the machine will be monitored 32 | 33 | :param socket_ids: list of monitored sockets, if None, all the available socket on the machine will be monitored 34 | 35 | :raise PyRAPLCantRecordEnergyConsumption: if the sensor can't get energy information about the given device in parameter 36 | 37 | :raise PyRAPLBadSocketIdException: if the given socket in parameter doesn't exist 38 | """ 39 | pyRAPL._sensor = Sensor(devices=devices, socket_ids=socket_ids) 40 | -------------------------------------------------------------------------------- /pyRAPL/result.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from typing import Optional, List 21 | from dataclasses import dataclass 22 | 23 | 24 | @dataclass(frozen=True) 25 | class Result: 26 | """ 27 | A data class to represent the energy measures 28 | 29 | :var label: measurement label 30 | :vartype label: str 31 | :var timestamp: measurement's beginning time (expressed in seconds since the epoch) 32 | :vartype timestamp: float 33 | :var duration: measurement's duration (in micro seconds) 34 | :vartype duration: float 35 | :var pkg: list of the CPU energy consumption -expressed in micro Joules- (one value for each socket) if None, no CPU energy consumption was recorded 36 | :vartype pkg: Optional[List[float]] 37 | :var dram: list of the RAM energy consumption -expressed in micro Joules- (one value for each socket) if None, no RAM energy consumption was recorded 38 | :vartype dram: Optional[List[float]] 39 | """ 40 | label: str 41 | timestamp: float 42 | duration: float 43 | pkg: Optional[List[float]] = None 44 | dram: Optional[List[float]] = None 45 | 46 | def __truediv__(self, number: int): 47 | """ devide all the attributes by the number number , used to measure one instance if we run the test inside a loop 48 | :param number: inteager 49 | """ 50 | 51 | _duration = self.duration / number 52 | _pkg = [j / number for j in self.pkg] if self.pkg else None 53 | _dram = [j / number for j in self.dram] if self.dram else None 54 | return Result(self.label, self.timestamp, _duration, _pkg, _dram) 55 | -------------------------------------------------------------------------------- /pyRAPL/sensor.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from typing import List, Optional 21 | 22 | from pyRAPL import Device, DeviceAPIFactory, PyRAPLCantInitDeviceAPI, PyRAPLCantRecordEnergyConsumption 23 | from pyRAPL import PyRAPLBadSocketIdException 24 | 25 | 26 | class SubstractableList(list): 27 | """ 28 | Substract each element of a list to another list except if they are negative numbers 29 | """ 30 | def __sub__(self, other): 31 | if len(other) != len(self): 32 | raise ValueError("List are not of the same length") 33 | return [a - b if a >= 0 and b >= 0 else -1 for a, b in zip(self, other)] 34 | 35 | 36 | class Sensor: 37 | """ 38 | Global singleton that return global energy consumption about monitored devices 39 | """ 40 | 41 | def __init__(self, devices: Optional[List[Device]] = None, socket_ids: Optional[List[int]] = None): 42 | """ 43 | :param devices: list of device to get energy consumption if None, all the devices available on the machine will 44 | be monitored 45 | :param socket_ids: if None, the API will get the energy consumption of the whole machine otherwise, it will 46 | get the energy consumption of the devices on the given socket package 47 | :raise PyRAPLCantRecordEnergyConsumption: if the sensor can't get energy information about a device given in 48 | parameter 49 | :raise PyRAPLBadSocketIdException: if the sensor can't get energy information about a device given in 50 | parameter 51 | """ 52 | self._available_devices = [] 53 | self._device_api = {} 54 | self._socket_ids = None 55 | 56 | tmp_device = devices if devices is not None else [Device.PKG, Device.DRAM] 57 | for device in tmp_device: 58 | try: 59 | self._device_api[device] = DeviceAPIFactory.create_device_api(device, socket_ids) 60 | self._available_devices.append(device) 61 | except PyRAPLCantInitDeviceAPI: 62 | if devices is not None: 63 | raise PyRAPLCantRecordEnergyConsumption(device) 64 | except PyRAPLBadSocketIdException as exn: 65 | raise exn 66 | 67 | if not self._available_devices: 68 | raise PyRAPLCantRecordEnergyConsumption(None) 69 | 70 | self._socket_ids = socket_ids if socket_ids is not None else list(self._device_api.values())[0]._socket_ids 71 | 72 | def energy(self) -> SubstractableList: 73 | """ 74 | get the energy consumption of all the monitored devices 75 | :return: a tuple containing the energy consumption of each device for each socket. The tuple structure is : 76 | (pkg energy socket 0, dram energy socket 0, ..., pkg energy socket N, dram energy socket N) 77 | """ 78 | result = SubstractableList([-1, -1] * (self._socket_ids[-1] + 1)) 79 | # print((result, self._socket_ids)) 80 | for device in self._available_devices: 81 | energy = self._device_api[device].energy() 82 | for socket_id in range(len(energy)): 83 | result[socket_id * 2 + device] = energy[socket_id] 84 | return result 85 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = pyRAPL 3 | version = attr: pyRAPL.__version__ 4 | description = 5 | long_description = file: README.md, LICENSE 6 | long_description_content_type= text/markdown 7 | keywords = energy 8 | platform = linux 9 | author = Chakib Belgaid, Arthur d'Azémar, Guillaume Fieni, Romain Rouvoy 10 | author_email = powerapi-staff@inria.fr 11 | license = MIT License 12 | classifiers = 13 | Programming Language :: Python :: 3.7 14 | License :: OSI Approved :: MIT License 15 | 16 | project_urls = 17 | Homepage = http://powerapi.org/ 18 | Source = https://github.com/powerapi-ng/pyRAPL 19 | 20 | [options] 21 | zip_safe = False 22 | include_package_data = True 23 | python_requires = >= 3.7 24 | packages = find: 25 | test_suite = tests 26 | setup_requires = 27 | pytest-runner >=3.9.2 28 | install_requires = 29 | tests_require = 30 | pytest >=3.9.2 31 | pyfakefs >= 3.6 32 | mock >=2.0 33 | 34 | [options.extras_require] 35 | docs = 36 | sphinx >=1.8.1 37 | sphinx-autodoc-typehints >=1.6.0 38 | mongodb = 39 | pymongo >= 3.9.0 40 | pandas = 41 | pandas >= 0.25.1 42 | [aliases] 43 | test = pytest 44 | 45 | [bdist_wheel] 46 | universal = true 47 | 48 | [flake8] 49 | max-line-length = 160 50 | ignore = W504 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | setup() -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powerapi-ng/pyRAPL/ff0f2edab08fe36f9887dc7ab9c6d0b3b66d4f11/tests/__init__.py -------------------------------------------------------------------------------- /tests/acceptation/test_context_measure.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | from tests.utils import fs_one_socket, write_new_energy_value, PKG_0_VALUE, DRAM_0_VALUE 22 | import pyRAPL 23 | 24 | POWER_CONSUMPTION_PKG = 20000 25 | POWER_CONSUMPTION_DRAM = 30000 26 | 27 | 28 | def measurable_function(a): 29 | # Power consumption of the function 30 | write_new_energy_value(POWER_CONSUMPTION_PKG, pyRAPL.Device.PKG, 0) 31 | write_new_energy_value(POWER_CONSUMPTION_DRAM, pyRAPL.Device.DRAM, 0) 32 | return 1 + a 33 | 34 | 35 | class dummyOutput(pyRAPL.outputs.Output): 36 | data = None 37 | 38 | def add(self, result: pyRAPL.Result): 39 | self.data = result 40 | 41 | 42 | 43 | def test_context_measure(fs_one_socket): 44 | """ 45 | Test to measure the energy consumption of a function using the Measurement class 46 | 47 | - launch the measure 48 | - write a new value to the RAPL power measurement api file 49 | - launch a function 50 | - end the measure 51 | 52 | Test if: 53 | - the energy consumption measured is the delta between the first and the last value in the RAPL power measurement 54 | file 55 | """ 56 | pyRAPL.setup() 57 | out = dummyOutput() 58 | with pyRAPL.Measurement('toto', output=out): 59 | measurable_function(1) 60 | 61 | assert out.data.pkg == [(POWER_CONSUMPTION_PKG - PKG_0_VALUE)] 62 | assert out.data.dram == [(POWER_CONSUMPTION_DRAM - DRAM_0_VALUE)] 63 | -------------------------------------------------------------------------------- /tests/acceptation/test_decorator_measureit.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | from tests.utils import fs_one_socket, write_new_energy_value, PKG_0_VALUE, DRAM_0_VALUE 22 | # from mock import patch 23 | # import time 24 | 25 | import pyRAPL 26 | 27 | POWER_CONSUMPTION_PKG = 20000 28 | POWER_CONSUMPTION_DRAM = 30000 29 | NUMBER_OF_ITERATIONS = 5 30 | 31 | 32 | def test_decorator_measureit(fs_one_socket): 33 | """ 34 | Test to measure the energy consumption of a function using the measure decorator 35 | 36 | - decorate a function with the measure decorator and use a CSVOutput 37 | - launch the function 38 | - read the produced csv file 39 | 40 | Test if: 41 | - the file contains 1 line + 1 header 42 | - a line contains the DRAM energy consumption 43 | - a line contains the PKG energy consumption 44 | """ 45 | pyRAPL.setup() 46 | 47 | csv_output = pyRAPL.outputs.CSVOutput('output.csv') 48 | 49 | @pyRAPL.measureit(output=csv_output, number=NUMBER_OF_ITERATIONS) 50 | def measurable_function(a): 51 | # Power consumption of the function 52 | write_new_energy_value(POWER_CONSUMPTION_PKG, pyRAPL.Device.PKG, 0) 53 | write_new_energy_value(POWER_CONSUMPTION_DRAM, pyRAPL.Device.DRAM, 0) 54 | return 1 + a 55 | 56 | measurable_function(1) 57 | 58 | csv_output.save() 59 | 60 | csv = open('output.csv', 'r') 61 | 62 | # flush header 63 | csv.readline() 64 | 65 | n_lines = 0 66 | for line in csv: 67 | n_lines += 1 68 | content = line.split(',') 69 | print(content) 70 | assert content[0] == 'measurable_function' 71 | assert content[3] == str((POWER_CONSUMPTION_PKG - PKG_0_VALUE) / NUMBER_OF_ITERATIONS) 72 | assert content[4] == str((POWER_CONSUMPTION_DRAM - DRAM_0_VALUE) / NUMBER_OF_ITERATIONS) 73 | 74 | assert n_lines == 1 75 | -------------------------------------------------------------------------------- /tests/acceptation/test_normal_measure_bench.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | from tests.utils import fs_one_socket, write_new_energy_value, PKG_0_VALUE, DRAM_0_VALUE 22 | import pyRAPL 23 | 24 | POWER_CONSUMPTION_PKG = 20000 25 | POWER_CONSUMPTION_DRAM = 30000 26 | 27 | 28 | def measurable_function(a): 29 | # Power consumption of the function 30 | write_new_energy_value(POWER_CONSUMPTION_PKG, pyRAPL.Device.PKG, 0) 31 | write_new_energy_value(POWER_CONSUMPTION_DRAM, pyRAPL.Device.DRAM, 0) 32 | return 1 + a 33 | 34 | 35 | def test_nomal_measure_bench(fs_one_socket): 36 | """ 37 | Test to measure the energy consumption of a function using the Measurement class 38 | 39 | - launch the measure 40 | - write a new value to the RAPL power measurement api file 41 | - launch a function 42 | - end the measure 43 | 44 | Test if: 45 | - the energy consumption measured is the delta between the first and the last value in the RAPL power measurement 46 | file 47 | """ 48 | pyRAPL.setup() 49 | measure = pyRAPL.Measurement('toto') 50 | measure.begin() 51 | measurable_function(1) 52 | measure.end() 53 | 54 | assert measure.result.pkg == [(POWER_CONSUMPTION_PKG - PKG_0_VALUE)] 55 | assert measure.result.dram == [(POWER_CONSUMPTION_DRAM - DRAM_0_VALUE)] 56 | -------------------------------------------------------------------------------- /tests/integration/outputs/test_csv_output.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import pytest 21 | import pyfakefs 22 | 23 | import os 24 | 25 | from pyRAPL import Result 26 | from pyRAPL.outputs import CSVOutput 27 | 28 | def test_add_2_result_in_empty_file(fs): 29 | """ 30 | Use a CSVOutput instance to write 2 result in an empty csv file named 'toto.csv' 31 | Each result contains power consumption of two sockets 32 | 33 | Test if: 34 | - Before calling the save method, the csv file contains only the header 35 | - After calling the save method, the csv file contains 4 lines containing the result values 36 | """ 37 | output = CSVOutput('toto.csv') 38 | result_list = [ 39 | Result('toto', 0, 0.1, [0.1111, 0.2222], [0.3333, 0.4444]), 40 | Result('titi', 0, 0.2, [0.5555, 0.6666], [0.7777, 0.8888]), 41 | ] 42 | 43 | for result in result_list: 44 | output.add(result) 45 | 46 | assert os.path.exists('toto.csv') 47 | 48 | csv_file = open('toto.csv', 'r') 49 | assert csv_file.readline() == 'label,timestamp,duration,pkg,dram,socket\n' 50 | 51 | output.save() 52 | for result in result_list: 53 | line1 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[0]},{result.dram[0]},0\n""" 54 | line2 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[1]},{result.dram[1]},1\n""" 55 | 56 | assert line1 == csv_file.readline() 57 | assert line2 == csv_file.readline() 58 | assert csv_file.readline() == '' 59 | 60 | 61 | def test_add_2_result_in_non_empty_file(fs): 62 | """ 63 | Use a CSVOutput instance to write 2 result in an non empty csv file named 'toto.csv' 64 | Each result contains power consumption of two sockets 65 | 66 | before adding result, the csv file contains a header an a line of result containing only 0 values 67 | 68 | Test if: 69 | - Before calling the save method, the csv file contains only the header and one line 70 | - After calling the save method, the csv file contains 4 lines containing the result values 71 | """ 72 | 73 | fs.create_file('toto.csv', contents='label,timestamp,duration,pkg,dram,socket\n0,0,0,0,0,0\n') 74 | output = CSVOutput('toto.csv') 75 | 76 | result_list = [ 77 | Result('toto', 0, 0.1, [0.1111, 0.2222], [0.3333, 0.4444]), 78 | Result('titi', 0, 0.2, [0.5555, 0.6666], [0.7777, 0.8888]), 79 | ] 80 | 81 | for result in result_list: 82 | output.add(result) 83 | 84 | assert os.path.exists('toto.csv') 85 | 86 | csv_file = open('toto.csv', 'r') 87 | assert csv_file.readline() == 'label,timestamp,duration,pkg,dram,socket\n' 88 | assert csv_file.readline() == '0,0,0,0,0,0\n' 89 | assert csv_file.readline() == '' 90 | 91 | output.save() 92 | for result in result_list: 93 | line1 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[0]},{result.dram[0]},0\n""" 94 | line2 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[1]},{result.dram[1]},1\n""" 95 | 96 | assert line1 == csv_file.readline() 97 | assert line2 == csv_file.readline() 98 | assert csv_file.readline() == '' 99 | 100 | 101 | def test_add_2_result_in_non_empty_file_non_append(fs): 102 | """ 103 | Use a CSVOutput instance to overwrite 2 results on a non empty csv file named 'toto.csv' 104 | Each result contains power consumption of two sockets 105 | 106 | before adding result, the csv file contains a header an a line of result containing only 0 values 107 | 108 | Test if: 109 | - Before calling the save method, the csv file contains only the header and one line 110 | - After calling the save method, the csv file contains 2 lines containing the new results values 111 | """ 112 | 113 | fs.create_file('toto.csv', contents='label,timestamp,duration,pkg,dram,socket\n0,0,0,0,0,0\n') 114 | output = CSVOutput('toto.csv', append='') 115 | 116 | result_list = [ 117 | Result('toto', 0, 0.1, [0.1111, 0.2222], [0.3333, 0.4444]), 118 | Result('titi', 0, 0.2, [0.5555, 0.6666], [0.7777, 0.8888]), 119 | ] 120 | 121 | for result in result_list: 122 | output.add(result) 123 | 124 | assert os.path.exists('toto.csv') 125 | 126 | csv_file = open('toto.csv', 'r') 127 | assert csv_file.readline() == 'label,timestamp,duration,pkg,dram,socket\n' 128 | 129 | output.save() 130 | for result in result_list: 131 | line1 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[0]},{result.dram[0]},0\n""" 132 | line2 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[1]},{result.dram[1]},1\n""" 133 | 134 | assert line1 == csv_file.readline() 135 | assert line2 == csv_file.readline() 136 | assert csv_file.readline() == '' 137 | 138 | 139 | def test_two_save_call(fs): 140 | """ 141 | Use a CSVOutput instance to write 1 result in an empty csv file named 'toto.csv' 142 | 143 | After saving the first result, write another result in the csv file 144 | 145 | Each result contains power consumption of two sockets 146 | 147 | Test if: 148 | - Before calling the fisrt save method, the csv file contains only the header and one line 149 | - After calling the fisrt save method, the csv file contains 2 lines containing the result values 150 | - After calling the second save method, the csv file contains 4 lines containing the results values 151 | """ 152 | 153 | result_list = [ 154 | Result('toto', 0, 0.1, [0.1111, 0.2222], [0.3333, 0.4444]), 155 | Result('titi', 0, 0.2, [0.5555, 0.6666], [0.7777, 0.8888]), 156 | ] 157 | 158 | output = CSVOutput('toto.csv') 159 | assert os.path.exists('toto.csv') 160 | csv_file = open('toto.csv', 'r') 161 | assert csv_file.readline() == 'label,timestamp,duration,pkg,dram,socket\n' 162 | assert csv_file.readline() == '' # end of file 163 | 164 | result = result_list[0] 165 | output.add(result) 166 | output.save() 167 | 168 | line1 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[0]},{result.dram[0]},0\n""" 169 | line2 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[1]},{result.dram[1]},1\n""" 170 | 171 | assert line1 == csv_file.readline() 172 | assert line2 == csv_file.readline() 173 | assert csv_file.readline() == '' 174 | csv_file.close() 175 | 176 | output.add(result_list[1]) 177 | output.save() 178 | csv_file = open('toto.csv', 'r') 179 | 180 | assert csv_file.readline() == 'label,timestamp,duration,pkg,dram,socket\n' 181 | for result in result_list: 182 | line1 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[0]},{result.dram[0]},0\n""" 183 | line2 = f"""{result.label},{result.timestamp},{result.duration},{result.pkg[1]},{result.dram[1]},1\n""" 184 | 185 | assert line1 == csv_file.readline() 186 | assert line2 == csv_file.readline() 187 | assert csv_file.readline() == '' 188 | 189 | 190 | def test_add_2_result_in_empty_file_semicolon_separator(fs): 191 | """ 192 | Use a CSVOutput instance to write 2 result in an empty csv file named 'toto.csv' 193 | Each result contains power consumption of two sockets 194 | Each values must be separated with a semicolong 195 | 196 | Test if: 197 | - Before calling the save method, the csv file contains only the header 198 | - After calling the save method, the csv file contains 4 lines containing the result values separated with 199 | semicolon 200 | """ 201 | output = CSVOutput('toto.csv', separator=';') 202 | result_list = [ 203 | Result('toto', 0, 0.1, [0.1111, 0.2222], [0.3333, 0.4444]), 204 | Result('titi', 0, 0.2, [0.5555, 0.6666], [0.7777, 0.8888]), 205 | ] 206 | 207 | for result in result_list: 208 | output.add(result) 209 | 210 | assert os.path.exists('toto.csv') 211 | 212 | csv_file = open('toto.csv', 'r') 213 | assert csv_file.readline() == 'label;timestamp;duration;pkg;dram;socket\n' 214 | 215 | output.save() 216 | for result in result_list: 217 | line1 = f"""{result.label};{result.timestamp};{result.duration};{result.pkg[0]};{result.dram[0]};0\n""" 218 | line2 = f"""{result.label};{result.timestamp};{result.duration};{result.pkg[1]};{result.dram[1]};1\n""" 219 | 220 | assert line1 == csv_file.readline() 221 | assert line2 == csv_file.readline() 222 | assert csv_file.readline() == '' 223 | -------------------------------------------------------------------------------- /tests/integration/outputs/test_mongo_output.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import time 21 | import pytest 22 | import pymongo 23 | from mock import patch, Mock 24 | 25 | from pyRAPL import Result 26 | from pyRAPL.outputs import MongoOutput 27 | 28 | @patch('pymongo.MongoClient') 29 | def test_save(_): 30 | 31 | output = MongoOutput(None, None, None) 32 | output._collection.insert_many = Mock() 33 | result = Result('toto', 0, 0.1, [0.1111, 0.2222], [0.3333, 0.4444]) 34 | output.add(result) 35 | output.save() 36 | 37 | args = output._collection.insert_many.call_args[0][0] 38 | 39 | for i in range(2): 40 | assert args[i]['label'] == result.label 41 | assert args[i]['timestamp'] == result.timestamp 42 | assert args[i]['duration'] == result.duration 43 | assert args[i]['pkg'] == result.pkg[i] 44 | assert args[i]['dram'] == result.dram[i] 45 | assert args[i]['socket'] == i 46 | -------------------------------------------------------------------------------- /tests/integration/outputs/test_pandas_output.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import pytest 21 | import time 22 | 23 | from pyRAPL import Result 24 | from pyRAPL.outputs import DataFrameOutput 25 | 26 | 27 | def assert_line_equal_result(df, n, result): 28 | assert df.iat[n, 0] == result.label 29 | assert df.iat[n, 1] == time.ctime(result.timestamp) 30 | assert df.iat[n, 2] == result.duration 31 | assert df.iat[n, 3] == result.pkg[0] 32 | assert df.iat[n, 4] == result.dram[0] 33 | assert df.iat[n, 5] == 0 34 | 35 | assert df.iat[n + 1, 0] == result.label 36 | assert df.iat[n + 1, 1] == time.ctime(result.timestamp) 37 | assert df.iat[n + 1, 2] == result.duration 38 | assert df.iat[n + 1, 3] == result.pkg[1] 39 | assert df.iat[n + 1, 4] == result.dram[1] 40 | assert df.iat[n + 1, 5] == 1 41 | 42 | # assert df.iat[n, 3][1] == result.pkg[1] 43 | # assert df.iat[n, 4][1] == result.dram[1 44 | 45 | 46 | def test_dataframe(): 47 | """ 48 | Add 2 result to a dataframe 49 | """ 50 | 51 | output = DataFrameOutput() 52 | result_list = [ 53 | Result('toto', 0, 0.1, [0.1111, 0.2222], [0.3333, 0.4444]), 54 | Result('titi', 0, 0.2, [0.5555, 0.6666], [0.7777, 0.8888]), 55 | ] 56 | 57 | output.add(result_list[0]) 58 | 59 | assert_line_equal_result(output.data, 0, result_list[0]) 60 | 61 | output.add(result_list[1]) 62 | 63 | for i in range(2): 64 | assert_line_equal_result(output.data, 2 * i, result_list[i]) 65 | -------------------------------------------------------------------------------- /tests/unit/outputs/test_print_output.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import time 21 | 22 | from pyRAPL import Result 23 | from pyRAPL.outputs import PrintOutput 24 | 25 | 26 | def test_non_raw_output(): 27 | """ 28 | run PrintOutput._format_output to print non raw result 29 | 30 | Test if: 31 | the returned string is correct 32 | """ 33 | result = Result('toto', 0, 0.23456, [0.34567], [0.45678, 0.56789]) 34 | correct_value = f"""Label : toto 35 | Begin : {time.ctime(result.timestamp)} 36 | Duration : 0.2346 us 37 | ------------------------------- 38 | PKG : 39 | \tsocket 0 : 0.3457 uJ 40 | ------------------------------- 41 | DRAM : 42 | \tsocket 0 : 0.4568 uJ 43 | \tsocket 1 : 0.5679 uJ 44 | -------------------------------""" 45 | output = PrintOutput() 46 | print(output._format_output(result)) 47 | assert output._format_output(result) == correct_value 48 | -------------------------------------------------------------------------------- /tests/unit/test_device_api.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | import pytest 22 | 23 | from pyRAPL import PkgAPI, DramAPI, DeviceAPIFactory, Device, PyRAPLCantInitDeviceAPI, PyRAPLBadSocketIdException 24 | from pyRAPL.device_api import cpu_ids, get_socket_ids 25 | from tests.utils import PKG_0_FILE_NAME, PKG_0_VALUE, PKG_1_FILE_NAME, PKG_1_VALUE 26 | from tests.utils import DRAM_0_FILE_NAME, DRAM_0_VALUE, DRAM_1_FILE_NAME, DRAM_1_VALUE 27 | from tests.utils import empty_fs, fs_one_socket, fs_two_socket, fs_one_socket_no_dram 28 | 29 | class DeviceParameters: 30 | def __init__(self, device_class, socket0_filename, socket0_value, socket1_filename, socket1_value): 31 | print(device_class.__name__) 32 | self.device_class = device_class 33 | self.socket0_filename = socket0_filename 34 | self.socket0_value = socket0_value 35 | self.socket1_filename = socket1_filename 36 | self.socket1_value = socket1_value 37 | 38 | @pytest.fixture(params=[DeviceParameters(PkgAPI, PKG_0_FILE_NAME, PKG_0_VALUE, PKG_1_FILE_NAME, PKG_1_VALUE), 39 | DeviceParameters(DramAPI, DRAM_0_FILE_NAME, DRAM_0_VALUE, DRAM_1_FILE_NAME, DRAM_1_VALUE)]) 40 | def device_api_param(request): 41 | """ 42 | parametrize a test with PkgAPI and DramAPI 43 | """ 44 | print(request.param.device_class.__name__) 45 | return request.param 46 | 47 | ########## 48 | # CPU ID # 49 | ########## 50 | def test_cpu_ids_normal_interval(fs): 51 | fs.create_file('/sys/devices/system/cpu/present', contents='1-3') 52 | assert cpu_ids() == [1, 2, 3] 53 | 54 | 55 | def test_cpu_ids_normal_list(fs): 56 | fs.create_file('/sys/devices/system/cpu/present', contents='1,3') 57 | assert cpu_ids() == [1, 3] 58 | 59 | 60 | def test_cpu_ids_interval_and_list(fs): 61 | fs.create_file('/sys/devices/system/cpu/present', contents='1,3-5') 62 | assert cpu_ids() == [1, 3, 4, 5] 63 | 64 | 65 | def test_cpu_ids_shuffuled_interval_and_list(fs): 66 | fs.create_file('/sys/devices/system/cpu/present', contents='2,3-6,11,1') 67 | assert cpu_ids() == [2, 3, 4, 5, 6, 11, 1] 68 | 69 | 70 | ############# 71 | # SOCKET ID # 72 | ############# 73 | def test_socket_ids(fs): 74 | fs.create_file('/sys/devices/system/cpu/present', contents='0-2') 75 | fs.create_file('/sys/devices/system/cpu/cpu0/topology/physical_package_id', contents='0') 76 | fs.create_file('/sys/devices/system/cpu/cpu1/topology/physical_package_id', contents='1') 77 | fs.create_file('/sys/devices/system/cpu/cpu2/topology/physical_package_id', contents='0') 78 | assert get_socket_ids() == [0, 1] 79 | 80 | 81 | ############# 82 | # INIT TEST # 83 | ############# 84 | def test_init_no_rapl_files(empty_fs, device_api_param): 85 | """ 86 | create a DeviceAPI (PkgAPI and DramAPI) instance with a filesystem without rapl api files 87 | Test if: 88 | - a PyRAPLCantInitDeviceAPI is raised 89 | """ 90 | with pytest.raises(PyRAPLCantInitDeviceAPI): 91 | device_api_param.device_class() 92 | 93 | 94 | def test_init_psys_files_one_sockets(fs_one_socket, device_api_param): 95 | """ 96 | create a DeviceAPI (PkgAPI and DramAPI) instance with a filesystem containing rapl files for socket 0 and psys 97 | domain 98 | Test if: 99 | - the attribute DeviceAPI._sys_files is a list of length 1 100 | - the attribute DeviceAPI._sys_files contains a file (test the file's name) 101 | """ 102 | fs_one_socket.create_file('/sys/class/powercap/intel-rapl/intel-rapl:1/name', contents='psys\n') 103 | 104 | device_api = device_api_param.device_class() 105 | assert isinstance(device_api._sys_files, list) 106 | assert len(device_api._sys_files) == 1 107 | assert device_api._sys_files[0].name == device_api_param.socket0_filename 108 | 109 | 110 | def test_init_psys_files_two_sockets(fs): 111 | """ 112 | create a DeviceAPI (PkgAPI and DramAPI) instance with a filesystem containing rapl files for socket 0 and 1 and psys 113 | domain 114 | Test if: 115 | - the attribute DeviceAPI._sys_files is a list of length 2 116 | - the attribute DeviceAPI._sys_files contains two file (test the file's name) 117 | """ 118 | fs.create_file('/sys/devices/system/cpu/present', contents='0-1') 119 | fs.create_file('/sys/devices/system/cpu/cpu0/topology/physical_package_id', contents='0') 120 | fs.create_file('/sys/devices/system/cpu/cpu1/topology/physical_package_id', contents='1') 121 | 122 | fs.create_file('/sys/class/powercap/intel-rapl/intel-rapl:0/name', contents='package-0\n') 123 | fs.create_file(PKG_0_FILE_NAME, contents=str(PKG_0_VALUE) + '\n') 124 | 125 | fs.create_file('/sys/class/powercap/intel-rapl/intel-rapl:1/name', contents='psys\n') 126 | 127 | fs.create_file('/sys/class/powercap/intel-rapl/intel-rapl:2/name', contents='package-1\n') 128 | fs.create_file('/sys/class/powercap/intel-rapl/intel-rapl:2/energy_uj', contents=str(PKG_1_VALUE) + '\n') 129 | 130 | device_api = PkgAPI() 131 | assert isinstance(device_api._sys_files, list) 132 | assert len(device_api._sys_files) == 2 133 | assert device_api._sys_files[0].name == PKG_0_FILE_NAME 134 | assert device_api._sys_files[1].name == '/sys/class/powercap/intel-rapl/intel-rapl:2/energy_uj' 135 | 136 | 137 | def test_init_one_file_one_socket(fs_one_socket, device_api_param): 138 | """ 139 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 140 | The filesystem contains a rapl file for the socket 0 141 | Test if: 142 | - the attribute DeviceAPI._sys_files is a list of length 1 143 | - the attribute DeviceAPI._sys_files contains a file (test the file's name) 144 | """ 145 | device_api = device_api_param.device_class() 146 | assert isinstance(device_api._sys_files, list) 147 | assert len(device_api._sys_files) == 1 148 | assert device_api._sys_files[0].name == device_api_param.socket0_filename 149 | 150 | 151 | def test_init_two_files_one_socket(fs_two_socket, device_api_param): 152 | """ 153 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 154 | The filesystem contains a rapl file for the socket 0 and 1 155 | Test if: 156 | - the attribute DeviceAPI._sys_files is a list of length 1 157 | - the attribute DeviceAPI._sys_files contains a file (test the file's name) 158 | """ 159 | device_api = device_api_param.device_class(socket_ids=[0]) 160 | assert isinstance(device_api._sys_files, list) 161 | assert len(device_api._sys_files) == 1 162 | assert device_api._sys_files[0].name == device_api_param.socket0_filename 163 | 164 | 165 | def test_init_two_files_last_socket(fs_two_socket, device_api_param): 166 | """ 167 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 1 168 | The filesystem contains a rapl file for the socket 0 and 1 169 | Test if: 170 | - the attribute DeviceAPI._sys_files is a list of length 1 171 | - the attribute DeviceAPI._sys_files contains one files (test the file's name) 172 | """ 173 | device_api = device_api_param.device_class(socket_ids=[1]) 174 | 175 | assert isinstance(device_api._sys_files, list) 176 | assert len(device_api._sys_files) == 1 177 | 178 | assert device_api._sys_files[0].name == device_api_param.socket1_filename 179 | 180 | 181 | def test_init_one_file_two_socket(fs_one_socket, device_api_param): 182 | """ 183 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 and 1 184 | The filesystem contains a rapl file for the socket 0 185 | Test if: 186 | - a PyRAPLBadSocketIdException is raised 187 | """ 188 | with pytest.raises(PyRAPLBadSocketIdException): 189 | device_api_param.device_class(socket_ids=[0, 1]) 190 | 191 | 192 | def test_init_two_files_two_socket(fs_two_socket, device_api_param): 193 | """ 194 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 and 1 195 | The filesystem contains a rapl file for the socket 0 and 1 196 | Test if: 197 | - the attribute DeviceAPI._sys_files is a list of length 2 198 | - the attribute DeviceAPI._sys_files contains two files (test the file's name) 199 | """ 200 | device_api = device_api_param.device_class(socket_ids=[0, 1]) 201 | 202 | assert isinstance(device_api._sys_files, list) 203 | assert len(device_api._sys_files) == 2 204 | 205 | assert device_api._sys_files[0].name == device_api_param.socket0_filename 206 | assert device_api._sys_files[1].name == device_api_param.socket1_filename 207 | 208 | 209 | def test_init_two_files_all_socket(fs_two_socket, device_api_param): 210 | """ 211 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 and 1 212 | The filesystem contains a rapl file for the socket 0 and 1 213 | Test if: 214 | - the attribute DeviceAPI._sys_files is a list of length 2 215 | - the attribute DeviceAPI._sys_files contains two files (test the file's name) 216 | """ 217 | device_api = device_api_param.device_class() 218 | 219 | assert isinstance(device_api._sys_files, list) 220 | assert len(device_api._sys_files) == 2 221 | 222 | assert device_api._sys_files[0].name == device_api_param.socket0_filename 223 | assert device_api._sys_files[1].name == device_api_param.socket1_filename 224 | 225 | def test_init_dram_api_without_dram_files(fs_one_socket_no_dram): 226 | """ 227 | create a DramAPI instance to measure energy consumption of device on socket 0 228 | The file system contains a rapl file for the socket 0 but no dram support 229 | Test if: 230 | - a PyRAPLCantInitDeviceAPI is raised 231 | """ 232 | with pytest.raises(PyRAPLCantInitDeviceAPI): 233 | DramAPI() 234 | 235 | 236 | ########## 237 | # ENERGY # 238 | ########## 239 | def test_energy_one_file_socket_0(device_api_param, fs_one_socket): 240 | """ 241 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 242 | use the energy function to get the energy consumption 243 | The filesystem contains a rapl file for the socket 0 244 | Test if: 245 | - the returned value is a tuple of length 1 containing float 246 | - the first value of the tuple is the energy consumption of the corresponding device on socket 0 247 | """ 248 | device_api = device_api_param.device_class() 249 | 250 | energy = device_api.energy() 251 | assert isinstance(energy, tuple) 252 | assert len(energy) == 1 253 | assert isinstance(energy[0], float) 254 | 255 | assert energy[0] == device_api_param.socket0_value 256 | 257 | 258 | def test_energy_two_files_socket_0(device_api_param, fs_two_socket): 259 | """ 260 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 261 | use the energy function to get the energy consumption 262 | The filesystem contains a rapl file for the socket 0 and 1 263 | Test if: 264 | - the returned value is a tuple of length 1 containing float 265 | - the first value of the tuple is the energy consumption of the corresponding device on socket 0 266 | """ 267 | device_api = device_api_param.device_class(socket_ids=[0]) 268 | 269 | energy = device_api.energy() 270 | assert isinstance(energy, tuple) 271 | assert len(energy) == 1 272 | assert isinstance(energy[0], float) 273 | 274 | assert energy[0] == device_api_param.socket0_value 275 | 276 | 277 | def test_energy_two_files_socket_1(device_api_param, fs_two_socket): 278 | """ 279 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 1 280 | use the energy function to get the energy consumption 281 | The filesystem contains a rapl file for the socket 0 and 1 282 | Test if: 283 | - the returned value is a tuple of length 2 containing float 284 | - the first value of the tuple is -1 285 | - the second value of the tuple is the energy consumption of the corresponding device on socket 1 286 | """ 287 | device_api = device_api_param.device_class(socket_ids=[1]) 288 | 289 | energy = device_api.energy() 290 | assert isinstance(energy, tuple) 291 | assert len(energy) == 2 292 | 293 | assert energy[0] == -1 294 | assert isinstance(energy[1], float) 295 | assert energy[1] == device_api_param.socket1_value 296 | 297 | 298 | def test_energy_two_files_socket_0_1(device_api_param, fs_two_socket): 299 | """ 300 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 0 and 1 301 | use the energy function to get the energy consumption 302 | The filesystem contains a rapl file for the socket 0 and 1 303 | Test if: 304 | - the returned value is a tuple of length 2 containing float 305 | - the first value of the tuple is the energy consumption of the corresponding device on socket 0 306 | - the second value of the tuple is the energy consumption of the corresponding device on socket 1 307 | """ 308 | device_api = device_api_param.device_class(socket_ids=[0, 1]) 309 | 310 | energy = device_api.energy() 311 | assert isinstance(energy, tuple) 312 | assert len(energy) == 2 313 | for val in energy: 314 | assert isinstance(val, float) 315 | 316 | assert energy[0] == device_api_param.socket0_value 317 | assert energy[1] == device_api_param.socket1_value 318 | 319 | 320 | def test_energy_two_files_socket_all(device_api_param, fs_two_socket): 321 | """ 322 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on all socket 323 | use the energy function to get the energy consumption 324 | The filesystem contains a rapl file for the socket 0 and 1 325 | Test if: 326 | - the returned value is a tuple of length 2 containing float 327 | - the first value of the tuple is the energy consumption of the corresponding device on socket 0 328 | - the second value of the tuple is the energy consumption of the corresponding device on socket 1 329 | """ 330 | device_api = device_api_param.device_class() 331 | 332 | energy = device_api.energy() 333 | assert isinstance(energy, tuple) 334 | assert len(energy) == 2 335 | for val in energy: 336 | assert isinstance(val, float) 337 | 338 | assert energy[0] == device_api_param.socket0_value 339 | assert energy[1] == device_api_param.socket1_value 340 | 341 | 342 | def test_energy_two_files_socket_1_0(device_api_param, fs_two_socket): 343 | """ 344 | create a DeviceAPI (PkgAPI and DramAPI) instance to measure energy consumption of device on socket 1 and 0 345 | use the energy function to get the energy consumption 346 | The filesystem contains a rapl file for the socket 0 and 1 347 | Test if: 348 | - the returned value is a tuple of length 2 containing float 349 | - the first value of the tuple is the energy consumption of the corresponding device on socket 0 350 | - the second value of the tuple is the energy consumption of the corresponding device on socket 1 351 | """ 352 | device_api = device_api_param.device_class(socket_ids=[1, 0]) 353 | 354 | energy = device_api.energy() 355 | assert isinstance(energy, tuple) 356 | assert len(energy) == 2 357 | for val in energy: 358 | assert isinstance(val, float) 359 | 360 | assert energy[0] == device_api_param.socket0_value 361 | assert energy[1] == device_api_param.socket1_value 362 | 363 | 364 | ########### 365 | # FACTORY # 366 | ########### 367 | def test_creating_dram_device(fs_two_socket): 368 | """ 369 | use the DeviceAPIFactory to create a DramAPI instance 370 | Test if: 371 | - the returned instance is an instance of DramAPI 372 | """ 373 | assert isinstance(DeviceAPIFactory.create_device_api(Device.DRAM, None), DramAPI) 374 | 375 | 376 | def test_creating_pkg_device(fs_two_socket): 377 | """ 378 | use the DeviceAPIFactory to create a PkgAPI instance 379 | Test if: 380 | - the returned instance is an instance of PkgAPI 381 | """ 382 | assert isinstance(DeviceAPIFactory.create_device_api(Device.PKG, None), PkgAPI) 383 | -------------------------------------------------------------------------------- /tests/unit/test_results.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | import pytest 22 | from pyRAPL import Result 23 | 24 | 25 | def test_fulldiv(): 26 | val = Result('toto', 0, 0.23456, [0.34567], [0.45678, 0.56789]) 27 | correct_value = Result('toto', 0, 0.023456, [0.034567], [0.045678, 0.056789]) 28 | result = val / 10 29 | assert result.label == correct_value.label 30 | assert result.timestamp == correct_value.timestamp 31 | assert round(result.duration, 6) == correct_value.duration 32 | assert [round(x, 6) for x in result.pkg] == correct_value.pkg 33 | assert [round(x, 6) for x in result.dram] == correct_value.dram 34 | assert len(result.pkg) == 1 35 | assert len(result.dram) == 2 36 | -------------------------------------------------------------------------------- /tests/unit/test_sensor.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | import pytest 22 | 23 | from pyRAPL import Sensor, Device, PkgAPI 24 | from pyRAPL import PyRAPLCantRecordEnergyConsumption, PyRAPLCantRecordEnergyConsumption, PyRAPLBadSocketIdException 25 | from tests.utils import PKG_0_VALUE, PKG_1_VALUE, DRAM_0_VALUE, DRAM_1_VALUE 26 | from tests.utils import empty_fs, fs_one_socket, fs_two_socket, fs_one_socket_no_dram 27 | 28 | 29 | ######## 30 | # INIT # 31 | ######## 32 | def test_no_rapl_api(empty_fs): 33 | """ 34 | create an instance of Sensor to monitor all device available 35 | the system have no rapl api 36 | Test if: 37 | - a PyRAPLCantRecordEnergyConsumption is raise 38 | """ 39 | with pytest.raises(PyRAPLCantRecordEnergyConsumption): 40 | Sensor() 41 | 42 | 43 | def test_no_rapl_api_for_dram(fs_one_socket_no_dram): 44 | """ 45 | create an instance of Sensor to monitor DRAM 46 | the system have no rapl api for dram but have one for package 47 | Test if: 48 | - a PyRAPLCantRecordEnergyConsumption is raise 49 | """ 50 | with pytest.raises(PyRAPLCantRecordEnergyConsumption): 51 | Sensor(devices=[Device.DRAM]) 52 | 53 | def test_measure_all_no_rapl_api_for_dram(fs_one_socket_no_dram): 54 | """ 55 | create an instance of Sensor to all available devices 56 | the system have no rapl api for dram but have one for package 57 | Test if: 58 | - the sensor attribute _device_api contains only one entrie. 59 | - The entrie key is Device.Pkg and its value is an instance of PkgAPI 60 | """ 61 | sensor = Sensor() 62 | assert len(sensor._device_api) == 1 63 | assert Device.PKG in sensor._device_api 64 | assert isinstance(sensor._device_api[Device.PKG], PkgAPI) 65 | 66 | 67 | def test_monitor_socket_1_but_one_socket(fs_one_socket): 68 | """ 69 | create an instance of Sensor to monitor all device available on socket 1 70 | the system have no rapl api for socket 1 but have one for socket 0 71 | Test if: 72 | - a PyRAPLBadSocketIdException is raise 73 | """ 74 | with pytest.raises(PyRAPLBadSocketIdException): 75 | Sensor(socket_ids=[1]) 76 | 77 | 78 | ########## 79 | # ENERGY # 80 | ########## 81 | class SensorParam: 82 | def __init__(self, devices, sockets, one_socket_result, two_socket_result): 83 | 84 | def div_if_positive_list(t, n): 85 | r = [] 86 | for v in t: 87 | if v > 0: 88 | r.append(v / n) 89 | else: 90 | r.append(v) 91 | return r 92 | 93 | self.devices = devices 94 | self.sockets = sockets 95 | self.one_socket_result = one_socket_result 96 | self.two_socket_result = two_socket_result 97 | # assert False 98 | 99 | 100 | @pytest.fixture(params=[ 101 | SensorParam(None, None, [PKG_0_VALUE, DRAM_0_VALUE], [PKG_0_VALUE, DRAM_0_VALUE, PKG_1_VALUE, DRAM_1_VALUE]), 102 | SensorParam([Device.PKG], None, [PKG_0_VALUE, -1], [PKG_0_VALUE, -1, PKG_1_VALUE, -1]), 103 | SensorParam([Device.DRAM], None, [-1, DRAM_0_VALUE], [-1, DRAM_0_VALUE, -1, DRAM_1_VALUE]), 104 | SensorParam([Device.PKG, Device.DRAM], None, [PKG_0_VALUE, DRAM_0_VALUE], [PKG_0_VALUE, DRAM_0_VALUE, PKG_1_VALUE, 105 | DRAM_1_VALUE]), 106 | 107 | SensorParam(None, [0], [PKG_0_VALUE, DRAM_0_VALUE], [PKG_0_VALUE, DRAM_0_VALUE]), 108 | SensorParam([Device.PKG], [0], [PKG_0_VALUE, -1], [PKG_0_VALUE, -1]), 109 | SensorParam([Device.DRAM], [0], [-1, DRAM_0_VALUE], [-1, DRAM_0_VALUE]), 110 | SensorParam([Device.PKG, Device.DRAM], [0], [PKG_0_VALUE, DRAM_0_VALUE], [PKG_0_VALUE, DRAM_0_VALUE]), 111 | ]) 112 | def sensor_param(request): 113 | """ 114 | parametrize a test with sensor parameters 115 | """ 116 | parameters = request.param 117 | print('test parameters') 118 | print('Device list : ' + str(parameters.devices)) 119 | print('socket list : ' + str(parameters.sockets)) 120 | print('one socket result : ' + str(parameters.one_socket_result)) 121 | print('two socket result : ' + str(parameters.two_socket_result)) 122 | 123 | return parameters 124 | 125 | 126 | @pytest.fixture(params=[ 127 | SensorParam(None, [1], None, [-1, -1, PKG_1_VALUE, DRAM_1_VALUE]), 128 | SensorParam([Device.PKG], [1], None, [-1, -1, PKG_1_VALUE, -1]), 129 | SensorParam([Device.DRAM], [1], None, [-1, -1, -1, DRAM_1_VALUE]), 130 | SensorParam([Device.PKG, Device.DRAM], [1], None, [-1, -1, PKG_1_VALUE, DRAM_1_VALUE]), 131 | 132 | SensorParam(None, [0, 1], [PKG_0_VALUE, DRAM_0_VALUE], [PKG_0_VALUE, DRAM_0_VALUE, PKG_1_VALUE, DRAM_1_VALUE]), 133 | SensorParam([Device.PKG], [0, 1], [PKG_0_VALUE, -1], [PKG_0_VALUE, -1, PKG_1_VALUE, -1]), 134 | SensorParam([Device.DRAM], [0, 1], [-1, DRAM_0_VALUE], [-1, DRAM_0_VALUE, -1, DRAM_1_VALUE]), 135 | SensorParam([Device.PKG, Device.DRAM], [0, 1], [PKG_0_VALUE, DRAM_0_VALUE], [PKG_0_VALUE, DRAM_0_VALUE, PKG_1_VALUE, 136 | DRAM_1_VALUE]) 137 | ]) 138 | def sensor_param_monitor_socket_1(request): 139 | parameters = request.param 140 | print('test parameters') 141 | print('Device list : ' + str(parameters.devices)) 142 | print('socket list : ' + str(parameters.sockets)) 143 | print('one socket result : ' + str(parameters.one_socket_result)) 144 | print('two socket result : ' + str(parameters.two_socket_result)) 145 | 146 | return parameters 147 | 148 | 149 | def test_energy_one_socket(fs_one_socket, sensor_param): 150 | """ 151 | Create a sensor with given parameters (see printed output) and get energy of monitored devices 152 | The machine contains only one socket 153 | Test: 154 | - return value of the function 155 | """ 156 | 157 | sensor = Sensor(sensor_param.devices, sensor_param.sockets) 158 | assert sensor.energy() == sensor_param.one_socket_result 159 | 160 | 161 | def test_energy_two_socket(fs_two_socket, sensor_param): 162 | """ 163 | Create a sensor with given parameters (see printed output) and get energy of monitored devices 164 | The machine contains two sockets 165 | Test: 166 | - return value of the function 167 | """ 168 | sensor = Sensor(sensor_param.devices, sensor_param.sockets) 169 | assert sensor.energy() == sensor_param.two_socket_result 170 | 171 | 172 | def test_energy_monitor_socket_1_two_socket(fs_two_socket, sensor_param_monitor_socket_1): 173 | """ 174 | Create a sensor with to monitor package and dram device on socket 1 175 | use the energy function to get the energy consumption of the monitored devices 176 | The machine contains two sockets 177 | Test: 178 | - return value of the function 179 | """ 180 | sensor = Sensor(sensor_param_monitor_socket_1.devices, sensor_param_monitor_socket_1.sockets) 181 | assert sensor.energy() == sensor_param_monitor_socket_1.two_socket_result 182 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # Copyright (c) 2019, INRIA 3 | # Copyright (c) 2019, University of Lille 4 | # All rights reserved. 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | from pyRAPL import Device 22 | import os 23 | import pytest 24 | # import pyfakefs 25 | 26 | SOCKET_0_DIR_NAME = '/sys/class/powercap/intel-rapl/intel-rapl:0' 27 | SOCKET_1_DIR_NAME = '/sys/class/powercap/intel-rapl/intel-rapl:1' 28 | 29 | PKG_0_FILE_NAME = SOCKET_0_DIR_NAME + '/energy_uj' 30 | PKG_0_VALUE = 12345 31 | PKG_1_FILE_NAME = SOCKET_1_DIR_NAME + '/energy_uj' 32 | PKG_1_VALUE = 54321 33 | 34 | DRAM_0_DIR_NAME = SOCKET_0_DIR_NAME + '/intel-rapl:0:0' 35 | DRAM_1_DIR_NAME = SOCKET_1_DIR_NAME + '/intel-rapl:1:0' 36 | 37 | DRAM_0_FILE_NAME = DRAM_0_DIR_NAME + '/energy_uj' 38 | DRAM_0_VALUE = 6789 39 | DRAM_1_FILE_NAME = DRAM_1_DIR_NAME + '/energy_uj' 40 | DRAM_1_VALUE = 9876 41 | 42 | 43 | def write_new_energy_value(val, device, socket_id): 44 | file_names = { 45 | Device.PKG: [PKG_0_FILE_NAME, PKG_1_FILE_NAME], 46 | Device.DRAM: [DRAM_0_FILE_NAME, DRAM_1_FILE_NAME] 47 | } 48 | 49 | api_file_name = file_names[device][socket_id] 50 | if not os.path.exists(api_file_name): 51 | return 52 | api_file = open(api_file_name, 'w') 53 | print(str(val) + '\n') 54 | api_file.write(str(val) + '\n') 55 | api_file.close() 56 | 57 | 58 | @pytest.fixture 59 | def empty_fs(fs): 60 | """ 61 | create an empty file system 62 | """ 63 | fs.create_file('/sys/devices/system/cpu/present', contents='0') 64 | fs.create_file('/sys/devices/system/cpu/cpu0/topology/physical_package_id', contents='0') 65 | return fs 66 | 67 | 68 | @pytest.fixture 69 | def fs_one_socket(fs): 70 | """ 71 | create a file system containing energy metric for package and dram on one socket 72 | """ 73 | fs.create_file('/sys/devices/system/cpu/present', contents='0') 74 | fs.create_file('/sys/devices/system/cpu/cpu0/topology/physical_package_id', contents='0') 75 | 76 | fs.create_file(SOCKET_0_DIR_NAME + '/name', contents='package-0\n') 77 | fs.create_file(PKG_0_FILE_NAME, contents=str(PKG_0_VALUE) + '\n') 78 | 79 | fs.create_file(DRAM_0_DIR_NAME + '/name', contents='dram\n') 80 | fs.create_file(DRAM_0_FILE_NAME, contents=str(DRAM_0_VALUE) + '\n') 81 | return fs 82 | 83 | 84 | @pytest.fixture 85 | def fs_two_socket(fs): 86 | """ 87 | create a file system containing energy metric for package and dram on two socket 88 | """ 89 | fs.create_file('/sys/devices/system/cpu/present', contents='0-1') 90 | fs.create_file('/sys/devices/system/cpu/cpu0/topology/physical_package_id', contents='0') 91 | fs.create_file('/sys/devices/system/cpu/cpu1/topology/physical_package_id', contents='1') 92 | 93 | fs.create_file(SOCKET_0_DIR_NAME + '/name', contents='package-0\n') 94 | fs.create_file(PKG_0_FILE_NAME, contents=str(PKG_0_VALUE) + '\n') 95 | 96 | fs.create_file(DRAM_0_DIR_NAME + '/name', contents='dram\n') 97 | fs.create_file(DRAM_0_FILE_NAME, contents=str(DRAM_0_VALUE) + '\n') 98 | 99 | fs.create_file(SOCKET_1_DIR_NAME + '/name', contents='package-1\n') 100 | fs.create_file(PKG_1_FILE_NAME, contents=str(PKG_1_VALUE) + '\n') 101 | 102 | fs.create_file(DRAM_1_DIR_NAME + '/name', contents='dram\n') 103 | fs.create_file(DRAM_1_FILE_NAME, contents=str(DRAM_1_VALUE) + '\n') 104 | return fs 105 | 106 | 107 | @pytest.fixture 108 | def fs_one_socket_no_dram(fs): 109 | """ 110 | create a file system containing energy metric for package and gpu and sys on one socket 111 | """ 112 | fs.create_file('/sys/devices/system/cpu/present', contents='0') 113 | fs.create_file('/sys/devices/system/cpu/cpu0/topology/physical_package_id', contents='0') 114 | 115 | fs.create_file(SOCKET_0_DIR_NAME + '/name', contents='package-0\n') 116 | fs.create_file(PKG_0_FILE_NAME, contents=str(PKG_0_VALUE) + '\n') 117 | 118 | fs.create_file(SOCKET_0_DIR_NAME + '/intel-rapl:0:0' + '/name', contents='gpu\n') 119 | fs.create_file(SOCKET_0_DIR_NAME + '/intel-rapl:0:1' + '/name', contents='sys\n') --------------------------------------------------------------------------------