├── .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 | [](https://spdx.org/licenses/MIT.html)
4 | [](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')
--------------------------------------------------------------------------------